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.

JWT templates

Clerk offers the ability to generate JSON Web Tokens (JWTs). 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.

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": "",
  "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": "",
  "interests": ["hiking", "knitting"],
  "name": "John",
  "surname": null,
  "email": ""
  // 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.


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.


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.

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.has_verified_email || user.has_verified_phone}}",

  // 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. Navigate to the Clerk Dashboard.
  2. In the navigation sidebar, select JWT Templates.
  3. Select New template.
  4. You can either create a new template from scratch or use a provided template as a starting point.

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:
  • Clerk ID: user_abcdef123456789
  • Email address (verified):
  • Phone number: (not provided)
  • Public metadata: { "profile" : {"interests": ["reading", "climbing"] } }
  • Unsafe metadata: { "foo" : { "bar": 42 } }

And given the following JWT template:

    // static values
    "aud": "",
    "version": 1,
    "foo": { "bar": [1,2,3] },

    // dynamic values
    "user_id": "{{}}",
    "avatar": "{{user.image_url}}",
    "full_name": "{{user.last_name}} {{user.first_name}} ",
    "email": "{{user.primary_email_address}}",
    "phone": "{{user.primary_phone_address}}",
    "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": "",
    "version": 1,
    "foo": { "bar": [1,2,3] },
    "user_id": "user_abcdef123456789",
    "avatar": "",
    "full_name": "Doe Maria",
    "email": "",
    "phone": null,
    "registration_date": 1227618844,
    "likes_to_do": ["reading", "climbing"],
    "unsafe_meta": {
        "foo" : {
            "bar": 42
    "invalid_shortcode": null,

     // default claims, included automatically
    "azp": "http://localhost:3000",
    "exp": 1639398300,
    "iat": 1639398272,
    "iss": "",
    "nbf": 1639398220,
    "sid": "sess_2ehYpzsasKNOZrpqPZ9yDWhrYVe",
    "sub": "user_1deJLArSTiWiF1YdsEWysnhJLLY"


What did you think of this content?