Skip to Content
Clerk logo

Clerk Docs

Ctrl + K
Go to clerkstage.dev

User metadata

Metadata allows for custom data to be saved on the User object. There are three types of metadata: "unsafe", "public", and "private".

MetadataFrontend APIBackend API
PrivateNo read or write accessRead & write access
PublicRead accessRead & write access
UnsafeRead & write accessRead & write access

Private metadata

Private metadata is only accessible by the backend, which makes this useful for storing sensitive data that you don't want to expose to the frontend. For example, you could store a user's Stripe customer ID.

Setting private metadata

curl.sh
curl -XPATCH -H 'Authorization: Bearer CLERK_SECRET_KEY' -H "Content-type: application/json" -d '{ "private_metadata": { "stripeId": "12356" } }' 'https://api.clerk.com/v1/users/{user_id}/metadata'
pages/api/private.ts
import { clerkClient } from "@clerk/nextjs"; import { NextApiRequest, NextApiResponse } from "next"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { stripeId, userId } = req.body; await clerkClient.users.updateUserMetadata(userId, { privateMetadata: { stripeId: stripeId } }) res.status(200).json({ success: true }); }
app/route/private.ts
import { NextResponse } from 'next/server'; import { clerkClient } from "@clerk/nextjs"; export async function POST() { const { stripeId, userId } = await body.json(); await clerkClient.users.updateUserMetadata(userId, { privateMetadata: { stripeId: stripeId } }); return NextResponse.json({ success: true }); }
private.ts
import { clerkClient } from "@clerk/clerk-sdk-node"; app.post('/updateStripe', (req, res) => { const { stripeId, userId } = await body.json(); await clerkClient.users.updateUserMetadata(userId, { privateMetadata: { stripeId: stripeId } }); res.status(200).json({ success: true }); });
private.go
var client clerk.Client func addStripeCustomerID(user *clerk.User, stripeCustomerID string) error { stripeID := map[string]interface{}{ "stripeID": stripeCustomerID, } user, err := s.clerkClient.Users().UpdateMetadata(sess.UserID, &clerk.updateMetadataRequest{ PrivateMetadata: stripeID, }) if err != nil { panic(err) } }
private.rb
// ruby json example with a private metadata and stripe id require 'clerk' require 'json' privateMetadata = { "stripeID": stripeCustomerID } clerk = Clerk::SDK.new(api_key: "your_clerk_secret_key") clerk.users.updateMetadata("user_xyz", private_metadata: privateMetadata)

Retrieving private metadata

You can retrieve the private metadata for a user by using the getUser method. This method will return the User object which contains the private metadata.

curl.sh
curl -XGET -H 'Authorization: CLERK_SECRET_KEY' -H "Content-type: application/json" 'https://api.clerk.com/v1/users/{user_id}'
app/private/route.ts
import { NextResponse } from 'next/server'; import { clerkClient } from "@clerk/nextjs"; export async function GET(request: Request) { const { stripeId, userId } = await request.body.json(); const user = await clerkClient.users.getUser(userId) return NextResponse.json(user.privateMetadata); }
pages/api/private.ts
import { clerkClient } from "@clerk/nextjs"; import { NextApiRequest, NextApiResponse } from "next"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { userId } = await req.body.json(); const user = await clerkClient.users.getUser(userId) res.status(200).json(user.privateMetadata); }
private.ts
import { clerkClient } from "@clerk/clerk-sdk-node"; app.post('/updateStripe', (req, res) => { const { userId } = await req.body.json(); const user = await clerkClient.users.getUser(userId) res.status(200).json(user.privateMetadata); });
private.go
var client clerk.Client func GetUserMetadata(user *clerk.User, stripeCustomerID string) error { user, err := s.clerkClient.Users().Read(sess.UserID) if err != nil { panic(err) } }
private.rb
// ruby json example with a private metadata and stripe id require 'clerk' clerk = Clerk::SDK.new(api_key: "your_clerk_secret_key") clerk.users.getUser("user_xyz")

Public metadata

Public metadata is accessible by both the frontend and the backend. This is useful for storing data that you want to expose to the frontend, but don't want the user to be able to modify. For example, you could store a custom role for a user.

Setting public metadata

curl.sh
curl -XPATCH -H 'Authorization: Bearer CLERK_SECRET_KEY' -H "Content-type: application/json" -d '{ "public_metadata": { "role": "shopper" } }' 'https://api.clerk.com/v1/users/{user_id}/metadata'
pages/api/public.ts
import { clerkClient } from "@clerk/nextjs"; import { NextApiRequest, NextApiResponse } from "next"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { role, userId } = req.body; await clerkClient.users.updateUserMetadata(userId, { publicMetadata: { role } }) res.status(200).json({ success: true }); }
app/route/public.ts
import { NextResponse } from 'next/server'; import { clerkClient } from "@clerk/nextjs"; export async function POST() { const { role, userId } = await body.json(); await clerkClient.users.updateUserMetadata(userId, { publicMetadata: { role } }) return NextResponse.json({ success: true }); }
public.ts
import { clerkClient } from "@clerk/clerk-sdk-node"; app.post('/updateRole', (req, res) => { const { role, userId } = req.body; await clerkClient.users.updateUserMetadata(userId, { publicMetadata: { role } }); res.status(200).json({ success: true }); });
public.go
var client clerk.Client func addStripeCustomerID(user *clerk.User, role string) error { Role := map[string]interface{}{ "role": role, } user, err := s.clerkClient.Users().UpdateMetadata(sess.UserID, &clerk.updateMetadataRequest{ PublicMetadata: role, }) if err != nil { panic(err) } }
public.rb
// ruby json example with a private metadata and stripe id require 'clerk' require 'json' publicMetadata = { "role": "awesome-user", } clerk = Clerk::SDK.new(api_key: "your_clerk_secret_key") clerk.users.updateMetadata("user_xyz", public_metadata: publicMetadata)

