appflare-cli-deploy
Configure, generate, migrate and deploy Appflare projects on Cloudflare Workers. Covers appflare.config.ts (D1 database with query limits, KV, R2, Better Auth options, scheduler queue, realtime Durabl
SKILL.mdskills/appflare-cli-deploy/SKILL.md
GitHubAppflare CLI and deployment
Commands (run from the backend package, always with Bun)
bun appflare dev # generate once
bun appflare dev --watch # regenerate on changes in scanDir
bun appflare build --no-build # generate without running `tsc --build`
bun appflare migrate --local # regenerate + drizzle-kit generate + wrangler d1 migrations apply
bun appflare migrate --remote
bun appflare migrate:custom --name add_guards # empty SQL migration (triggers, views, backfills)
bun appflare add-admin -n "Admin" -e admin@example.com -p "…" --local
Every command accepts -c <config path>.
Minimal config
import { bearer } from "better-auth/plugins";
export default {
scanDir: "./src",
outDir: "./_generated",
exclude: ["**/__fixtures__/**"], // optional, skipped by discovery
schemaDsl: { entry: "./schema.ts" },
schema: ["./_generated/schema.compiled.js", "./_generated/auth.schema.js"],
database: {
binding: "DB",
databaseName: "my-app-d1",
databaseId: "<id>",
migrationsDir: "./drizzle",
query: { defaultLimit: 100, maxLimit: 1000 }, // optional ctx.db read limits
},
kv: { binding: "CACHE", id: "<id>" }, // optional
r2: { binding: "ASSETS", bucketName: "my-app-assets" }, // optional, needed for storage
auth: {
enabled: true,
basePath: "/api/auth",
options: { emailAndPassword: { enabled: true }, plugins: [bearer()] },
clientOptions: {},
},
scheduler: { enabled: true, binding: "MY_APP_QUEUE", queue: "my-app-scheduler" },
wranglerOutPath: "./",
wranglerOverrides: {
name: "my-app-api",
main: "./_generated/server.js",
compatibility_date: "2026-04-08",
compatibility_flags: ["nodejs_compat", "nodejs_compat_populate_process_env"],
},
};
New project checklist
-
bun add appflare hono zod drizzle-orm better-auth better-auth-cloudflare @hono/standard-validator -
bun add -d drizzle-kit wrangler typescript @better-auth/cli - Create resources:
bunx wrangler d1 create …,kv namespace create,r2 bucket create,queues create - Write
appflare.config.tswith the real ids, and setwranglerOverrides.main - Write
schema.tsand at least one handler undersrc/ -
bun appflare dev, thenbun appflare migrate --local, thenbunx wrangler dev - Add
_generated/to.gitignore, and commitdrizzle/
Production deploy checklist
-
bun appflare build -
bun appflare migrate --remote(review the SQL first) -
bunx wrangler secret put BETTER_AUTH_SECRET(plus any app secrets) - Set
wranglerOverrides.vars.ALLOWED_DOMAINSto a comma-separated list of origins -
bunx wrangler deploy - Optional:
bun appflare add-admin … --remote, then open/admin
Gotchas
- The config is validated strictly, so unknown keys fail. Don't annotate the object as
AppflareConfig, because that widens the client auth plugin types. - The generated
maindefaults to./src/index.ts. Override it with./_generated/server.js. wranglerOverridesmerges objects deeply, but arrays replace generated arrays (d1_databases,queues,routes…).- Only the first
database,kvandr2entries are used by the runtime and bymigrate. - Queue bindings are generated only when a
schedulerorcronhandler exists. Realtime Durable Object bindings are on by default. migrateregenerates artifacts itself before diffing, so it can't run against a stale schema. Pass one target flag only.- When
tsconfig.jsonexists andbuildis true, generation ends withtsc --build, so type errors fail the command. Use--no-buildto isolate generation problems. - With
ALLOWED_DOMAINSunset, CORS allows every origin with credentials. add-adminneeds Better Auth'sadmin()plugin (for theroleandbannedcolumns) and a migrated database.
References
- Read references/config-reference.md for every config option with its defaults, the full list of generated
wrangler.jsonbindings, and generated file names.