Configuration
Most khotan configuration lives in environment variables and your khotan.ts factory registration file.
Today, khotan-data is opinionated toward Vercel-style Next.js deployments. That means the default durable workflow story assumes Vercel Workflow and the default scheduling story assumes a single dispatcher cron hitting your app over HTTP.
Environment variables
DATABASE_URL— used by Drizzle for migrations and runtime DB accessKHOTAN_SECRET— random 32+ byte secret used to encrypt stored varsKHOTAN_WEBHOOK_URL— public origin used when building default wire callback URLsKHOTAN_DEBUG— enables plug debugging routes and CLI access to themCRON_SECRET— optional bearer secret for protecting the built-in/api/khotan/crondispatcher route
# .env.local (dev)
DATABASE_URL=postgres://...
KHOTAN_SECRET=...
KHOTAN_WEBHOOK_URL=https://abc123.ngrok.app
KHOTAN_DEBUG=1
CRON_SECRET=...In development, KHOTAN_WEBHOOK_URL should usually point at a tunnel rather than localhost.
`khotan.config.ts`
This is the CLI config file. The most important value is outputDir, which tells khotan where to place generated runtime source files.
`khotan.ts`
This is the factory registration file. It wires khotan to your database and declares what should exist in the runtime.
import { khotan, drizzleAdapter } from "khotan-data/factory";
import { db } from "@/db";
import { pollinatePlug } from "./plugs/pollinate";
import { slackPlug } from "./plugs/slack";
import { pollinateProductsSnapshotCache } from "./caches/pollinate-products-snapshot";
import { pollinateProductsInflow } from "./flows/pollinate-products";
import { pollinateWire } from "./wires/pollinate-wire";
import { pollinateCatch } from "./webhooks/pollinate-catch";
import { pollinateToSlack } from "./webhooks/pollinate-to-slack";
const khotanData = khotan({
adapter: drizzleAdapter(db),
resources: [
{
name: "products",
mapping: {
connectField: "sku",
plugs: {
pollinate: { uniqueIdentifier: "id" },
},
},
description: "Shared product identity",
},
{
name: "customers",
mapping: {
connectField: ["tenantId", "email"],
plugs: {
pollinate: { uniqueIdentifier: "id" },
cin7: { uniqueIdentifier: "id" },
},
},
description: "Shared customer identity across systems",
},
],
caches: [
pollinateProductsSnapshotCache,
],
plugs: [
{
name: "pollinate",
plug: pollinatePlug,
flows: [pollinateProductsInflow],
wires: [pollinateWire],
catches: [pollinateCatch],
passes: [pollinateToSlack],
},
{
name: "slack",
plug: slackPlug,
},
],
});
export default khotanData;What each key does
adapter— connects khotan to your Drizzle databaseresources— shared logical entities for cross-system mappingcaches— durable named key/value stores for workflow stateplugs— external services your app talks toflows— durable data movement for one plugwires— webhook registration lifecycle for one plugcatches— verified inbound webhook processingpasses— verified inbound webhook forwarding to another plug
Resource mapping contracts
Resources define the shared identity contract for mappings through their mapping block.
mapping.connectFieldcan be a single field name such as"email"- or an ordered field list such as
["tenantId", "email"] mapping.plugsdeclares which plug names are allowed in mappingrefs- each declared plug provides exactly one
uniqueIdentifierdefinition
resources: [
{
name: "customers",
mapping: {
connectField: ["tenantId", "email"],
plugs: {
pollinate: { uniqueIdentifier: "id" },
cin7: { uniqueIdentifier: "id" },
},
},
},
]This matters because khotan stores one canonical connectValue per mapping row, keeps external per-plug IDs in refs, and reserves metadata for contextual non-identity fields.
Plug configuration
A plug usually lives in src/khotan/plugs/<name>.ts or khotan/plugs/<name>.ts.
baseUrl- auth strategy
- retry and timeout behavior
- typed endpoint metadata
- encrypted vars
- request hooks for token exchange or custom signing
Flow configuration
Flow builders like inflow, outflow, and relay create durable work definitions.
nameresourcescheduleworkflow
Flow, relay, catch, and pass workflows also receive khotanCache(ctx, "name"), which resolves a registered durable cache for snapshots, cursors, and dedupe markers.
Cache configuration
A cache usually declares name, scope, and ttl.
import { cache } from "./caches/cache";
export const pollinateProductsSnapshotCache = cache({
name: "pollinate-products-snapshot",
scope: {
plug: "pollinate",
resource: "products",
flow: "pollinate-products",
},
ttl: "6h",
});Registered caches are upserted into khotan_caches. Individual key/value rows live in khotan_cache_entries, where khotan stores the latest value per (cache, key) pair and respects optional TTL expiry.
Wire configuration
A wire usually declares events, onSubscribe, onUnsubscribe, and optional onVerify.
Catch and pass configuration
Catch and pass builders define durable handlers for verified inbound events:
catchEvent({ name, events, workflow })pass({ name, to, events, workflow })
Pass handlers receive destVars from the destination plug automatically.
Runtime tables
khotan_plugskhotan_resourceskhotan_flowskhotan_wireskhotan_webhook_handlerskhotan_webhook_eventskhotan_runskhotan_mappingskhotan_cacheskhotan_cache_entries
That schema is why the CLI, UI, and runtime all see the same system state.
Scheduling and run types
Flows can be listed and triggered through the flows CLI. Mappings can be browsed and mutated through the mappings CLI and browser UI.
fulldeltabackfillreconciledry-run
For scheduled production runs, prefer one dispatcher cron in vercel.json instead of one platform cron per flow:
{
"crons": [
{ "path": "/api/khotan/cron", "schedule": "* * * * *" }
]
}Then define schedules only on flows in khotan.ts:
{
name: "products-inflow",
type: "inflow",
schedule: "0 * * * *",
resource: "products",
}The dispatcher route checks which flows are due for the current minute and starts them through the normal run-tracking path. If CRON_SECRET is set, Vercel should call /api/khotan/cron with Authorization: Bearer <CRON_SECRET>.
Agent-facing configuration
npx khotan init --yes also installs built-in skills and writes AGENTS.md.
- how khotan is installed in the project
- which skills to read
- where generated source files live
- which commands to use for debugging and runtime operations