User metadata
Metadata allows for custom data to be saved on the User
object. There are three types of metadata: "unsafe", "public", and "private".
Metadata | Frontend API | Backend API |
---|---|---|
Private | No read or write access | Read & write access |
Public | Read access | Read & write access |
Unsafe | Read & write access | Read & write access |
Metadata is limited to 8kb maximum.
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
app/route/private.tsimport { 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 }); }
pages/api/private.tsimport { 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 }); }
private.tsimport { 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.govar 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)
curl.shcurl -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'
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.
app/private/route.tsimport { 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.tsimport { 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.tsimport { 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); });
curl.shcurl -XGET -H 'Authorization: CLERK_SECRET_KEY' -H "Content-type: application/json" 'https://api.clerk.com/v1/users/{user_id}'
private.govar 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
app/route/public.tsimport { 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 }); }
pages/api/public.tsimport { 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 }); }
public.tsimport { 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.govar 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)
curl.shcurl -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'
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(opens in a new tab)), 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
Updating this value overrides the previous value; it does not merge. To perform a merge, you can pass something like { …user.unsafeMetadata, …newData }
to this parameter.
Using the Backend API
app/route/private.tsimport { 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 }); }
pages/api/private.tsimport { 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 }); }
private.tsimport { 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.govar 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)
curl.shcurl -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'
Using the Frontend SDKs
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] = useState(""); return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
pages/unsafe.tsximport {useUser} from "@clerk/nextjs"; import {useState} from "react"; export default function UnSafePage() { const { user } = useUser(); const [birthday, setBirthday] = useState("") return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
unsafe.tsximport {useUser} from "@clerk/clerk-react"; import {useState} from "react"; export default function UnSafePage() { const { user } = useUser(); const [birthday, setBirthday] = useState("") return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
unsafe.tsximport {useUser} from "@clerk/remix"; import {useState} from "react"; export default function UnSafePage() { const { user } = useUser(); const [birthday, setBirthday] = useState("") 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] = useState("") return( <div> <input type="text" value={birthday} onChange={(e) => setBirthday(e.target.value)} /> <button onClick={() => { user.update({ unsafeMetadata: { birthday } }) }}>Update Birthday</button> </div> ) }
main.jsimport Clerk from '@clerk/clerk-js'; // Initialize Clerk with your Clerk publishable key const clerk = new Clerk('{{pub_key}}'); await clerk.load(); if (clerk.user) { await clerk.user.update({ unsafeMetadata: { "birthday": "01-01-2000" } }) .then((res) => console.log(res)) .catch((error) => console.log("An error occurred:", error.errors)); } else { document.getElementById("app").innerHTML = ` <div id="sign-in"></div> `; const signInDiv = document.getElementById("sign-in"); clerk.mountSignIn(signInDiv); }
index.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> <div id="app"></div> <!-- Initialize Clerk with your Clerk Publishable key and Frontend API URL --> <script async crossorigin="anonymous" data-clerk-publishable-key="{{pub_key}}" src="https://{{fapi_url}}/npm/@clerk/clerk-js@latest/dist/clerk.browser.js" type="text/javascript" ></script> <script> window.addEventListener("load", async function () { await Clerk.load(); if (Clerk.user) { await Clerk.user.update({ unsafeMetadata: { "birthday": "01-01-2000" } }) .then((res) => console.log(res)) .catch((error) => console.log("An error occurred:", error.errors)); } else { document.getElementById("app").innerHTML = ` <div id="sign-in"></div> `; const signInDiv = document.getElementById("sign-in"); Clerk.mountSignIn(signInDiv); } }); </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. It can also be attached to a session token, which can be retrieved with the auth()
helper in Next.js applications, or with the useAuth()
hook. 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.
Last updated on March 26, 2024