External Identifiers
This guide explains how to configure and use the external_id field in Ory Kratos to support external primary identifiers such as
customer_id, employee_id, or similar. This is especially useful for migrations from systems where you need to preserve
identifiers or support user-defined primary identifiers.
The external_id must be unique across all identities. If you attempt to import multiple identities with the same external_id,
the operation will fail with a 409 Conflict.
Overview
Traditionally, Ory Kratos identifies users using an internal identity.id UUID. With the external_id feature, you can:
- Assign a unique, domain-specific identifier to each identity.
- Query and manage identities using external_id.
- Use external_idas thesub(subject) claim in JWTs.
- Preserve identity semantics across systems during migration.
This helps simplify migrations, reduce mapping layers, and align Kratos with your existing infrastructure.
Configuration
Use external_id via API, not schema
The external_id is not part of the identity JSON Schema. Instead, it is a dedicated top-level attribute in API requests that
create or update identities.
Do not add external_id to your identity schema definition. It is handled separately by Ory Kratos internally.
Use external_id in JWT sub claim
Set the subject_source to external_id in the tokenization config:
session:
  whoami:
    tokenizer:
      templates:
      jwt_template_1:
        jwks_url: base64://... # A JSON Web Key Set (required)
        claims_mapper_url: base64://... # A JsonNet template for modifying the claims
        ttl: 1m # 1 minute (defaults to 10 minutes)
        subject_source: external_id
      another_jwt_template:
        jwks_url: base64://... # A JSON Web Key Set
This will populate the sub claim in JWTs with the value of external_id.
If external_id is not set for a user when subject_source is external_id, tokenization will fail.
API usage
Create an identity with external_id
POST /admin/identities
Content-Type: application/json
{
  "schema_id": "default",
  "traits": {
    "email": "user@example.com"
  },
  "external_id": "customer-12345"
}
Get identity by external_id
GET /admin/identities/by/external/customer-12345
Optional query parameter
- include_credential=password,oidc,...— Include specific credentials in the response.
Example:
GET /admin/identities/by/external/customer-12345?include_credential=password
Response:
{
  "id": "uuid-abc123",
  "external_id": "customer-12345",
  "traits": {
    "email": "user@example.com"
  },
  "credentials": {
    "password": { ... }
  }
}
Error responses:
- 404– Identity not found.
- 409– Duplicate- external_idon creation.
- 400– Invalid request structure.
There are no other APIs that support external_id, for the APIs that require a Kratos identity_id you need to use the
Get identity by external_id API above and use the identity id from there.
JWT configuration
Jsonnet example
When tokenizing sessions, external_id is available in the session context:
local claims = std.extVar('claims');
local session = std.extVar('session');
{
  claims: {
    iss: claims.iss + "/additional-component",
    schema_id: session.identity.schema_id,
    external_id: session.identity.external_id,
    session: session,
  }
}
Token behavior with external_id
If subject_source is set to external_id in the tokenizer template, the JWT's sub claim becomes:
{
  "sub": "customer-12345"
}
If external_id is missing, tokenization will fail.
Migration guide
To migrate from an existing system, you can bulk import identities into Kratos and set their external_id using the
Identity Import API.
Use PATCH /admin/identities
Basic import example
[
  {
    "schema_id": "default",
    "external_id": "customer-001",
    "traits": {
      "email": "alice@example.com"
    }
  }
]
Pre-hashed password example
[
  {
    "schema_id": "default",
    "external_id": "customer-002",
    "traits": {
      "email": "bob@example.com"
    },
    "credentials": {
      "password": {
        "config": {
          "hashed_password": "$2b$12$abc123..." // bcrypt hash
        }
      }
    }
  }
]
Migration tips
- Pre-hash passwords to avoid timeouts
- Validate external_iduniqueness before import
- Import in smaller batches (≤ 200 identities if using plaintext passwords)
Troubleshooting
| Error / Code | Context | Description | 
|---|---|---|
| 409 Conflict | Migration (batch import) | One or more identities have a duplicate external_id. Ensure all values are unique. | 
| 400 Bad Request | Migration (batch import) | The request payload is invalid or improperly formatted. Check JSON structure and required fields. | 
| 504 Gateway Timeout | Migration (batch import) | The batch is too large or includes plaintext passwords. Reduce batch size or pre-hash passwords. | 
| 500 Internal Server Error | Session token generation | If subject_source = external_idis configured, the session will not tokenize unless the identity has anexternal_idset. | 
For advanced examples, see: