Skip to main content

Warning

This guide is for creating custom JWT templates in order to generate JSON Web Tokens with Clerk. If you are looking for how to customize your Clerk-generated session token, refer to the Customize your session token guide.

Clerk offers the ability to generate JSON Web Tokens (). Each JWT, or token, represents a user that is currently signed in to your application.

You can control the claims that will go into these tokens by creating custom JWT templates that fit your needs. This enables you to integrate with any third-party services that support authentication with JWTs. An example use case is integrating with a third-party service that is able to consume JWTs, but requires them to be in a particular format.

Warning

When using custom JWTs, there may be increased latency in token generation due to the additional processing required to fetch and include the custom claim data.

What is a JWT template?

JWT templates are essentially JSON objects that specify claims to be included in the generated tokens, along with their respective values.

Claim values can be either static or dynamic.

Static values can be any of the regular JSON data types (strings, numbers, booleans, arrays, objects, null) and will be included as-is in the tokens.

Dynamic values, also called shortcodes, are special strings that will be substituted for their actual values when the tokens are generated. Read more in the Shortcodes section.

The following example shows a template that demonstrates both static values and shortcodes. In this example, the values of the aud and interests claims are static, and the values of the name and email claims are dynamic.

{
  "aud": "https://example.com",
  "interests": ["hiking", "knitting"],
  "name": "{{user.first_name}}",
  "surname": "{{user.last_name}}",
  "email": "{{user.primary_email_address}}"
}

A token generated using the template above would look something like this:

{
  "aud": "https://example.com",
  "interests": ["hiking", "knitting"],
  "name": "John",
  "surname": null,
  "email": "john@doe.org"
  // ...plus some automatically-included claims
  // See the following section section for more information
}

Default claims

In every generated token, there are certain claims that are automatically included and cannot be overridden by templates. Clerk calls these "default claims" and you can learn more about them in the Session tokens reference documentation.

For custom JWTs, Clerk automatically includes the following default claims:

{
  // default claims, included automatically
  "azp": "http://localhost:3000",
  "exp": 1639398300,
  "iat": 1639398272,
  "iss": "https://clean-mayfly-62.clerk.accounts.dev",
  "jti": "10db7f531a90cb2faea4",
  "nbf": 1639398220,
  "sub": "user_1deJLArSTiWiF1YdsEWysnhJLLY"
}

However, other default claims - such as sid (session ID) - are only included in session-bound tokens, not in custom JWTs. Custom JWTs are not inherently tied to a session. Instead, they're designed to be flexible tokens that include the default claims listed above, along with any additional claims you explicitly set in your JWT template. For this reason, session-tied claims like sid, v, pla, or fea cannot be included in custom JWTs.

If you need to generate a token that includes both session-bound data (like sid) and any additional claims, the recommended approach is to use a custom session token instead of a JWT template. See the guide on customizing your Clerk session token.

Shortcodes

To include dynamic values in your tokens, you can use shortcodes. Shortcodes are strings that Clerk will replace with the actual values of the corresponding user information when the token is generated.

Note

Even though shortcodes are string values, their type in the generated token depends on the original type of the information that's included. For example, {{user.public_metadata}} will be substituted for a JSON object, not a string.

Available shortcodes

You can use these shortcodes in JWT templates and in custom session claims. Timestamps are Unix timestamps in seconds.

