kavachOS

Additional fields

Extend the user and session schemas with typed custom fields, stored in existing metadata columns.

The additionalFields plugin lets you attach typed custom data to users and sessions without writing migrations. Fields are stored in the metadata JSON column of kavach_users and kavach_sessions.

Install

Ships inside kavachos/auth — no extra packages needed.

Setup

Define a schema once when creating your KavachOS instance:

import { createKavach } from 'kavachos';
import { additionalFields } from 'kavachos/auth';

const kavach = await createKavach({
  database: { provider: 'sqlite', url: 'kavach.db' },
  plugins: [
    additionalFields({
      user: {
        plan:     { type: 'string',  required: false, defaultValue: 'free' },
        credits:  { type: 'number',  required: false, defaultValue: 0 },
        verified: { type: 'boolean', required: false },
        settings: { type: 'json',    required: false },
      },
      session: {
        ipCountry:  { type: 'string', required: false, defaultValue: 'unknown' },
        deviceType: { type: 'string', required: false },
      },
    }),
  ],
});

Reading and writing user fields

import type { AdditionalFieldsModule } from 'kavachos/auth';

const mod = kavach.plugins.getContext().additionalFields as AdditionalFieldsModule;

// Write
await mod.setUserFields(userId, { plan: 'pro', credits: 100 });

// Read (missing fields get their defaultValue)
const fields = await mod.getUserFields(userId);
// => { plan: 'pro', credits: 100, verified: undefined, settings: undefined }

setUserFields merges with existing fields. Previously stored keys not present in the update are preserved.

Reading and writing session fields

await mod.setSessionFields(session.id, { ipCountry: 'DE', deviceType: 'mobile' });

const sessionFields = await mod.getSessionFields(session.id);
// => { ipCountry: 'DE', deviceType: 'mobile' }

Validation

Validate a field map before writing, or in a request handler:

const result = mod.validate({ plan: 42 }, 'user');
// => { valid: false, errors: ['Field "plan" must be of type string'] }

const ok = mod.validate({ plan: 'pro', credits: 10 }, 'user');
// => { valid: true }

Rules enforced during validate() (and automatically on every setUserFields / setSessionFields call):

  • Required fields must be present.
  • Field values must match the declared type.
  • Fields not in the schema are rejected.

Field types

TypeAccepted values
stringAny typeof v === 'string'
numberAny typeof v === 'number'
booleantrue or false
jsonAny non-null value

REST endpoints

MethodPathDescription
GET/auth/users/fields?userId=<id>Read additional fields for a user
PUT/auth/users/fieldsSet additional fields on a user
POST/auth/fields/validateValidate fields against the schema

GET example

GET /auth/users/fields?userId=usr_abc
{ "fields": { "plan": "pro", "credits": 100 } }

PUT example

PUT /auth/users/fields
Content-Type: application/json

{
  "userId": "usr_abc",
  "fields": { "plan": "enterprise" }
}
{ "updated": true }

Returns 422 when validation fails. Returns 404 when the user does not exist.

Validate example

POST /auth/fields/validate
Content-Type: application/json

{
  "schema": "user",
  "fields": { "plan": 42 }
}
{
  "valid": false,
  "errors": ["Field \"plan\" must be of type string"]
}

Schema reference

interface FieldDefinition {
  type: 'string' | 'number' | 'boolean' | 'json';
  required?: boolean;
  defaultValue?: unknown;
}

interface AdditionalFieldsConfig {
  user?:    Record<string, FieldDefinition>;
  session?: Record<string, FieldDefinition>;
}

Storage

No database migrations are needed. User fields are stored under user.metadata.additionalFields and session fields under session.metadata.additionalFields. Other metadata keys written by the core system or other plugins are not modified.

On this page