User metadata
Metadata allows for custom data to be saved on the User
object . There are three types of metadata: "unsafe", "public", and "private".
Warning
Metadata is limited to 8kb maximum.
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.
app /route /private.ts import { NextResponse } from 'next/server'
import { clerkClient } from '@clerk/nextjs/server'
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.ts import { clerkClient } from '@clerk/nextjs/server'
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.ts import { clerkClient } from '@clerk/clerk-sdk-node'
app .post ( '/updateStripe' , async (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)
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'
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.ts import { NextResponse } from 'next/server'
import { clerkClient } from '@clerk/nextjs/server'
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/server'
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' , async (req , res) => {
const { userId } = await req . body .json ()
const user = await clerkClient . users .getUser (userId)
res .status ( 200 ) .json ( user .privateMetadata)
})
curl.sh curl -XGET -H 'Authorization: CLERK_SECRET_KEY' -H "Content-type: application/json" 'https://api.clerk.com/v1/users/{user_id}'
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 is accessible by both the frontend and the backend, but can only be set on 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.
app /route /public.ts import { NextResponse } from 'next/server'
import { clerkClient } from '@clerk/nextjs/server'
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.ts import { clerkClient } from '@clerk/nextjs/server'
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.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)
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'
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 .
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.
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.
app /route /private.ts import { NextResponse } from 'next/server'
import { clerkClient } from '@clerk/nextjs/server'
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.ts import { clerkClient } from '@clerk/nextjs/server'
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.ts import { clerkClient } from '@clerk/clerk-sdk-node'
app .post ( '/updateStripe' , async (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)
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'
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.tsx 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 >
)
}
unsafe.tsx 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 >
)
}
unsafe.tsx import { 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 >
)
}
main.js import { Clerk } from '@clerk/clerk-js'
// Initialize Clerk with your Clerk publishable key
const clerk = new Clerk ( ' YOUR_PUBLISHABLE_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 = " YOUR_PUBLISHABLE_KEY "
src = "https:// YOUR_FRONTEND_API_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 >
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 .