Docs

CLI

Learn how the kizlo CLI scaffolds a project, generates the typed contract, and runs your tests.

The kizlo CLI is the tool you run alongside your project. It scaffolds Kizlo into a new or existing app, generates the contract that types your client against your server, runs local WordPress you can build against, and runs your tests against a real, disposable WordPress.

It ships with the kizlo package, so there's nothing extra to install. Reach for it through your package runner:

npx kizlo <command>

The commands

A handful of commands cover the whole workflow, from first scaffold to a green test run:

CommandWhat it does
kizlo createScaffold a new project with Kizlo already wired
kizlo initSet up Kizlo in the current project
kizlo checkVerify generated files without changing them
kizlo generateGenerate the contract once for builds and CI
kizlo devWatch and regenerate the contract as you edit, and run local WordPress. Bare starts it; stop, reset
kizlo testBoot local WordPress, seed it, and run your tests (stop, reset)

Every command reads kizlo.config.ts to find where your server lives and how your test environment is set up.

kizlo create

create scaffolds a new project with Kizlo already wired. Reach for it when you're starting from scratch rather than adding to an app you already have. Give it a template and a directory name:

npx kizlo@latest create nextjs my-app

Both arguments are optional (kizlo create [template] [project-name]); create prompts for whichever you leave off, and the template auto-selects while nextjs is the only one. It creates my-app/, lays down a fully wired project for that template, and runs the same WordPress credential prompts as init. When it finishes, cd my-app, install dependencies, and make your first call. The Next.js quickstart walks it end to end.

kizlo init

init sets Kizlo up in an existing project. It detects your framework, package manager, src directory, and import alias, then scaffolds the server, client, route handler, and config, prompting for your WordPress credentials along the way and writing them to .env.

npx kizlo@latest init

It's the recommended path through the Installation guide, which walks the prompts in detail. Past the defaults, a few flags tune the run:

FlagEffect
--yes, -ySkip the prompts and scaffold with defaults (non-interactive, for CI)
--force, -fOverwrite existing .env values and the server entry without asking
--preset <id>Force a setup preset (base, nextjs) instead of auto-detecting
--alias <prefix>Set the import alias for generated imports (e.g. @); blank for relative

Backend URL

The plugin delivers webhook events to your Kizlo backend, so init records where it's reachable. A detected framework mounts the handler at a known path (/api/kizlo for Next.js), so init asks one question: your app's public URL. It derives the backend from it and writes it to NEXT_PUBLIC_KIZLO_BASE_URL.

The base preset has no known mount, so init asks two URLs: your public site URL (what the plugin points back at) and the backend URL where you've mounted the handler (written to KIZLO_BASE_URL).

The generated client.ts routes to that backend. Same-origin deploys default to the page origin; a backend on a different origin is inlined into createKizloClient(contract, { url }). Swap that for your bundler's own public env var (e.g. import.meta.env.VITE_KIZLO_BASE_URL) to set it per environment.

kizlo check

check compares the configured introspection.ts with the contract published by WordPress. It exits non-zero and prints a unified diff when the committed introspection is stale. It never changes the file.

npx kizlo check

Use --test after kizlo test to check against the WordPress stack built from test.fixtures. This makes the same command useful locally and in CI:

npx kizlo test
npx kizlo check --test

Pass --dir <path> when the Kizlo server lives somewhere other than the configured dir. If the check fails, the message prints the matching kizlo generate command.

kizlo generate

generate builds the contract once and exits. It's the command for builds and CI. Run it before you typecheck or bundle, so server/generated/ exists and is current when the client imports it.

npx kizlo generate

Your configured server entry must export procedures. generate reads that procedure tree and writes a JSON contract whose generated barrel is typed as typeof procedures:

src/lib/kizlo/server/index.ts
export const { procedures, client, context, handler } = createKizlo({ integrations: [nextjs()] })

A legacy router export is not used as a fallback. Rename it to procedures; the CLI reports this migration directly instead of saying that no Kizlo server was found.

It exits non-zero if it can't find a Kizlo server in your entry file or if generation fails, so a stale or broken contract fails the build rather than slipping through. It accepts --dir to point at a Kizlo directory other than the one in kizlo.config.ts, --test to generate from WordPress left running by kizlo test, and --strict to refuse a partial contract.

Wire kizlo generate into your build script (e.g. "build": "kizlo generate && next build") so the contract is always regenerated as part of every production build.

When a plugin's routes can't be published

One plugin's broken declaration doesn't take the rest of the contract down with it. WordPress publishes everything that passed validation and reports what it had to drop, and generate writes that subset. Your routes keep regenerating while the broken plugin gets fixed.

