Skip to main content
The repost schema commands define your outbound webhook events in a Prisma-derived schema language, version them like database migrations, and generate a type-safe TypeScript SDK and Standard Webhooks-compatible JSON Schema. If you know the Prisma schema and its initmigrate devgenerate flow, you already know this.

How it works

repost schema is a thin front end. Each subcommand delegates to a bundled schema engine (repost-schema): your arguments pass through verbatim, and the engine’s output and exit code are preserved.

Nothing extra to install

Release builds of repost embed the engine for your platform and extract it on first use. Installing the CLI is all you need.

Version-pinned

The CLI runs only the engine build it ships with. A mismatched engine is refused rather than run.

Local and CI-friendly

Every command runs locally against files in your repo. validate and fmt --check return a non-zero exit code for CI gates.
These are human-facing commands. Unlike the agentic commands, they do not emit the JSON envelope, and the --json and --output flags do not apply — arguments are forwarded to the engine as-is.

Scaffold a workspace

repost schema init creates a repost/ directory with a working starter schema, a .env with the two placeholders you’ll fill (REPOST_SEND_API_KEY="" for the generated client’s sends, REPOST_TOKEN="" for deploys), and a .gitignore entry for .env. For TypeScript the generated client lives in node_modules, which any Node project already ignores.
Run bare on a terminal, init asks which SDK language the generator should emit (the list comes from repost schema languages) and where generated code should go; with any argument, or outside a TTY, it runs headless. Languages other than TypeScript have no default output — pass --output (their generated code is committed to your repo, not gitignored). It refuses to run if a repost/ directory already exists, then prints the language’s next steps: edit repost/schema.repost, install the runtime, run repost schema migrate dev --name init, and import the client.
Pick one layout. Your schema is either a single repost/schema.repost or a repost/schema/ directory of .repost files — never both. If you outgrow one file, move all of them into repost/schema/; leaving extra .repost files next to schema.repost, or keeping both layouts, is a hard error naming the offending files (rather than silently ignoring them and writing a removal migration for the events they held).

Validate and format

Check that your schema parses and satisfies every rule without writing any output. The exit code reflects the diagnostics, so this fits directly into CI.
Format .repost files in place. Formatting is idempotent; pass --check to fail instead of writing when a file is not already formatted.

Generate the client

repost schema generate validates the schema, then emits a complete, precompiled client package — package.json, CommonJS and ESM builds, one typed index.d.ts, the Svix-compatible schemas.json, and a copy of your schema. It does not write a migration.
With no output in your generator block (the default), the package is written to node_modules/.repost/client, and the installed @repost/client package re-exports it — exactly how Prisma’s @prisma/client works:
Everything is typed from your schema: model interfaces, enums (value object + type), and the webhooks.<catalog>.<member>() method tree.

Run your first send

Three practicalities a fresh project hits:
  • An API key. Create an Environment in the dashboard — it generates the environment’s publish API key and shows it once. Paste it into the .env that init scaffolded (REPOST_SEND_API_KEY="rp_..."). Nothing loads .env automatically — pass --env-file to Node (20.6+) or use your framework’s loader. createRepostClient() reads REPOST_SEND_API_KEY at send time (an explicit apiKey option overrides it).
  • Running a TypeScript file. Plain node index.ts does not work on current Node. Use npx tsx index.ts, or Node’s built-in stripping (node --experimental-strip-types index.ts, Node 22.6+), or write the entry as plain .mjs.
  • Module type. Give your project a "type": "module" in package.json (or use .mts/.mjs extensions) so Node doesn’t warn about, and reparse, every typeless file.
Sends go to Repost’s publish API and are delivered to your customers’ subscribed endpoints. To test sends without network calls, pass the exported stubTransport to createRepostClient({ transport }) — it validates, serializes, and returns a typed result locally.

Custom output for monorepos

