Managing User Profiles with Ory Kratos Identity Schemas
Learn how to use Ory Kratos Identity Schemas to create flexible user profiles without constant database migrations, using JSON Schema for validation.
02 Mar 2026, 14:55 UTC

The Rigid User Table Problem
Most authentication systems force you into a predefined user model. When you need to add a "company_id," a "preferred_timezone," or a "marketing_consent" flag, you typically have two choices: migrate your core database schema or create a secondary "user_profiles" table that requires a join on every single request. This creates a bottleneck where the identity provider dictates the data structure of your application.
Ory Kratos (v1.x) solves this by treating the user identity as a flexible JSON object governed by a JSON Schema. Instead of hard-coded columns, Kratos uses an Identity Schema to validate traits, allowing you to evolve your user profile without running database migrations on the identity provider itself.
How Identity Schemas Work
In Kratos, an Identity is the account, while a Session is the active login state. The Identity contains a traits object. This object is where your custom user data lives. By defining a JSON Schema, you tell Kratos exactly what fields are required, what types they are, and how they should be validated before they are persisted.
Because Kratos is headless, it does not provide the registration form UI. It provides the validation logic and the state machine. Your application UI collects the data, sends it to the Kratos Public API, and Kratos rejects the request if the data does not match your schema.
Implementing a Custom User Profile
To implement a custom profile, you must define an identity.schema.json file. This file is loaded by Kratos and used to validate all registration and profile update requests.
Example Configuration
Below is a schema that requires a standard email but adds a custom username and a membership_level dropdown.
{
"$id": "https://schemas.ory.sh/kratos/identity/1",
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"title": "E-Mail",
"ory.sh/kratos": { "privileged": true }
},
"username": {
"type": "string",
"title": "Username",
"minLength": 3
},
"membership_level": {
"type": "string",
"title": "Membership Level",
"enum": ["free", "pro", "enterprise"]
}
},
"required": ["email", "username"]
}
Applying the Schema
Mount the file into your Kratos container and reference it in your kratos.yaml configuration:
selfservice:
flows:
registration:
ui_url: http://your-app.com/registration
identity:
default_schema_id: default
schemas:
- id: default
url: file:///etc/config/kratos/identity.schema.json
Verification and Diagnostics
Ory Kratos registration is a stateful process. You cannot send a single POST request to create a user; you must first initialize a flow to receive a flow_id, then submit the data to that specific flow.
To verify your schema is enforcing constraints (like minLength: 3 for usernames), run these commands from a terminal with access to the Kratos Public API (typically port 4433). Use a placeholder <flow_id> for the ID returned by the first call.
# 1. Initialize the registration flow to get a flow_id
curl -X GET http://kratos-public-api:4433/self-service/registration/browser
# 2. Attempt to submit data that violates the schema (username too short)
# Replace <flow_id> with the ID from the previous step
curl -X POST http://kratos-public-api:4433/self-service/registration?flow=<flow_id> \
-H "Content-Type: application/json" \
-d '{"traits": {"email": "[contact removed]", "username": "ab"}}'
Expected Result: The API should return a 400 Bad Request. The response body will contain a nodes array in the JSON, specifically identifying that the username field failed validation. If the request returns a 200 OK or a redirect, the schema is not being applied correctly.
The Trade-off: Application-Side Mapping
While flexible schemas remove database migration pain, they introduce a mapping challenge. Kratos identifies users by a UUID (e.g., id: 123e4567-e89b...). Your internal application database likely has its own user_id or domain-specific primary key.
You must maintain a mapping table in your own database that links the Kratos Identity ID to your internal domain entities. When a user logs in, your app must extract the Kratos identity ID from the session and look up the corresponding internal record to fetch application-specific data (like order history or API keys) that does not belong in the identity schema.
Closing Action
When starting a project with Ory Kratos, define your identity.schema.json early. Start with the bare minimum required fields and use the enum and format properties to enforce data integrity at the edge, preventing malformed user data from ever entering your system.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.