Skip to main content
edgespark dev runs your full-stack EdgeSpark project on your machine. The CLI launches a local Miniflare process with both the user-worker and the platform’s sidecar bound to local D1 and R2, starts the Vite dev server for the frontend, and proxies both under one origin with hot reload. The goal is a zero-config loop for the common case — email/password auth, database, and storage all work immediately — and a short path to OAuth and external APIs when you need them.

Start the dev server

From your project root:
By default the app is served at http://localhost:7775. Pick a different port with --port:
dev is long-running. Stop it with Ctrl+C; stop and wipe local state in one step with --reset:

What runs locally

When edgespark dev starts, you get:
  • Local D1 — a SQLite file managed by Miniflare. The schema in server/src/defs/db_schema.ts is pushed via drizzle-kit push before the workers start, and re-pushed after every save; destructive changes (drop column, NOT NULL without a default) auto-apply and may truncate tables.
  • Local R2 — a filesystem-backed bucket. Presigned URL uploads work through a local S3 proxy.
  • Local auth — email/password ready out of the box. Sessions use a local KV namespace.
  • Reverse proxy on one porthttp://localhost:7775/api/* hits the worker; every other path hits the Vite dev server. Same origin, so cookies and CORS are not a problem.
  • Hot reload — backend source changes trigger an esbuild rebuild and reload the worker without a process restart. Frontend uses Vite HMR.
  • Dev-mode email — no email is sent. Verification URLs are stored in a dev-only row keyed by email, and /api/_es/dev/auto-verify replays them through Better Auth so signup proceeds without a mailbox round-trip. To test real email delivery, deploy to a cloud environment.
Zero configuration is needed for any of this. Signup, session cookies, database queries, storage uploads, and the dev-only auto-verify helper all work against local bindings.

Local values with .env.local

.env.local holds the values that differ between your machine and a deployed environment — OAuth client secrets, external API keys, and anything else declared in server/src/defs/runtime.ts. The file is local to your machine, must stay gitignored, and is read at each edgespark dev start. On reload, changing .env.local is enough — the CLI re-reads values without you restarting the process. Example:
.env.local
Only keys declared in server/src/defs/runtime.ts (VarKey / SecretKey unions) or referenced by configs/auth-config.yaml are loaded into the local worker — undeclared lines in .env.local are silently ignored. Add the key to runtime.ts first.
.env.local values live only on your machine. Never commit it, and do not reuse production keys locally. For deployed environments use edgespark var set and edgespark secret set instead — see manage vars and manage secrets.
Keys you declare in server/src/defs/runtime.ts but leave unset in .env.local show a startup warning and become null or undefined at runtime:
OAuth providers enabled in configs/auth-config.yaml that reference a missing secret remain unavailable locally — the rest of the app boots normally.

Dev-mode email and auto-verify

In edgespark dev, no email leaves your machine — the local sidecar always intercepts Better Auth’s email hooks regardless of any *_API_KEY you set. Each verification URL Better Auth generates is written to a dev-owned row keyed by email, and the dev-only endpoint /api/_es/dev/auto-verify replays it through the auth handler to issue a session. You normally do not call this endpoint by hand. ctx.auth.createUser (see seed data) already calls it for you, and your app’s signup flow can follow the same pattern in dev. The fact a URL was issued is logged so you can spot-check it:
If you need to test real email delivery (Resend, Postmark, etc.), deploy to a cloud environment. There is no .env.local switch that turns the dev override off.

Hot reload behavior

Local data is ephemeral. Destructive schema changes — dropping a column, adding NOT NULL without a default — auto-apply locally and may truncate tables. Use seed data so you can rebuild a known state on demand.

Database and storage in dev

Both client.db and client.storage work against local bindings with no code changes:
  • Schema sync — the CLI runs drizzle-kit push against local D1 before the workers accept traffic, and again after every backend rebuild, applying server/src/defs/db_schema.ts directly. No migration files are needed locally — the production deploy still runs edgespark db migrate.
  • D1 queries — identical API to production; SQL validation runs in the sidecar the same way.
  • R2 uploads and downloads — stored under .edgespark/state/r2/.
  • Presigned URLs — the sidecar signs URLs against a local S3 proxy. Uploading to a presigned URL writes to the local bucket.
See use the database and use storage for the SDK patterns; they do not change between local and deployed modes.

Seed data with server/dev/seed.ts

Templates scaffolded by edgespark init include @edgespark/devkit in server/package.json. Create server/dev/seed.ts to populate your local database on every dev start:
server/dev/seed.ts
The ctx the CLI passes in gives you: The seed runs once when the dev server starts. It re-runs on the next edgespark dev if the file has changed or you pass --reset; edits during an already-running session take effect on the next start. The change-detection sentinel is a SHA-256 of server/dev/seed.ts itself — it does not cover any helper modules seed.ts imports. If you split seed logic across files and edit only a helper, touch seed.ts (or pass --reset) to force a re-run.
dev/seed.ts runs only in edgespark dev. It is never bundled into a deployed build. Use it for reproducible local state, not for production data loading.

Local state and logs

Local persistence lives under your project:
--reset deletes the entire .edgespark/state/ directory before starting. Use it whenever you want to rerun migrations and seed data against an empty database. Tail .edgespark/logs/dev.log in a second terminal if you want a persistent log alongside the console output. Both .edgespark/ and .env.local must stay gitignored.

OAuth providers locally

To test a real OAuth sign-in against local dev, register a second OAuth app (or add a second callback URL on your existing one):
Fill in the provider’s client ID and client secret in .env.local using the exact key names from add social login (GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET, etc.). Save the file — the worker auto-reloads with the new bindings. No platform-side edgespark var set or edgespark secret set is needed for local use; those commands target deployed environments.

Troubleshooting

For platform-wide naming and quota limits, see platform limits.

See also

Development workflow

The edit → CLI → deploy loop for schema, storage, vars, secrets, and types.

Add social login

Register OAuth apps and wire providers for both local dev and deployed environments.
Last modified on April 30, 2026