Retrieving public metadata

There are multiple ways to retrieve public metadata. It is available on the User object returned by the useUser hook, and it can also be attached to the session token to be retrieved with auth() and useAuth(). If you need to retrieve public metadata frequently in the backend, the best option is to attach it to the session token and retrieve it from the session token.

To read about customizing your session token and retrieving that data, check out our session token customization guide.

Unsafe metadata

If you need to implement custom fields that will be attached to the User object from the frontend (using our ClerkJS library or the Frontend API), you should choose the unsafeMetadata property.

One common use case for this attribute is to implement custom fields that will be attached to the User object.

Clerk has named this type of metadata "unsafe" since it can be set and accessed by both the Frontend API and the Backend API. This provides a quick method to add custom attributes to a user. These attributes will be stored on the User object and will be available for access at all times.

The "unsafe" custom attributes can be set upon sign-up when creating or updating a SignUp object. After a successful sign-up, these attributes will be copied to the User object. From that point on they can be accessed as a direct attribute of the User object.

Setting unsafe metadata

Using the Backend API

curl.sh
curl -XPATCH -H 'Authorization: Bearer CLERK_SECRET_KEY' -H "Content-type: application/json" -d '{ "unsafe_metadata": { "birthday": "11-30-1969" } }' 'https://api.clerk.com/v1/users/{user_id}/metadata'
pages/api/private.ts
import { clerkClient } from "@clerk/nextjs"; import { NextApiRequest, NextApiResponse } from "next"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { stripeId, userId } = req.body; await clerkClient.users.updateUserMetadata(userId, { unsafeMetadata: { "birthday": "11-30-1969" } }) res.status(200).json({ success: true }); }
app/route/private.ts
import { NextResponse } from 'next/server'; import { clerkClient } from "@clerk/nextjs"; export async function POST() { const { stripeId, userId } = await body.json(); await clerkClient.users.updateUserMetadata(userId, { unsafeMetadata: { "birthday": "11-30-1969" } }); return NextResponse.json({ success: true }); }
private.ts
import { clerkClient } from "@clerk/clerk-sdk-node"; app.post('/updateStripe', (req, res) => { const { stripeId, userId } = await body.json(); await clerkClient.users.updateUserMetadata(userId, { unsafeMetadata: { "birthday": "11-30-1969" } }); res.status(200).json({ success: true }); });
private.go
var client clerk.Client func addStripeCustomerID(user *clerk.User, stripeCustomerID string) error { birthday := map[string]interface{}{ "birthday": "04-20-1969", } user, err := s.clerkClient.Users().UpdateMetadata(sess.UserID, &clerk.updateMetadataRequest{ UnsafeMetadata: birthday, }) if err != nil { panic(err) } }
private.rb
// ruby json example with a private metadata and stripe id require 'clerk' require 'json' unsafeMetadata = { "birthday": "04-20-1969" } clerk = Clerk::SDK.new(api_key: "your_clerk_secret_key") clerk.users.updateMetadata("user_xyz", unsafe_metadata: unsafeMetadata)

Using the Frontend SDKs

pages/unsafe.tsx
import {useUser} from "@clerk/nextjs"; import {useState} from "react"; export default function UnSafePage() { const { user } = useUser(); const [birthday, setBirthday] = "" return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
app/route/unsafe.tsx
"use client" import {useUser} from "@clerk/nextjs"; import {useState} from "react"; export default function UnSafePage() { const { user } = useUser(); const [birthday, setBirthday] = "" return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
unsafe.tsx
import {useUser} from "@clerk/clerk-react"; import {useState} from "react"; export default function UnSafePage() { const { user } = useUser(); const [birthday, setBirthday] = "" return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
unsafe.tsx
import {useUser} from "@clerk/remix"; import {useState} from "react"; export default function UnSafePage() { const { user } = useUser(); const [birthday, setBirthday] = "" return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
unsafe.tsx
// use Clerk React for gatsby import {useUser} from "@clerk/clerk-react"; import {useState} from "react"; export default function UnSafePage() { const { user } = useUser(); const [birthday, setBirthday] = "" return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
unsafe.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Clerk Unsafe Update</title> </head> <body> <h1>Clerk JavaScript Email + Password</h1> <div id="birthday-update"> <input placeholder="your birthday" id="birthday" type="string"></input> <button onclick="updateBirthday()">Update Birthday</button> </div> <script> const updateBirthday = async () => { const birthday = document.getElementById('birthday').value; const {user} = window.Clerk; user.update({ unsafeMetadata: { "birthday": birthday } }) }; </script> // Script to load Clerk up <script src="src/script.js" async crossorigin="anonymous"></script> </body> </html>

Retrieving unsafe metadata

There are multiple ways to retrieve unsafe metadata. It is available in the User object returned by the useUser hook, and it can also be attached to the session token to be retrieved with auth() or useAuth(). If you need to retrieve unsafe metadata frequently in the backend, the best option is to attach it to the session token and retrieve it from the session token.

To read about customization your session token and retrieving that data, check out our session token customization guide.

What did you think of this content?

Clerk © 2023