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:
| Command | What it does |
|---|---|
kizlo create | Scaffold a new project with Kizlo already wired |
kizlo init | Set up Kizlo in the current project |
kizlo check | Verify generated files without changing them |
kizlo generate | Generate the contract once for builds and CI |
kizlo dev | Watch and regenerate the contract as you edit, and run local WordPress. Bare starts it; stop, reset |
kizlo test | Boot 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-appBoth 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 initIt's the recommended path through the Installation guide, which walks the prompts in detail. Past the defaults, a few flags tune the run:
| Flag | Effect |
|---|---|
--yes, -y | Skip the prompts and scaffold with defaults (non-interactive, for CI) |
--force, -f | Overwrite 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 checkUse --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 --testPass --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 generateYour configured server entry must export procedures. generate reads that procedure tree and writes a JSON
contract whose generated barrel is typed as typeof procedures:
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 --strictStrict 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 devWhen 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: "..." }overwp-content/pluginsso plugin source edits show up live. - Your data survives. It's never wiped on start; fixtures seed only on a fresh install.
| Command | What it does |
|---|---|
kizlo dev | Start local WordPress and the watcher; idempotent, never wipes |
kizlo dev stop | Stop local WordPress, keeping everything for a fast resume |
kizlo dev reset | Wipe 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 testLocal 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:
| Flag | Effect |
|---|---|
--reset | Wipe the database and reseed from scratch before running |
--teardown | Stop it after the suite finishes, instead of leaving it up |
npx kizlo test --reset --teardown # a clean install, torn down afterwardUnder 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:
| Command | What it does |
|---|---|
kizlo test stop | Stop it, keeping the database volume for a fast resume |
kizlo test reset | Wipe 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:
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.
| Field | Used by | Meaning |
|---|---|---|
dir | all | Kizlo'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 |
alias | init | Import-alias prefix for generated imports, e.g. @. Blank means relative imports |
local | dev, test | Local Docker WordPress, off unless set. true enables both stacks with defaults; the object form enables and configures them |
local.name | dev, test | Base name for the Docker projects (<name>-dev, <name>-test). Defaults to the sanitized package.json name, then the config dir basename |
local.worktrees | dev, test | Append 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.enable | dev | Run 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.version | dev | WordPress image tag the dev stack boots (the tag after wordpress:). Defaults to latest |
local.dev.port | dev | Port local WordPress listens on under kizlo dev. Defaults to 8080 |
local.dev.dbPort | dev | Host 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.fixtures | dev | Fixtures 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.enable | test | Run 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.inherit | test | Fall 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.version | test | WordPress image tag the test stack boots. Falls back to local.dev.version unless inherit is false |
local.test.port | test | Port test WordPress listens on. Defaults to 8889 |
local.test.fixtures | test | Fixtures to install and seed onto test WordPress. Falls back to local.dev.fixtures unless inherit is false |
local.test.packageManager | test | Package manager for the test command. Auto-detected from your lockfile when unset |
local.test.command | test | Override the test command entirely, instead of <packageManager> test |