CLI
The CLI ships inside the kibbleforms package. Run it with npx kibbleforms <command> before the package is installed, or npx kibble <command> after. Every command accepts --json for machine-readable output on stdout. Errors say what went wrong and what to do next. Exit codes: 0 ok, 1 error, 2 check failed.
Configuration: kibble.json at the repo root (projectId, apiUrl, formsDir), secret key in .kibble/secret (gitignored). KIBBLE_SECRET_KEY and KIBBLE_API_URL override both, which is what CI uses.
kibble init
Creates a sandbox project and sets up the repo.
npx kibbleforms init [--agent claude-code] [--name "Marketing site"] [--api-url https://api.kibbleforms.com] [--forms-dir kibble/forms] [--force]
Writes kibble.json, .kibble/secret, kibble/client.ts, kibble/forms/, adds .kibble/ to .gitignore, and appends a short "Kibble forms" section to AGENTS.md and CLAUDE.md (whichever exist; creates AGENTS.md if neither). Prints the claim link. Refuses to overwrite an existing kibble.json without --force. Provisioning is rate limited per IP. In a monorepo, run it in the app's folder: at a monorepo root (no package.json, a workspace setup, or no React) with an app below it, it stops before doing anything (a link code stays unused) and names the app folders, Next.js and Astro apps first; --force sets up the current folder anyway. The install hint matches your package manager.
--agent <name> tells Kibble which coding agent ran the command (agents should name themselves). Without it the CLI uses KIBBLE_AGENT, then AI_AGENT, then CLAUDECODE=1 (Claude Code); init --link sends it too.
kibble init --link <code>
Connects the repo to a site that was started in the Kibble dashboard (New form). The dashboard puts the command, with a one-time code, at the top of the prompt it gives you for the form; codes work once and expire after 24 hours.
npx kibbleforms init --link kbl_XXXXX-XXXXX-XXXXX-XXXXX [--force]
It does everything init does (config, .kibble/secret, kibble/client.ts, the plugin, CSS and the AGENTS.md section) but uses the site created in the dashboard, which is already claimed, so there is no claim link. It prints Connected to <site> and the forms waiting for a file:
✓ Connected to Acme marketing (prj_…)
Forms waiting for a file:
kibble/forms/waitlist.ts — Waitlist
Template: waitlist
kibble/forms/quote.ts — Quote request
A short intake for people who want a custom quote: what they need built, rough budget…
Write each file with defineForm and the same slug; the dev server (or kibble push) registers it and the draft becomes the form. --json returns the project id and the drafts (never the key). An expired or used code asks you to copy a fresh prompt from the form's page; a site that is already connected to another repo returns the API's hint (copy KIBBLE_SECRET_KEY from that repo, or create a reconnect code on the site's page under Sites). --force replaces an existing kibble.json.
Setup edits
Both forms of init also set up the site, unless you pass --no-setup:
- Framework plugin. Next.js: wraps the default export of
next.config.(ts|mjs|js)inwithKibble(...)and adds the import (handlesexport default nextConfig,export default { … }andmodule.exports = …). Vite and React Router: addskibble()topluginsinvite.config.*. Astro: addskibble()tovite.pluginsinastro.config.*. When the file has a shape it doesn't recognize, nothing is written and the two lines to add are printed. - CSS. With Tailwind v4, adds
@import "kibbleforms/theme.css"and the@sourceline after@import "tailwindcss"in the global stylesheet; on shadcn/ui sites (components.json, whosetailwind.csspath is used) also@import "kibbleforms/themes/shadcn.css". With Tailwind v3 it addskibbleforms/styles.css(andthemes/shadcn-v3.css) at the top. Without Tailwind it printsimport 'kibbleforms/styles.css'.
kibble push [slug] [--env production|preview|development]
Loads every schema in the forms directory, validates it locally, and registers it. Prints the version number and the diff from the previous version. Warns while the project is unclaimed. With the framework plugin you rarely need it: the dev server registers a form when its file is saved, and builds run sync. The push is tagged with its source (see sync); from a laptop that is development, so it never changes the questions the dashboard shows for a live form. Pass --env to override.
✓ waitlist v3 (new version)
+ role (dropdown)
~ name → full_name
⚠ Project is unclaimed. Public origins are rejected until it is claimed: https://kibbleforms.com/claim/…
kibble test [slug]
Sends a sample submission built from the schema, with the secret key and a test flag, then reads it back. Test submissions are stored with status test and never count as responses or toward the response limit (triage still reviews them, at most 20 per form a day, and those reviews count toward the triage quota), and never trigger notifications.
kibble status
Project mode (sandbox or live), claim link, allowed origins, delivery destinations, drafts created in Kibble that are still "Waiting for a file: kibble/forms/kibble review <slug> --ideal "…"), prints one line per schema that still carries moved settings (notifyEmails, allowedOrigins, review), and flags forms whose local schema differs from what was registered.
Updates: status and sync say when a newer kibbleforms is on npm ("kibbleforms 0.1.5 is out (you have 0.1.4). What changed: https://kibbleforms.com/docs/changelog · Upgrade: pnpm add kibbleforms@latest"; --json has it as update). They ask the registry at most once a day (cached in ~/.cache/kibbleforms), give up after 1.5 seconds and never fail a command; the dev server's sync and CI builds don't check. KIBBLE_NO_UPDATE_CHECK=1 turns it off. See the changelog.
kibble sync [--env production|preview|development]
The build step the framework plugin runs (withKibble during next build, kibble() during vite build); call it yourself in any other CI.
- With a key (
KIBBLE_SECRET_KEY, or.kibble/secretlocally): registers every form, tagged with its source. - Without one: checks the registered forms through the public schema endpoint. It fails (exit 2) only when visitors would be rejected: the project is unclaimed, or a form is not registered yet (a new form, or a draft created in Kibble whose file has not been registered). It warns when a form's questions changed since the registered version (those responses are accepted and flagged).
An unclaimed project fails builds on a deploy host or in CI and only warns on a local build. If Kibble can't be reached, the build continues with a warning.
The source comes from the host: Vercel (VERCEL_ENV, VERCEL_GIT_COMMIT_REF, VERCEL_GIT_COMMIT_SHA, VERCEL_URL), Netlify (CONTEXT, BRANCH, COMMIT_REF, DEPLOY_URL), Cloudflare Pages (CF_PAGES_BRANCH, CF_PAGES_COMMIT_SHA, CF_PAGES_URL; production on KIBBLE_PRODUCTION_BRANCH or main), GitHub Actions (GITHUB_REF_NAME, GITHUB_SHA; same branch rule), Render (RENDER, IS_PULL_REQUEST, RENDER_GIT_BRANCH, RENDER_GIT_COMMIT), Railway (RAILWAY_ENVIRONMENT_NAME, RAILWAY_GIT_BRANCH, RAILWAY_GIT_COMMIT_SHA); GitLab, CircleCI, Buildkite, Jenkins, Bitbucket Pipelines, Azure Pipelines and Travis by their branch and pull/merge request variables (production on KIBBLE_PRODUCTION_BRANCH, else the provider's default branch when it says, else main; preview otherwise); and any other build server that sets CI (production). KIBBLE_ENV overrides. A build with no host signal and no CI is development, a local next build included, so building a branch on your laptop never changes production; if your build server doesn't set CI, set KIBBLE_ENV=production (the build output says so). Production registrations set the questions the dashboard shows; preview and development ones never do.
kibble sync production (vercel, main@1a2b3c4) · 3 forms · registering
✓ demo-request v4 registered
✓ waitlist v2 unchanged
kibble env vercel|netlify|print
Puts the project's secret key into the host so deploy builds can register forms.
vercel:vercel env add KIBBLE_SECRET_KEY productionand… preview, with the key on stdin. Needs the Vercel CLI, logged in andvercel linked.netlify:netlify env:set KIBBLE_SECRET_KEY … --context productionanddeploy-preview. Needs the Netlify CLI, logged in andnetlify linked.print: printsKIBBLE_SECRET_KEY=sk_…to your terminal so you can paste it into any host's settings.
Kibble never prints the key for vercel and netlify, and redacts it from the host CLI's output. When the host CLI is missing, not logged in, not linked, or the variable already exists, the error says what to run.
kibble check [--allow-sandbox]
A strict local check. Exits 2 when the project is unclaimed or any local schema differs from the registered one (or was never registered). Builds don't need it any more: the framework plugin runs sync, which fails only when visitors would be rejected.
kibble claim [--open] [--wait]
Prints the claim link. --open opens it in a browser. --wait polls until the project is live.
kibble submissions [slug] [--status all|complete|partial|spam|test] [--limit 20]
Reads submissions through the project API, with each response's kind (complete, partial, spam, test), when it came in and its first answers. For sandbox projects this is the agent's own test data and expires after 72 hours.
kibble destinations …
Wire delivery without the dashboard. Destinations belong to a form: pass --form <slug>, or --all-forms to create one per form.
npx kibble destinations list
npx kibble destinations add slack --form demo-request # "Add to Slack": prints a link for a person, waits for the channel
npx kibble destinations add slack https://hooks.slack.com/services/… --all-forms
npx kibble destinations add postgres "postgres://…pooler.supabase.com:6543/postgres" [--table demo_requests] --form demo-request
npx kibble destinations add email [email protected] --form demo-request # workspace members only
npx kibble destinations add email [email protected] --form demo-request --categories "Sales lead,Partnership" --fit high,medium
npx kibble destinations add slack https://hooks.slack.com/services/… --form demo-request --fit high
npx kibble destinations add slack https://hooks.slack.com/services/… --form demo-request --group "Urgency: This week"
npx kibble destinations add webhook https://example.com/hooks/kibble --form demo-request # prints the signing secret once
npx kibble destinations remove dst_…
npx kibble destinations test dst_…
Slack without a URL prints a 30-minute link to "Add to Slack" for a person in the site's workspace (an agent can't click through Slack's install screen) and waits up to 10 minutes for the channel to appear; --no-wait prints the link and exits, and --json returns { connectUrl, expiresAt, destinations } without waiting. Claimed sites only.
Webhook URLs must be public https:// addresses, and databases public hosts: Kibble refuses localhost, private ranges and internal names (see Destinations).
Email goes only to workspace members (no verification step); for shared inboxes and aliases use Slack. Adding a non-member address fails with the API's hint. Connection strings are encrypted at rest and never printed back. See Destinations.
--categories (comma-separated, matching the form's review categories) and --fit (high, medium, low) filter what an email or Slack destination sends, using the AI review. Responses without a review (review off, or the review failed) are always sent. The flags are rejected for webhooks and Postgres, which receive every response with the review attached. destinations list shows each filter in plain words ("Sales lead or Partnership, high or medium fit"); --json includes the filter object.
kibble domains list|add <domain>|remove <domain>
The site's production domains (allowed origins). Visitors from them can submit once the project is claimed; localhost always works. Accepts example.com, www.example.com, *.example.com or a full origin with a port. A domain and its www. form count as one site: adding either allows both (most sites redirect one to the other). API: GET/POST/DELETE /v1/project/domains.
npx kibble domains add example.com
npx kibble domains list
npx kibble domains remove staging.example.com
kibble review <slug> [--ideal "…"] [--summary "…"] [--categories "A,B"] [--group "Name: A, B"] [--remove-group "Name"] [--off|--on]
Triage for one form. With no flags it shows the settings; flags change them and keep the rest (so --ideal alone keeps the categories). The ideal response describes a great respondent in plain words and enables fit levels; --summary says what each summary should focus on (3 to 500 characters); categories are 2 to 12 labels; --group "Urgency: This week, Later" adds a group triage sorts into, or replaces the one with that name (repeatable, up to 5), and --remove-group drops one. API: GET/PUT /v1/project/forms/:slug/review. See AI review.
npx kibble review demo-request --ideal "Product or research leads at 20–500 person software companies who want to start this quarter."
kibble turnstile [set <secret key> [--site-key <key>]|remove]
Cloudflare Turnstile with your own widget. set gives Kibble the widget's secret key so it can check visitors' tokens: the site key comes from settings.turnstileSiteKey in your schema files (--site-key when they use more than one), Kibble checks the secret key with Cloudflare, and it is stored encrypted and never printed back. With no action it lists the forms that set a site key and whether their tokens are checked; remove deletes the secret key. API: GET/PUT/DELETE /v1/project/turnstile.
npx kibble turnstile set 0x4AAAAAAA…
npx kibble turnstile