Getting started
From clone to a running local app you are an admin of, in the order that works — install, secrets, database, dev server, first sign-in.
Prerequisites
- Bun, the only package manager the repo uses. The version
it expects is pinned in
package.jsonunderpackageManager. - Node.js 22 or newer. Wrangler and Vite run on Node
even when you start them through
bun run. openssl, or any other way to generate a random secret.- A Google Cloud project for OAuth credentials, when you want the Google button to work. You can start without one: see step 2.
- A Cloudflare account, only when you deploy. Local development needs no account.
Everything runs locally: the dev server executes your code in workerd, the
same runtime Cloudflare uses in production, with a local D1 database and a
local R2 bucket on disk under .wrangler/state.
1. Clone and install
Clone the ShipKit repository your GitHub account was given access to, then install dependencies:
git clone <repository-url> my-app
cd my-app
bun install
bun run cf-typegen # generates worker-configuration.d.ts, the Env types
worker-configuration.d.ts is gitignored, so every fresh clone needs
cf-typegen once for the editor and bun run typecheck to see the Worker's
types. If you plan to pull future ShipKit releases into your project, read
Upgrading before you rewrite history or rename the remote.
2. Create .dev.vars
Local secrets live in .dev.vars, which is gitignored. .dev.vars.example is
the full key list with a comment on each one; copy it:
cp .dev.vars.example .dev.vars
openssl rand -base64 32 # paste the output as BETTER_AUTH_SECRET
Four values are required before the app will serve a request that touches auth. The environment is validated on first use, and a missing or malformed key stops the request with an error naming it.
| Key | Local value |
|---|---|
APP_URL | http://localhost:3000 (already set) |
BETTER_AUTH_SECRET | The random string you generated; at least 16 characters |
GOOGLE_CLIENT_ID | Your Google OAuth client id |
GOOGLE_CLIENT_SECRET | Its secret |
If you do not have Google credentials yet, put any non-empty placeholder in
both Google keys. Validation passes, every page renders, and you can sign in
with an emailed code; only the Google button fails until real credentials are
in place. The redirect URI to register with Google is
http://localhost:3000/api/auth/callback/google. For the One Tap prompt, the
same client id also goes into googleClientId in src/config/app-config.ts.
Authentication covers the Google setup in full.
Everything else in the file is optional and can stay empty for now: GitHub
sign-in, the payment provider keys, RESEND_API_KEY. The app runs without
them; Configuration lists what each one switches on.
Environment is read when the dev server boots. Restart it after editing
.dev.vars.
3. Create the local database
bun run db:migrate:local
This applies every migration in drizzle/ to the local D1 database. There is
nothing to provision: the database is a file under .wrangler/state, created
on first use.
Optionally, fill it with demo data so the admin console and its charts are not empty:
bun run db:seed # 90 days of users, subscriptions, orders, credits
bun run db:seed --reset # removes the seed rows again, and nothing else
Seed rows all have ids starting with seed- and use fake email domains, so
they never mix with accounts you create yourself. The data is deterministic,
and dates are relative to the day you run it.
4. Start the dev server
bun run dev # http://localhost:3000
Open http://localhost:3000. The landing page, /pricing, /docs, /blog
and their Chinese versions under /zh should all render. /dashboard
redirects to /login until you sign in.
If port 3000 is taken, an old dev server is usually still running; stop it before starting a new one.
5. Sign in
Go to /login. There are up to three ways in, and all of them create the
account on first use; there is no separate sign-up.
-
Emailed code is always available. Without
RESEND_API_KEYnothing is sent; the code is printed to the dev server's terminal instead:[auth] no mail provider — code for you@example.com: 123456Enter it on the login page. Codes expire after ten minutes.
-
Google works once real credentials are in
.dev.vars. -
GitHub appears only when both
GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRETare set.
After signing in you land on /dashboard.
6. Make yourself an admin
A new account has no role. Promote yours in the local database, using the address you signed in with:
bunx wrangler d1 execute DB --local \
--command "UPDATE user SET role='admin' WHERE email='you@example.com';"
Reload the page and an Admin entry appears in the sidebar, leading to
/admin. The built-in admin role holds every permission; other roles can be
created in the console under Settings → Roles, as described in
Permissions.
With Claude Code
If you use Claude Code, the /setup skill runs these steps for you: it
installs, creates .dev.vars with a generated secret, migrates, starts the
dev server, checks that pages respond and promotes your account once you have
signed in. The steps that need a browser have their own skills:
/google-oauth for the Google credentials, /stripe or /creem for
payments, /deploy for Cloudflare. Working with AI agents
describes all of them.
Next steps
- Go through Configuration before anything else. The
app name, canonical URL, support address and legal entity in
src/config/app-config.tsare placeholders, and they reach the marketing pages, every transactional email and the Terms and Privacy pages. - Remove the modules you do not need with Deleting features, while the codebase is still untouched.
- Wire payments with Payments. Until a provider key is set, purchase buttons say "coming soon" and nothing can be charged.
- When you are ready to go live, follow Deploy.
ShipKit