Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.edgespark.dev/llms.txt

Use this file to discover all available pages before exploring further.

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:
edgespark dev
By default the app is served at http://localhost:7775. Pick a different port with --port:
edgespark dev --port 8080
dev is long-running. Stop it with Ctrl+C; stop and wipe local state in one step with --reset:
edgespark dev --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
# Google OAuth — add http://localhost:7775/api/_es/auth/callback/google
# as a redirect URI in your Google OAuth app
GOOGLE_CLIENT_ID=123-xxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx

# External APIs — use test keys locally
STRIPE_SECRET_KEY=sk_test_...
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:
⚠ GOOGLE_CLIENT_SECRET declared in runtime.ts but not in .env.local
  → Google sign-in is unavailable locally until configured
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:
[edgespark] Verification URL for alice@example.com — auto-verify via /api/_es/dev/auto-verify
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

ChangeEffect
Backend source in server/src/esbuild rebuilds the bundle; Miniflare reloads the worker in place
Frontend source in web/src/Vite HMR updates the browser without a full reload
.env.localWorker reloads with updated bindings
configs/auth-config.yamlWorker reloads with the new auth config
server/src/defs/db_schema.tsRe-pushed via drizzle-kit push after every successful rebuild — saving the file is enough. Destructive changes auto-apply locally.
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
import { defineSeed } from "@edgespark/devkit";
import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy";
import { posts } from "../src/defs/db_schema";
import type * as schema from "../src/defs/db_schema";

export default defineSeed<SqliteRemoteDatabase<typeof schema>>(async (ctx) => {
  const { user, fetch } = await ctx.auth.createUser({
    email: "alice@example.com",
    password: "correct-horse-battery-staple",
    name: "Alice",
  });

  await ctx.db.insert(posts).values([
    { title: "Hello world", authorId: user.id },
    { title: "Second post", authorId: user.id },
  ]);

  const response = await fetch("/api/posts");
  console.log(`Seeded ${user.email} with ${(await response.json()).length} posts`);
});
The ctx the CLI passes in gives you:
FieldWhat it is
ctx.dbA Drizzle client bound to the local D1 database. Type it with your own schema for full inference.
ctx.originThe dev proxy origin, e.g. http://localhost:7775.
ctx.fetch(input, init?)fetch that resolves relative paths against ctx.origin. Absolute URLs pass through. Unauthenticated — use the fetch returned from ctx.auth.createUser for authenticated requests.
ctx.auth.createUser({ email, password, name })Signs up a user through the real Better Auth endpoint, then auto-verifies them via the dev-only endpoint. Returns { user, fetch } where fetch replays the user’s session cookie on same-origin requests. If the project has email verification disabled, the auto-verify call returns 404 and the seed proceeds with the session cookie from sign-up — this is fine, not an error.
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:
.edgespark/
├── state/
│   ├── d1/          # SQLite files (survive restarts)
│   ├── r2/          # Stored objects (survive restarts)
│   ├── kv/          # KV data (sessions, auth)
│   └── secrets.json # Auto-generated dev-only internals
└── logs/
    ├── dev.log      # Current session
    └── dev.log.prev # Previous session (rotated at 5 MB)
--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):
http://localhost:7775/api/_es/auth/callback/<provider>
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

SymptomLikely causeFix
Port 7775 already in useAnother edgespark dev or process is on the portRun edgespark dev --port <n> or stop the other process.
No verification email reaches inboxLocal dev never sends real email — the auto-verify endpoint replays the URL through Better Auth insteadUse ctx.auth.createUser in dev/seed.ts, or POST to /api/_es/dev/auto-verify?email=<email> from your client-side flow. To test real email delivery, deploy to a cloud environment.
edgespark dev startup is stuck at “Running server/dev/seed.ts…”A request inside seed.ts is hangingctx.fetch and ctx.auth.createUser time out after 30 seconds and surface the error. Check the seed for a call to an unreachable external service.
Stale data after schema changesLocal destructive changes auto-applied and may have truncated tablesRun edgespark dev --reset and let your dev/seed.ts rebuild state.
Provider button missing in local login UI.env.local is missing a *_CLIENT_SECRET, or the provider is disabled in configs/auth-config.yamlAdd the secret, confirm the provider block has enabled: true. Saving either file auto-reloads the worker.
Backend changes not picked upesbuild rebuild error, or (in fullstack mode) a backend route is declared outside /api/*Read the error in the dev output; fix the build error or move the route under /api/*, then save again. Typecheck failures only print warnings — they never block reload.
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