Every exclusion is printed with the schema or route it landed on, then a count of what's missing from the generated introspection. Warnings are separate: a warning means a declaration lost a constraint, and the type it produces is still correct.

Pass --strict to refuse a partial contract instead:

npx kizlo generate --strict

Strict generation prints the same diagnostics, exits non-zero, and leaves the introspection already on disk untouched, so a build never replaces a complete introspection with one that's quietly short of routes. Reach for it in CI, where a missing route typechecks clean and ships the gap. It governs the introspection only, since contract.json is built from your own procedures and has no third-party contributors.

kizlo dev is always partial. A watcher that refused to write would leave you working against a client that can't be regenerated until someone else's plugin is fixed.

kizlo dev

kizlo dev is the command you run while developing. It watches your server and regenerates the contract on every save. A debounce collapses a burst of saves into one rebuild, and your client stays typed against the latest server without you thinking about it. A broken server entry doesn't stop it: it reports the error and keeps watching, so the next save can fix it. Only one watcher writes the contract at a time. A single-instance lock means a framework dev script and kizlo dev won't fight over the same file.

npx kizlo dev

When local is set, kizlo dev also runs local WordPress alongside the watcher: a long-lived Docker WordPress on http://localhost:8080 you build your app against. Without it (a project pointed at your own WordPress), it runs the watcher alone. What sets local WordPress apart from the test environment:

  • Every file is editable. The whole install (core, themes, uploads, plugins) lives in .kizlo/local, bind-mounted into the container. Browse and edit it from your file manager, and changes are live.
  • Plugins come from your local.dev.fixtures, the same fixtures the test environment uses. Each fixture installs the plugins it needs (a wp.org slug or zip source), or bind-mounts a local { path: "..." } over wp-content/plugins so plugin source edits show up live.
  • Your data survives. It's never wiped on start; fixtures seed only on a fresh install.
CommandWhat it does
kizlo devStart local WordPress and the watcher; idempotent, never wipes
kizlo dev stopStop local WordPress, keeping everything for a fast resume
kizlo dev resetWipe the database and the .kizlo/local install, so the next kizlo dev rebuilds fresh

kizlo dev runs in the foreground and stops local WordPress when you press Ctrl+C or close the terminal, like next dev, so it never outlives your session. stop and reset are escape hatches for the rare times you need to act on it outside a kizlo dev run.

Enabling local WordPress

kizlo create and kizlo init offer to set up local WordPress during onboarding. Choose it and they provision the .kizlo/local install, write the connection to .env, and set local with its dev and test stacks in kizlo.config.ts for you. There's no folder to pick: the install location is fixed.

local is committed, so a teammate who clones the repo gets local WordPress on their first kizlo dev (it provisions their own .kizlo/local). The install and credentials stay per-machine and gitignored.

Fresh local WordPress installs use admin for both the wp-admin username and password. Existing installs keep their current login. Run kizlo dev reset to rebuild an existing dev site with the default credentials.

kizlo dev creates a separate application password for REST access and writes it to the local .env connection. That credential is not the wp-admin password.

Its MySQL is published on 127.0.0.1:3307 (override with dev.dbPort) so you can inspect or edit tables directly, beyond what wp-admin exposes. Kizlo doesn't bundle a database UI. Point whatever client you already use (the mysql CLI, TablePlus, DBeaver, Sequel Ace) at host 127.0.0.1, that port, database wordpress, user wordpress, password wppass:

mysql -h127.0.0.1 -P3307 -uwordpress -pwppass wordpress

.kizlo/local is your persistent dev site: it holds the database-linked files, uploads, and edits. It sits under the gitignored .kizlo/ working dir, so don't delete it by hand. Only kizlo dev reset is meant to wipe it.

Local WordPress always enables PHP OPcache with dev-safe settings (it still picks up your live edits), which offsets most of the slower file I/O that a bind-mounted install costs on macOS/Windows.

On native Linux it also runs the container as your user, so the files WordPress writes into the folder are owned by you and stay editable: no sudo, no permission surprises. macOS/Windows handle that mapping already, so nothing extra happens there.

Seed with fixtures

