Publishing a Template
Any app you own becomes a template the moment you make it public and push a repo snapshot. There’s no submission, no review queue, and no separate template object — the app is the template.
Why publish
Section titled “Why publish”- Distribution. Listed templates show up in the dashboard’s Templates browser and in
find_templatesfor every AI agent connected to Butterbase. - A clone counter. Every clone increments your app’s
fork_count, which is what “Popular” sorts on. - Onboarding that actually works. Instead of a README telling someone to create eight resources by hand, they click Clone and have a running copy.
- A hook into the moment someone clones. Fire a webhook on every completed clone — greet the new owner, count adoption, trigger provisioning on your side.
Requirements
Section titled “Requirements”| Requirement | Why |
|---|---|
visibility: public | Clone refuses non-public sources with a 404 |
At least one butterbase repo push | Clone refuses a source with no repo snapshot — that snapshot is the code that gets copied |
listed: true (optional) | Controls discoverability, not clonability — see below |
| App is fully provisioned | Un-provisioned apps are filtered out of discovery |
Step 1 — Prepare the app for other people
Section titled “Step 1 — Prepare the app for other people”Do this before you flip visibility. Once the app is public, its schema, function code, repo files, and env var key names are readable by anyone.
- Audit what’s in your tables. Only tables marked
_seed: trueget their rows copied, but the schema of every table is public. Don’t ship column names that leak anything. - Audit your repo snapshot. Whatever
butterbase repo pushuploaded is what people download. Check for committed.envfiles, fixture data with real customer records, hardcoded keys in source. Use the ignore rules — seebutterbase repo. - Audit your env var names. Values never leave, but names are part of the public surface.
ACME_CORP_INTERNAL_TOKENtells a story. - Audit your RLS policies. They copy verbatim. If your app is permissive because it’s a demo, every clone inherits that. Consider tightening first — see Row-Level Security.
- Make sure it works from cold. Clone your own app into a scratch app and run through it. That is the experience you’re shipping.
Step 2 — Add seed data
Section titled “Step 2 — Add seed data”An empty app is a bad first impression. Mark a table with _seed: true in your schema and its rows travel with the clone:
{ "tables": { "categories": { "_seed": true, "id": "uuid primary key", "name": "text not null" } }}Tables without _seed: true clone with their schema only. Use this for reference data, example content, and demo rows — not for anything user-specific. See Database & Schema.
Step 3 — Push your repo snapshot
Section titled “Step 3 — Push your repo snapshot”butterbase repo init <app_id> # once, binds the folderbutterbase repo push --message "Template release"Pushes are content-addressed, so re-pushing is cheap. The latest snapshot is what clones copy — push again whenever you want new cloners to get updated code. Existing clones are unaffected; they took a copy.
Step 4 — Make it public
Section titled “Step 4 — Make it public”CLI:
butterbase visibility public --listedbutterbase visibility public --unlistedbutterbase visibility privateMCP:
manage_app action: "set_visibility" app_id: "<app_id>" visibility: "public" listed: trueREST:
PATCH /v1/{app_id}/config/visibility{ "visibility": "public", "listed": true }Dashboard: App → Settings → Visibility.
public vs listed
Section titled “public vs listed”visibility | listed | Result |
|---|---|---|
private | — | Not clonable at all |
public | true | Clonable, and appears in the Templates browser and find_templates |
public | false | Clonable by anyone who has the app id, but not discoverable |
public + listed: false is the “share this with my team / my course / my client” mode: send the app id to whoever needs it and nobody else can find it.
Step 5 — Set a clone webhook (optional)
Section titled “Step 5 — Set a clone webhook (optional)”Get an HTTP callback whenever someone finishes cloning your app.
MCP:
manage_app action: "set_clone_webhook" app_id: "<app_id>" webhook_url: "https://example.com/hooks/butterbase-clone" webhook_secret: "<16-256 chars>"REST:
PATCH /v1/{app_id}/config/clone-webhook{ "webhook_url": "https://example.com/hooks/clone", "webhook_secret": "…" }Clear it with { "clear": true }, or clear_webhook: true over MCP.
The delivery:
POST https://example.com/hooks/cloneContent-Type: application/jsonX-Butterbase-Event: clone_completedX-Butterbase-Signature: sha256=<hex>
{ "event": "clone_completed", "job_id": "…", "source_app_id": "app_yours", "dest_app_id": "app_theirs", "dest_region": "us-west-2", "completed_at": "2026-08-26T12:00:00.000Z"}Verify the signature. It’s HMAC-SHA256 of the raw request body, keyed with your secret, formatted sha256=<hex>:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, header: string, secret: string) { const expected = `sha256=${createHmac('sha256', secret).update(rawBody).digest('hex')}`; const a = Buffer.from(expected), b = Buffer.from(header); return a.length === b.length && timingSafeEqual(a, b);}Compute it over the raw body, not a re-serialized object. Return a 2xx — non-2xx responses are retried with backoff and then dropped.
Maintaining a template
Section titled “Maintaining a template”Publishing an update: push a new repo snapshot and apply any schema migration to your app. New clones pick up the current state; existing clones don’t change. There is no upgrade path from a template to its clones — a clone is a fork, not a subscription.
Unpublishing: set visibility: private. Existing clones keep working; they’ve been independent since the moment they completed.
Versioning: if you need a v1 and a v2 that both stay clonable, keep them as two apps. repo retains only the five most recent snapshots, and clones always take the latest.
Checklist before you flip the switch
Section titled “Checklist before you flip the switch”- No secrets, real customer data, or
.envfiles in the repo snapshot - Env var names give nothing away
- RLS policies are safe as a default for someone else’s app
- Seed data is representative and contains nothing private
- You cloned it yourself and the clone actually runs
- A README in the repo explains what to configure post-clone
-
listedset the way you want — discoverable, or id-only