Build stage — for AI agents and the people who run them
Hand your agent a SaaS to ship.
Free, and done in one paste.
This page is built to be pasted. Copy the handoff prompt below into Claude Code, Cursor, OpenCode, or any coding agent. It scaffolds a production-ready SaaS (Next.js on Cloudflare, auth on D1, Stripe billing, transactional email, a brand kit page), wires up GitHub, and deploys. Every step has a verification; the agent stops and reports if one fails.
1 · Scaffold the app (or let the agent run it)
2 · Give the agent the startupkit skill (optional)
3 · Add the research + launch tools via MCP (optional, free key at /dashboard)
The agent handoff prompt
Replace <Product Name>, <org>, and <yourdomain.com> before pasting.
You are scaffolding a new SaaS application from the StartupKit template and
taking it through local setup, GitHub repo creation, and Cloudflare deployment.
Follow the steps in order. Each step has a verification — do not proceed past a
failed verification.
## Prerequisites
- `bun` installed (`bun --version`)
- `git` configured with the user's credentials
- Cloudflare account access (`bunx wrangler whoami` works, or the user provides
`CLOUDFLARE_API_TOKEN`)
- `gh` CLI authenticated (`gh auth status`) — for repo creation
## 1. Scaffold
```bash
npx startupkit init <Project Name>
cd <project-name-slug>
```
This clones the template from `01-studio/startupkit/templates/repo` (private
repo — requires git credentials), initializes a fresh git history, runs
`bun install`, and installs the `startupkit` agent skill to
`.agents/skills/startupkit/`. A root `AGENTS.md` (with `CLAUDE.md`) ships too —
the Stack guide: our default agent behaviors (how we work, Linear workflow +
etiquette, the git-worktree workflow, SDLC/ADLC) plus a `## StartupKit Stack`
section for this scaffold. Refresh it with `npx startupkit agents --stack`; use
`npx startupkit agents` to install the tool-agnostic default guide in any other
project.
**Stack included:** Next.js 16 App Router, Cloudflare Workers via OpenNext,
better-auth (email OTP + optional Google OAuth) on Cloudflare D1, Stripe
billing (checkout + portal + webhook), transactional email (React Email +
Cloudflare Email binding), shadcn-style UI, Tailwind 4, next-intl, vitest.
## 2. Local environment
```bash
cd apps/web
cp .env.example .env.local
cp .dev.vars.example .dev.vars
```
Generate one secret and put the **same value in both files** as
`BETTER_AUTH_SECRET=`:
```bash
openssl rand -base64 32
```
**Verify:**
```bash
bun run typecheck # passes
bun run migrate # applies 2 migrations to local D1
bun run test # 12 tests pass
```
## 3. Cloudflare D1 (required)
```bash
cd apps/web
bunx wrangler d1 create startupkit-db
```
Copy the returned `database_id` into **both** `wrangler.jsonc` and
`wrangler.next-dev.jsonc` (replace `REPLACE_WITH_YOUR_D1_DATABASE_ID`).
**Verify:** `bunx wrangler d1 list` shows `startupkit-db`.
## 4. Run locally
```bash
# repo root
bun run dev
```
- App: http://localhost:3000
- Email template preview: http://localhost:3001
**Verify:** open http://localhost:3000, click "Get started", sign up with an
email — locally the email is NOT sent (console-only fallback); in dev the OTP
is printed to the dev server log. Sign in with it. Dashboard loads.
Also verify the brand kit route is public but unindexed:
```bash
curl -sI http://localhost:3000/brandkit | grep -i "HTTP\|x-robots-tag" # 200 + noindex, nofollow
curl -s http://localhost:3000/robots.txt # Disallow: /brandkit
```
## 5. Optional integrations
### Google OAuth
Set `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` in `apps/web/.env.local`
(OAuth credentials from console.cloud.google.com). Auth degrades to email-OTP
only when unset.
### Stripe billing
1. Create one recurring product ("Pro") in the Stripe dashboard with monthly
and yearly prices.
2. In `apps/web/.env.local` set:
- `STRIPE_SECRET_KEY` (test key for dev)
- `STRIPE_PRICE_PRO_MONTHLY`, `STRIPE_PRICE_PRO_YEARLY`
3. Add a webhook endpoint (Stripe dashboard → Developers → Webhooks):
- URL: `https://<your-domain>/api/stripe/webhook`
- Events: `checkout.session.completed`,
`customer.subscription.created|updated|deleted`
- Copy the signing secret into `STRIPE_WEBHOOK_SECRET`
4. Enable the customer portal in Stripe settings (one-time dashboard action).
**Verify:** Settings page shows upgrade buttons; test checkout with card
`4242 4242 4242 4242` completes and returns to `/settings?upgraded=1`.
### Transactional email
Verify a sending domain in Cloudflare (Email Service) — the `EMAIL` binding is
already declared in both wrangler configs. Set `EMAIL_FROM` in
`apps/web/lib/email.ts` to the verified sender address.
## 6. Rename the product
Search-and-replace across the repo:
- `startupkit` → your product name (package names in `apps/web/package.json`,
root `package.json` script filters, wrangler worker name `startupkit-web`,
D1 name `startupkit-db`)
- `yourdomain.com` → the real domain (`apps/web/lib/urls.ts`,
`apps/web/lib/email.ts`, `apps/web/wrangler.jsonc` route)
- Favicon/copy: `apps/web/public/favicon.svg`, `apps/web/i18n/messages/en.json`,
`apps/web/app/page.tsx`, root `README.md`
### Make the brand kit yours
`/brandkit` (`apps/web/app/brandkit`) is a public, unindexed page with paste-ready
directory copy, brand assets, and a submission tracker. It ships describing
StartupKit itself — overwrite it with the new product:
1. `apps/web/lib/site.ts` — `name`, `url`, `oneLiner`, `tagline` (≤10 words),
`short` (≤60 chars), `category`, `model`, `platform`, `brandColor` (from the
new favicon), `socials` (only accounts that exist), and `surfaces` to
screenshot. This file also drives the root `<meta description>` and OG/Twitter
tags in `app/layout.tsx`.
2. `apps/web/app/brandkit/page.tsx` — rewrite the five long-description tiers from
the product's marketing copy (2–4 short paragraphs, a different opening
sentence per tier so directories don't dedupe them, ending with the URL),
the `Tags:` line, the Submit-next rows, and the Still-needed list. Leave
Confirmed live listings empty until a listing is verified live.
3. Regenerate assets (Chrome headless; committed to `apps/web/public/brandkit/`):
```bash
bun run brandkit:assets --skip-screenshots # logo.svg, logo PNGs, OG, social banners
BRANDKIT_BASE_URL=http://localhost:3000 bun run brandkit:assets --only=screenshots # with the app running
```
**Verify:** `bun run test` passes (`lib/site.test.ts` checks the ≤60/≤10
constraints and rejects placeholders); `GET /brandkit` contains no "StartupKit",
"startupkit.com", `{Product}`, or `TODO`; the site URL pasted into a link-preview
tool shows the new OG image and description.
> **Stack vs Launch:** this scaffold is the free **StartupKit Stack**. Paid
> research and launch tooling (StartupKit Launch) lives outside the scaffold —
> never imply it's included. The default `model`/pricing, tagline, and long
> descriptions describe StartupKit itself and must be replaced during the rebrand,
> or the scaffold ships drift.
## 7. Create the GitHub repo
```bash
gh repo create <org>/<repo-name> --private --source . --push
```
Enable Dependabot security updates. Version updates already come from
`.github/dependabot.yml` in the template, but security updates are per-repo
settings and off by default for private repos:
```bash
gh api -X PATCH repos/<org>/<repo-name> \
-f security_and_analysis[dependabot_alerts]=enabled \
-f security_and_analysis[dependabot_security_updates]=enabled
```
**Verify:** `gh api repos/<org>/<repo-name> --jq '.security_and_analysis'`
reports `dependabot_alerts` and `dependabot_security_updates` both `enabled`.
**Verify:** CI workflow (`.github/workflows/ci.yml`) runs green on the first
push (lint + typecheck + test).
Set the Cloudflare secrets so `.github/workflows/deploy.yml` can run (it
auto-deploys `main`: pending D1 migrations first, then the new worker version):
```bash
gh secret set CLOUDFLARE_API_TOKEN --repo <org>/<repo-name> \
--body "<API token with Workers + D1 edit permissions>"
gh secret set CLOUDFLARE_ACCOUNT_ID --repo <org>/<repo-name> \
--body "<account id from bunx wrangler whoami>"
```
## 8. Deploy to Cloudflare
```bash
# one-time secrets for production
cd apps/web
bunx wrangler secret put BETTER_AUTH_SECRET # same value as generated above
bunx wrangler secret put BETTER_AUTH_URL # https://<your-domain>
# apply migrations to production D1
bun run migrate:remote
# build + upload + shift traffic to 100%
bun run deploy
```
For a custom domain, uncomment the `routes` block in `apps/web/wrangler.jsonc`
with the real domain and re-run `bun run deploy`.
From now on, pushes to `main` deploy automatically: the workflow applies
pending D1 migrations before the code that reads them ships (`bun run
deploy` / `bun run migrate:remote` stay available for manual runs).
**Verify:** `curl -s -o /dev/null -w "%{http_code}" https://<your-domain>`
returns 200; sign-up flow works against production (emails actually send once
the domain is verified); Stripe webhook shows 200 deliveries.
## Troubleshooting
- `ENEEDAUTH`/404 from npm publish — not applicable here; that's CLI publishing.
- Checkout/`wrangler` 404s — wrong account; re-check `bunx wrangler whoami`.
- Session logged out randomly in dev — `BETTER_AUTH_SECRET` differs between
`.env.local` and `.dev.vars`; they must match.
- `wrangler d1 migrations apply DB --local` fails — run it from `apps/web/`.What the agent ships
Next.js 16 on Cloudflare Workers
App Router + OpenNext, one Worker serves everything. Versioned deploys with bun run deploy.
Auth on D1
better-auth with email OTP sign-in and optional Google OAuth. Sessions in Cloudflare D1, versioned migrations.
Stripe billing
Checkout, customer portal, webhook subscription sync. One 'pro' plan, monthly + yearly prices.
Transactional email
React Email templates with a preview server, delivered via Cloudflare Email Service.
Brand kit at /brandkit
Logos, OG image, social banners for every platform, screenshots, paste-ready descriptions, and a 15-directory submission tracker. Regenerate with bun run brandkit:assets.
Agent-ready repo
CI (lint, typecheck, test), the startupkit skill pre-installed, and a README the agent can follow.
Verified at every step
The handoff prompt requires typecheck, tests, migrations, and HTTP checks to pass before it moves on.
Full walkthrough in the docs. Then let the agent research the market and launch it. Reading this page with an agent? Point it at /llms.txt. Agent-specific guides: /claude-code · /cursor · /opencode · /hermes · /codex.