User
ShortcodeTypeDescription
{{user.id}}stringThe user's ID.
{{user.external_id}}string | nullThe user's external ID.
{{user.first_name}}string | nullThe user's first name.
{{user.last_name}}string | nullThe user's last name.
{{user.full_name}}string | nullThe user's full name.
{{user.username}}string | nullThe user's username.
{{user.primary_email_address}}string | nullThe user's primary email address.
{{user.primary_phone_number}}string | nullThe user's primary phone number.
{{user.primary_web3_wallet}}string | nullThe user's primary Web3 wallet address.
{{user.email_verified}}booleanWhether the user has a verified email address.
{{user.phone_number_verified}}booleanWhether the user has a verified phone number.
{{user.two_factor_enabled}}booleanWhether the user has two-factor authentication enabled.
{{user.image_url}}stringThe user's image URL.
{{user.has_image}}booleanWhether the user has uploaded an image.
{{user.created_at}}numberWhen the user was created.
{{user.updated_at}}numberWhen the user was last updated.
{{user.last_sign_in_at}}numberWhen the user last signed in.
{{user.organizations}}objectThe user's Organizations, as a map of Organization ID to Role key. Can grow large enough to push a session token past browser cookie limits.
{{user.public_metadata}}objectThe user's public metadata. Supports dot notation.
{{user.unsafe_metadata}}objectThe user's unsafe metadata. Supports dot notation.
Session
ShortcodeTypeDescription
{{session.created_at}}numberWhen the session was created. Doesn't change for the life of the session. Use it to enforce your own maximum session age.
{{session.expire_at}}numberWhen the session reaches its maximum lifetime. If maximum lifetime is turned off, this is a date far in the future. A session can end sooner, such as after an inactivity timeout. Not the same as the token's exp.
{{session.reverification_id}}string | nullThe ID of the session's latest reverification. Its presence means one was started, not that it completed. See how to correlate a reverification with a specific action.
{{session.actor}}object | nullThe session's actor, present on impersonation and Agent Task sessions. Use dot notation to read a single field, such as {{session.actor.sub}} for the actor's ID.
{{source.platform}}stringThe platform the session was created on: web or native. Experimental. Clerk may change or remove it without notice.
Active Organization

When the session has no Active Organization, these resolve to null, except {{org.public_metadata}} and {{org_membership.public_metadata}}, which resolve to an empty object.

ShortcodeTypeDescription
{{org.id}}string | nullThe Organization's ID.
{{org.name}}string | nullThe Organization's name.
{{org.slug}}string | nullThe Organization's slug.
{{org.role}}string | nullThe user's Role key in the Organization, such as org:admin.
{{org.image_url}}string | nullThe Organization's image URL.
{{org.has_image}}boolean | nullWhether the Organization has an uploaded image.
{{org.public_metadata}}objectThe Organization's public metadata. Supports dot notation.
{{org_membership.permissions}}string[] | nullThe user's custom Permissions. System Permissions aren't included.
{{org_membership.public_metadata}}objectThe membership's public metadata. Supports dot notation.

Metadata in shortcodes

While you can use the {{user.public_metadata}} or {{user.unsafe_metadata}} shortcodes to include the complete metadata object in the final token, there might be cases where you only need a specific piece of information.

To keep your tokens lean, you can use the dot notation to access nested fields of the metadata object.

Let's assume the user's public metadata are the following:

{
  "interests": ["hiking", "knitting"],
  "addresses": {
    "Home": "2355 Pointe Lane, 56301 Minnesota",
    "Work": "3759 Newton Street, 33487 Florida"
  }
}

To access the interests array, you would use the shortcode {{user.public_metadata.interests}}. To access the Home address, you would use {{user.public_metadata.addresses.Home}}. See the following example:

// The template
{
  "likes_to_do": "{{user.public_metadata.interests}}",
  "shipping_address": "{{user.public_metadata.addresses.Home}}"
}

// The generated token
{
  "likes_to_do": ["hiking", "knitting"],
  "shipping_address": "2355 Pointe Lane, 56301 Minnesota"
}

Interpolation in shortcodes

Shortcodes can be interpolated inside strings.

For example, you could use interpolation to build the user's full name:

{
  "full_name": "{{user.last_name}} {{user.first_name}}"
}

Interpolated shortcodes will always result to string values. For example, if the user does not have a last name associated, the above full name value would be null John.

Conditional expressions in shortcodes

Conditional expressions use the || operator and can be used to substitute a default fallback value for shortcodes that would otherwise result in null or false values.

The format of a conditional expression is the following:

{
  "key": "{{ <operand_1> || <operand_2> || <operand_n> }}"
}

The result of a conditional expression is that of the first operand that does not evaluate to null or false (also known as "falsy"). If all operands of the expression are falsy, the last operand is returned no matter its value. Therefore, you should always place the default value as the last operand. See the following example:

