Ory Kratos provides powerful tools for importing identities from external systems and exporting identity data. This is useful for migrations, backups, and integrating with external user management systems.
Importing identities
You can import identities using the Admin API or CLI with support for credentials, traits, and metadata.
Import via CLI
The CLI supports importing single identities or batches from JSON files:
Single identity from file
Multiple identities
From stdin
kratos import identities identity.json
Import via Admin API
For programmatic imports, use the Admin API:
Single identity import
Use the POST /admin/identities endpoint: curl -X POST "https://kratos-admin/admin/identities" \
-H "Content-Type: application/json" \
-d @identity.json
Batch import
Use the PATCH /admin/identities endpoint for bulk operations: curl -X PATCH "https://kratos-admin/admin/identities" \
-H "Content-Type: application/json" \
-d '{
"identities": [
{"create": {...}},
{"create": {...}},
{"create": {...}}
]
}'
Basic identity
{
"schema_id" : "default" ,
"traits" : {
"email" : "[email protected] " ,
"name" : {
"first" : "John" ,
"last" : "Doe"
}
},
"state" : "active"
}
Identity with credentials
identity-with-password.json
{
"schema_id" : "default" ,
"traits" : {
"email" : "[email protected] "
},
"credentials" : {
"password" : {
"config" : {
"hashed_password" : "$2a$10$XQ7..."
}
}
},
"verifiable_addresses" : [
{
"value" : "[email protected] " ,
"verified" : true ,
"via" : "email" ,
"status" : "completed"
}
]
}
Identity with OIDC credentials
{
"schema_id" : "default" ,
"traits" : {
"email" : "[email protected] "
},
"credentials" : {
"oidc" : {
"config" : {
"providers" : [
{
"subject" : "google-user-id-12345" ,
"provider" : "google"
}
]
}
}
}
}
identity-with-metadata.json
{
"schema_id" : "default" ,
"traits" : {
"email" : "[email protected] "
},
"metadata_public" : {
"theme" : "dark" ,
"language" : "en"
},
"metadata_admin" : {
"internal_id" : "legacy-12345" ,
"migrated_from" : "old-system" ,
"migration_date" : "2024-01-15"
},
"external_id" : "external-system-id-12345"
}
Batch import
{
"identities" : [
{
"patch_id" : "00000000-0000-0000-0000-000000000001" ,
"create" : {
"schema_id" : "default" ,
"traits" : {
"email" : "[email protected] "
}
}
},
{
"patch_id" : "00000000-0000-0000-0000-000000000002" ,
"create" : {
"schema_id" : "default" ,
"traits" : {
"email" : "[email protected] "
}
}
}
]
}
{
"identities" : [
{
"action" : "create" ,
"identity" : "9f425a8d-7efc-4768-8f23-7647a74fdf13" ,
"patch_id" : "00000000-0000-0000-0000-000000000001"
},
{
"action" : "error" ,
"patch_id" : "00000000-0000-0000-0000-000000000002" ,
"error" : {
"code" : 409 ,
"status" : "Conflict" ,
"reason" : "identity with this email already exists"
}
}
]
}
Batch limits
Import limits:
Up to 1,000 identities per request with hashed passwords
Up to 200 identities per request with plaintext passwords
Plaintext passwords must be hashed during import, which is CPU-intensive. Use pre-hashed passwords for large imports.
Password import
Kratos accepts passwords in PHC format :
Plaintext password import
{
"credentials" : {
"password" : {
"config" : {
"password" : "user-password-123"
}
}
}
}
Plaintext passwords are hashed using bcrypt during import. For performance, pre-hash passwords before importing large batches.
Password migration hook
For just-in-time password migration:
{
"credentials" : {
"password" : {
"config" : {
"use_password_migration_hook" : true
}
}
}
}
This triggers a webhook during the user’s first login to retrieve and migrate their password from the legacy system.
Verifiable and recovery addresses
Import verified addresses
Addresses must match fields in the identity schema. If a trait changes during an update, addresses not matching traits will be removed.
Exporting identities
Export via Admin API
List all identities
Export with credentials
Filter by organization
curl "https://kratos-admin/admin/identities?page_size=100"
[
{
"id" : "9f425a8d-7efc-4768-8f23-7647a74fdf13" ,
"schema_id" : "default" ,
"schema_url" : "https://example.com/schemas/default" ,
"state" : "active" ,
"state_changed_at" : "2024-01-15T09:30:00Z" ,
"traits" : {
"email" : "[email protected] " ,
"name" : {
"first" : "John" ,
"last" : "Doe"
}
},
"verifiable_addresses" : [
{
"id" : "..." ,
"value" : "[email protected] " ,
"verified" : true ,
"via" : "email" ,
"status" : "completed" ,
"created_at" : "2024-01-15T09:30:00Z"
}
],
"recovery_addresses" : [ ... ],
"metadata_public" : { ... },
"created_at" : "2024-01-15T09:30:00Z" ,
"updated_at" : "2024-01-15T09:30:00Z"
}
]
Use keyset pagination for large exports:
# First page
curl "https://kratos-admin/admin/identities?page_size=250"
# Next page (use page_token from Link header)
curl "https://kratos-admin/admin/identities?page_size=250&page_token=..."
Import validation
The import process performs validation:
Schema validation
Identity traits are validated against the specified schema.
Credential validation
Password hashes must be in valid PHC format. OIDC providers must have subject and provider fields.
Identifier uniqueness
Credential identifiers must be unique across all identities.
Address consistency
Verifiable and recovery addresses must match trait fields defined in the schema.
Handling import errors
When batch importing, check the response for errors:
type BatchIdentityPatchResponse struct {
Action BatchPatchAction `json:"action"`
IdentityID * uuid . UUID `json:"identity,omitempty"`
PatchID * uuid . UUID `json:"patch_id,omitempty"`
Error * herodot . DefaultError `json:"error,omitempty"`
}
Best practices
Import considerations:
Pre-hash passwords for batches larger than 200 identities
Use patch_id to correlate responses with requests
Import verifiable addresses as verified: true to skip verification flows
Set external_id to link identities to your legacy system
Use metadata_admin for internal tracking data
Validate import files before batch operations
Import in batches of 200-1000 identities
Use pre-hashed passwords to avoid CPU bottlenecks
Parallelize imports across multiple requests
Monitor database performance during large imports
Use transactions where possible
Example: Complete migration
#!/bin/bash
# Export from old system and import to Kratos
# 1. Export from legacy system
legacy-export-tool --output users.json
# 2. Transform to Kratos format
jq '[.[] | {
create: {
schema_id: "default",
traits: {email: .email, name: .full_name},
credentials: {
password: {config: {hashed_password: .password_hash}}
},
verifiable_addresses: [{
value: .email,
verified: .email_verified,
via: "email",
status: "completed"
}],
external_id: .id,
metadata_admin: {legacy_id: .id, migrated_at: now}
}
}] | {identities: .}' users.json > kratos-import.json
# 3. Import to Kratos in batches
split -l 500 kratos-import.json batch-
for batch in batch-* ; do
curl -X PATCH "https://kratos-admin/admin/identities" \
-H "Content-Type: application/json" \
-d @" $batch "
done