Set output to write the identical package to a path instead (resolved relative to the schema directory). Import it by path or re-export it from a workspace package; @repost/client then serves only as the runtime dependency.
Custom output refuses to overwrite a directory it does not recognize as a previously generated client, so a mistyped path cannot destroy your code.

Other SDK languages

The same schema generates typed senders for Go, Python, Java, and Kotlin — set the generator’s language (Ctrl+Space in your editor completes the supported values). Unlike TypeScript, these languages have no node_modules to hide output in: output is required, and the generated package is committed to your repo like any other codegen (protoc/sqlc convention).
Install the language’s runtime once — go get github.com/repost-sh/repost-go or pip install repost-client — then repost schema generate and send:
Auth and configuration behave identically in every language: REPOST_SEND_API_KEY read at send time, REPOST_API_URL to override the API, a no-network stub transport for tests, and the same retry/idempotency semantics — the runtimes share one wire format, pinned by a cross-language conformance suite. A generated client and its runtime library verify a descriptor-format handshake at construction, so a version mismatch fails immediately with the fix (regenerate, or upgrade the runtime) instead of corrupting sends.

Java and Kotlin

The JVM languages generate the same way but need two extra generator settings, because a JVM client is more than a source tree: it also emits a discovery resource. Set packageName (the Java/Kotlin package), clientName (the generated client class), and both a source root (output) and a resource root (resourceOutput):
Repost owns both roots: it writes the generated .java/.kt files under output and one client.json registry per client under resourceOutput, and replaces both together in one transaction. Point them at build-output directories — they are regenerated, not committed. Most JVM projects don’t call the CLI directly. A Maven or Gradle plugin runs the same engine as part of the build, and both offer a check goal that regenerates into an isolated directory and fails on drift without mutating your tree:
The Java and Kotlin guides cover the plugin, BOM, sends, framework injection, and support baseline end to end. One schema can carry multiple generators — a multi-service monorepo generates every service’s client in one repost schema generate run, including the same language twice with different outputs:
Working examples live in the repo under examples/go, examples/python, examples/java, and examples/kotlin.
Upgrading from the loose-TS output? Earlier builds emitted models.ts/webhooks.ts/client.ts source files. Delete those generated directories once — generate refuses to overwrite them because it cannot prove they are generated — and update imports to @repost/client (default) or your output path.

Generated client and your package manager

The default output lives in node_modules, which your package manager owns. Three things to know:
  • Regenerate after install. A fresh clone or a CI job that restores node_modules from cache does not have your generated client. Run repost schema generate in your build (or a postinstall script) — the same practice Prisma projects use. Until the first generate, importing @repost/client throws @repost/client did not initialize yet. Please run "repost schema generate".
  • The repost CLI is a prerequisite. Generation is done by the repost CLI (the engine ships inside it), so machines that build your app need it installed.
  • No node_modules at all? Yarn PnP and similar layouts have nowhere for the default output to go — set output and import by path instead.
@repost/client is not on npm yet (publishing is pending license confirmation). Until it is, install it locally with pnpm pack and an install from the tarball — see the runtime package’s README.md (sdk/typescript) for the full local-development steps. Once published, it becomes a plain npm install @repost/client.

Version changes with migrations

repost schema migrate dev diffs the current output against your last migration. When something changed, it writes a new repost/migrations/<timestamp>_<name>/ snapshot — the directory is prefixed with a UTC YYYYMMDDHHMMSS timestamp — bumps the version of each affected event, and regenerates. When nothing changed, it is a no-op.
--name is optional. When you omit it and a migration is being written, the command prompts you for a name (press Enter with no input for a timestamp-only directory). With no name and no terminal — such as in CI — it errors instead, so a migration is never written unnamed by accident. A no-op run needs no name at all. The migration directory and the version bumps are committed to your repo, so every schema change is reviewable in git diff.
Removing event types is confirmed. When a migration would drop event types from the catalog, migrate dev lists them and asks for an explicit y (default No). In CI or any non-interactive shell it errors instead — pass --accept-removals to remove deliberately.