{
  "has_verified_contact_info": "{{user.email_verified || user.phone_number_verified}}",

  // fallback to a string value
  "full_name": "{{user.full_name || 'Awesome User'}}",

  // fallback to a number value
  "age": "{{user.public_metadata.age || user.unsafe_metadata.age || 30 }}"
}

For this example, in the case that user:

  • has verified their phone number
  • has not verified their email
  • has not provided their first or last name
  • does not have any public or unsafe metadata assigned

Then, the output of the generated token would be:

{
  "has_verified_contact_info": true,
  "full_name": "Awesome User",
  "age": 30
}

The rules that govern conditional expressions are as follows:

  • The result of an expression is either the first operand that is not falsy, or the last operand (no matter its value).
  • String literals should use single quotes (').
  • Only strings, booleans and numbers are permitted as literal (i.e., non-shortcodes) operands.

Create a JWT template

A template consists of the following four properties:

  • Template name: a unique identifier for the template. When generating a token, you will have to specify the template to use, using this name. This is a required field.
  • Token lifetime: the time in seconds, after which tokens generated with this template will expire. This setting determines the value of the exp claim (i.e., exp=current_time+lifetime). Default is 60 seconds.
  • Token allowed clock skew: the time in seconds, provided as a leeway to account for clock skews between different servers. This setting determines the value of the nbf claim (i.e., nbf=current_time-allowed_clock_skew). Default is 5 seconds.
  • Claims: the actual template that's entered into the JSON editor (see screenshot below). A template is essentially a JSON object that describes what the final token claims will look like (shortcodes can be used here). This is a required field.

To create a JWT template:

  1. In the Clerk Dashboard, navigate to the JWT templates page.
  2. Select New template.
  3. You can either select a blank template or choose one of the provided templates.

Generate a JWT

To generate a token using a template, you can use the getToken() method. See the reference documentation for more information and example usage.

Complete example

The following example demonstrates the full capabilities of JWT templates, including static claim values, dynamic claim values via shortcodes, and Clerk's "default claims".

Given the following user:

  • First name: Maria
  • Last name: Doe
  • Profile picture URL: https://example.com/avatar.jpg
  • Clerk ID: user_abcdef123456789
  • Email address (verified): maria@example.com
  • Phone number: (not provided)
  • Public metadata: { "profile" : {"interests": ["reading", "climbing"] } }
  • Unsafe metadata: { "foo" : { "bar": 42 } }

And given the following JWT template:

{
  // static values
  "aud": "https://my-site.com",
  "version": 1,
  "foo": { "bar": [1, 2, 3] },

  // dynamic values
  "user_id": "{{user.id}}",
  "avatar": "{{user.image_url}}",
  "full_name": "{{user.last_name}} {{user.first_name}} ",
  "email": "{{user.primary_email_address}}",
  "phone": "{{user.primary_phone_number}}",
  "registration_date": "{{user.created_at}}",
  "likes_to_do": "{{user.public_metadata.profile.interests}}",
  "unsafe_meta": "{{user.unsafe_metadata}}",
  "invalid_shortcode": "{{user.i_dont_exist}}"
}

The generated token would look like this:

{
  "aud": "https://my-site.com",
  "version": 1,
  "foo": { "bar": [1, 2, 3] },
  "user_id": "user_abcdef123456789",
  "avatar": "https://example.com/avatar.jpg",
  "full_name": "Doe Maria",
  "email": "maria@example.com",
  "phone": null,
  "registration_date": 1227618844,
  "likes_to_do": ["reading", "climbing"],
  "unsafe_meta": {
    "foo": {
      "bar": 42
    }
  },
  "invalid_shortcode": "{{user.i_dont_exist}}",

  // default claims, included automatically
  "azp": "http://localhost:3000",
  "exp": 1639398300,
  "iat": 1639398272,
  "iss": "https://clean-mayfly-62.clerk.accounts.dev",
  "jti": "10db7f531a90cb2faea4",
  "nbf": 1639398220,
  "sub": "user_1deJLArSTiWiF1YdsEWysnhJLLY"
}

Feedback

What did you think of this content?

Last updated on