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 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 — and derives the backend from it, writing it to NEXT_PUBLIC_KIZLO_API_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_API_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_API_URL) to set it per environment.
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 generateIt 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.
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.
kizlo dev
kizlo dev is the command you run while developing. It watches your server and regenerates the
contract on every save — debounced, so a burst of saves collapses 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 dev.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
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
dev.local and test.local in kizlo.config.ts for you. There's no
folder to pick: the install location is fixed.
dev.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.
On first install, kizlo dev mints a random admin password (not a guessable default) and prints it once
so you can log into wp-admin. Save it then; it isn't stored on disk. Later runs are idempotent and leave
that login untouched.
Local WordPress mints no application password — that's a test concern. To point an app at it over
REST, create one in wp-admin → Users → Profile → Application Passwords.
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 — so your dev environment 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 test.local set, kizlo test runs your test suite against real WordPress — 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, 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. Without test.local, 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: "@",
dev: {
local: true,
port: 8080,
dbPort: 3307,
fixtures: [],
},
test: {
local: true,
port: 8889,
fixtures: [],
},
})| 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) |
alias | init | Import-alias prefix for generated imports, e.g. @. Blank means relative imports |
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 |
dev.local | dev | Run local WordPress under kizlo dev. When true, kizlo dev boots a Docker WordPress in .kizlo/local; unset or false runs the watcher alone. Set by create/init when you choose local WordPress |
dev.port | dev | Port local WordPress listens on under kizlo dev. Defaults to 8080 |
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 |
dev.fixtures | dev | Fixtures to build local WordPress from — the same fixtures the test environment uses. 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) |
test.local | test | Run local WordPress under kizlo test. When true, kizlo test boots a disposable Docker WordPress, seeds it, and runs your suite against it; unset or false just runs your test script. Set by create/init when you choose local WordPress |
test.port | test | Port test WordPress listens on. Defaults to 8889 |
test.fixtures | test | Fixtures to install and seed onto test WordPress — core content plus any extension's fixtures |
test.packageManager | test | Package manager for the test command. Auto-detected from your lockfile when unset |
test.command | test | Override the test command entirely, instead of <packageManager> test |