Docs

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

Warning

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.

Set private metadata

app/private/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { clerkClient } from '@clerk/nextjs/server'

export async function POST(request: NextRequest) {
  const { stripeId, userId } = await request.json()

  const client = await clerkClient()

  await client.users.updateUserMetadata(userId, {
    privateMetadata: {
      stripeId: stripeId,
    },
  })

  return NextResponse.json({ success: true })
}

Caution

On January 8, 2025, the Node SDK will no longer be available. Upgrade to the Express SDK.

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'

Retrieve private metadata

You can retrieve the private metadata for a user by using the JavaScript Backend SDK's getUser() method. This method will return the User object which contains the private metadata.

app/private/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { clerkClient } from '@clerk/nextjs/server'

export async function GET(request: NextRequest) {
  const { userId } = await request.json()

  const client = await clerkClient()

  const user = await client.users.getUser(userId)

  return NextResponse.json(user.privateMetadata)
}

Caution

On January 8, 2025, the Node SDK will no longer be available. Upgrade to the Express SDK.

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

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.

Set public metadata

app/public/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { clerkClient } from '@clerk/nextjs/server'

export async function POST(request: NextRequest) {
  const { stripeId, userId } = await request.json()

  const client = await clerkClient()

  await client.users.updateUserMetadata(userId, {
    publicMetadata: {
      stripeId: stripeId,
    },
  })

  return NextResponse.json({ success: true })
}

Caution

On January 8, 2025, the Node SDK will no longer be available. Upgrade to the Express SDK.

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'

Retrieve public metadata

There are multiple ways to retrieve public metadata.

On the frontend, it's available on the User object which can be accessed using the useUser() hook.

On the backend, it's available on the Backend User object which can be accessed using the JavaScript Backend SDK's getUser() method. It can also be attached to a session token, and the sessionClaims of the session token can be retrieved on the Auth object. 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. See the guide on customizing your session token.

Unsafe metadata

Unsafe metadata can be both read and set from the frontend and the backend. It's called "unsafe" metadata because it can be modified directly from the frontend, which means malicious users could potentially tamper with these values.

Unsafe metadata is the only metadata property that can be set during sign-up, so a common use case is to use it in custom onboarding flows. Custom data collected during the onboarding (sign-up) flow can be stored in the SignUp object. After a successful sign-up, SignUp.unsafeMetadata is copied to the User object as User.unsafeMetadata. From that point on, the unsafe metadata is accessible as a direct attribute of the User object.

Set unsafe metadata

The following examples demonstrate how to update unsafe metadata for an existing user. Updating unsafeMetadata replaces the previous value; it doesn't perform a merge. To merge data, you can pass a combined object such as { …user.unsafeMetadata, …newData } to the unsafeMetadata parameter.

The following examples demonstrate how to update unsafeMetadata using the Backend API or the Frontend SDKs.

Using the Backend API

app/unsafe/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { clerkClient } from '@clerk/nextjs/server'

export async function POST(request: NextRequest) {
  const { userId } = await request.json()

  const client = await clerkClient()

  await client.users.updateUserMetadata(userId, {
    unsafeMetadata: {
      birthday: '11-30-1969',
    },
  })

  return NextResponse.json({ success: true })
}

Caution

On January 8, 2025, the Node SDK will no longer be available. Upgrade to the Express SDK.

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
require 'clerk'
require 'json'

unsafeMetadata = {
  "birthday": "04-20-1969"
}

clerk = Clerk::SDK.new(api_key: "your_clerk_secret_key")
clerk.users.updateMetadata("user_123", 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/unsafe/route.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>
  )
}
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 pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

const clerk = new Clerk(pubKey)
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)
}

Retrieve unsafe metadata

There are multiple ways to retrieve unsafe metadata.

On the frontend, it is available on the User object, which you can access by using the useUser() hook.

On the backend, it's available on the Backend User object which can be accessed using the JavaScript Backend SDK's getUser() method. It can also be attached to a session token, and the sessionClaims of the session token can be retrieved on the Auth object. 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. See the guide on customizing your session token.

Feedback

What did you think of this content?

Last updated on