Merging branches with migrations

Migration directories carry a UTC timestamp prefix, so two branches almost never mint the same directory name and git merges them cleanly; migration_lock.toml holds no history, so it never conflicts either. Each migration also records its parent (the previous migration) and a checksum of its snapshot, so the CLI can reconstruct the lineage. When two branches each ran migrate dev, merging both leaves the lineage with two heads — a fork. Every command that reads migrations detects it: generate and migrate status fail loudly and name both directories rather than silently emitting reconciled versions. Run repost schema migrate dev once on the merged schema to resolve it: it diffs the merge against each side, prints a per-parent summary of what each branch contributed, resolves any contested version number to the next free one, and writes a single reconciling migration recording parents: [a, b]. Commit that migration. For CI, run repost schema validate and then repost schema migrate status. status exits non-zero on a fork, on unmigrated changes (the schema is ahead of the recorded history), or on a hand-edited snapshot (checksum mismatch), and exits 0 when the lineage is clean.

Deploy to your environment

repost schema migrate deploy applies your committed migrations to a live Repost environment: it sends the migration head, the full lineage, and the accumulated schemas.json, and the server materializes the event-type registry from it. It requires a clean local status — a fork, unmigrated changes, or a checksum mismatch is a hard error, so what you deploy is always what your repo records.
Authentication comes from REPOST_TOKEN — an environment-scoped token with the schema:deploy scope — and REPOST_API_URL when you target something other than the default API. Both are read from repost/.env (scaffolded by repost schema init) before the process environment. On success the CLI prints the applied diff (created, updated, archived event types); re-deploying the same head is a no-op. When the server’s deployed head is not an ancestor of yours, the deploy is refused with a reason. If your checkout is merely behind the deployed history, pull the latest migrations from your repo (git pull) and redeploy. If the histories have diverged, merge the deployed migrations in git and run repost schema migrate dev to reconcile — or replace the deployed history with --force (below). For development and staging loops, repost schema push sends the current schema without writing a migration artifact — the equivalent of Prisma’s db push. It is refused on environments that already have migration history, so a push can never fork a production lineage. Repeat pushes of an unchanged schema are no-ops.
Pass --dry-run to either command to print the deployment payload instead of sending it.

Forcing past a diverged history

Some histories the ancestry gate can never accept on their own: you squashed or re-initialized your migration directories, or restored the environment from another repo. There is no ancestor to fast-forward from and no git pull that closes the gap. repost schema migrate deploy --force replaces the deployed history outright. Force is deliberate and shows its work. The CLI first fetches the deployed head and prints exactly what will be replaced — checksum, kind, migration name, and deploy time — then asks for an explicit y (default No). In CI or any non-interactive shell it errors instead; pass --accept-replace to force deliberately.
Force is leased, not blind. The head the CLI just showed you is echoed back to the server as the expected head; if another deploy moved it between your look and your force, the server refuses rather than clobber a head nobody reviewed. Forcing to an older, previously deployed checksum is the supported rollback path — it rewinds the registry to that earlier state, and the CLI prints an extra warning when it detects one. A wrong force is recoverable: archived event types still accept publishes and still deliver, so nothing is dropped, and redeploying the correct head heals the registry.

Import from Svix

Convert a Svix environment export into a repost/ workspace — one catalog member and event binding per event type, a payload model per schema, and an initial migration that preserves the source’s full version history.
Names are kept verbatim. Any Svix event-type name that cannot be represented is reported as an error listing every offender, rather than being silently changed.

Command summary

Each command has its own reference page:
Contributors and CI that build a dev CLI do not embed the engine. make -C repost-go install-dev-cli installs the engine onto your PATH alongside the devrepost binary. Set REPOST_SCHEMA_ENGINE to point at a specific engine build if you need to override resolution.

Next steps

Reference

See every repost schema command and flag in the complete CLI reference.

Authentication

Install the CLI binary — the schema engine ships bundled inside it.