If you want a code-defined starting point rather than a real-data snapshot, point dev.fixtures at the same fixtures your test environment uses. Each runs once over REST (and can drop to wp-cli for anything REST can't reach) on a fresh install: the first kizlo dev or after reset. Your dev environment then mirrors your test data from versioned code:

dev: { fixtures: [postFixture()] }

On reruns of an existing install, fixtures don't re-run, so your manual edits are never clobbered.

kizlo test

With local set, kizlo test runs your test suite against real WordPress, with no mocks. It boots a Docker-based WordPress (on http://localhost:8889, independent of your local dev WordPress), seeds it with your fixtures, then runs your project's own test command against it:

npx kizlo test

Local WordPress is idempotent here. The first run boots the containers and seeds them; later runs reuse the same install and skip the slow seed, so reruns are fast. After the suite finishes it's left running by default for that reason. Two flags change the lifecycle:

FlagEffect
--resetWipe the database and reseed from scratch before running
--teardownStop it after the suite finishes, instead of leaving it up
npx kizlo test --reset --teardown   # a clean install, torn down afterward

Under the hood test runs your package manager's test script (pnpm test, npm test, or bun run test), detected from your lockfile, or whatever you set as the test command. Its output streams straight to your terminal, and kizlo test exits with your suite's exit code. With the test stack off, kizlo test skips the Docker WordPress entirely and just runs that script.

See Development & Testing for how to write the suites that run against WordPress, and how fixtures seed the data they expect.

Managing test WordPress by hand

A kizlo test run leaves WordPress up by default, so you can poke at WP Admin, debug a fixture, or point another tool at the seeded credentials between runs. To get running WordPress without invoking your suite, run kizlo test once. It seeds and stays up.

These subcommands drive that same install's lifecycle by hand:

CommandWhat it does
kizlo test stopStop it, keeping the database volume for a fast resume
kizlo test resetWipe the database volume and reseed fresh

The difference between stop and reset is the database volume: stop keeps it, reset throws it away.

Configuration

kizlo.config.ts lives at your project root (.js and .mjs work too). init writes it for you; you edit it as your project grows. Define it with defineConfig for a fully typed config:

kizlo.config.ts
import { defineConfig } from "kizlo/config"

export default defineConfig({
  dir: "src/lib/kizlo",
  alias: "@",
  local: {
    dev: {
      version: "latest",
      port: 8080,
      dbPort: 3307,
      fixtures: [],
    },
    test: {
      port: 8889,
    },
  },
})

Everything about generation lives under dir; everything about the local stacks lives under local. dir takes a string (the home directory Kizlo owns) or an object that sets server, contract, and introspection paths on their own. Omit server and there are no sources to watch and no contract, only the generated introspection.

FieldUsed byMeaning
dirallKizlo's home directory, where the CLI finds server/ and writes generated/. Defaults to src/lib/kizlo (or lib/kizlo without a src/ directory). Pass an object ({ server, contract, introspection }) to set each path on its own
aliasinitImport-alias prefix for generated imports, e.g. @. Blank means relative imports
localdev, testLocal Docker WordPress, off unless set. true enables both stacks with defaults; the object form enables and configures them
local.namedev, testBase name for the Docker projects (<name>-dev, <name>-test). Defaults to the sanitized package.json name, then the config dir basename
local.worktreesdev, testAppend the checked-out branch to name, so each branch gets its own Docker projects (<name>-<branch>-dev). Off unless set. Turn it on when you keep several checkouts of one project, typically git worktrees, so their WordPress and database stop being shared
local.dev.enabledevRun the dev stack. On by default when local is set; false keeps local WordPress on for kizlo test while kizlo dev runs the watcher alone
local.dev.versiondevWordPress image tag the dev stack boots (the tag after wordpress:). Defaults to latest
local.dev.portdevPort local WordPress listens on under kizlo dev. Defaults to 8080
local.dev.dbPortdevHost port the local MySQL is published on (bound to 127.0.0.1) so you can point a SQL client at the database. Defaults to 3307; change it if it clashes
local.dev.fixturesdevFixtures to build local WordPress from, matching the test environment. Each declares the plugins it needs (wp.org slugs / zip sources to install, or { path } local dirs bind-mounted live into wp-content/plugins by basename) and is seeded on a fresh install (first kizlo dev / after reset)
local.test.enabletestRun the test stack. On by default when local is set; false keeps local WordPress on for kizlo dev while kizlo test runs your test script alone
local.test.inherittestFall back to the dev stack's version and fixtures when the test stack omits them (default true). Set false to configure the test stack independently
local.test.versiontestWordPress image tag the test stack boots. Falls back to local.dev.version unless inherit is false
local.test.porttestPort test WordPress listens on. Defaults to 8889
local.test.fixturestestFixtures to install and seed onto test WordPress. Falls back to local.dev.fixtures unless inherit is false
local.test.packageManagertestPackage manager for the test command. Auto-detected from your lockfile when unset
local.test.commandtestOverride the test command entirely, instead of <packageManager> test

On this page