Config
This page lists every option in void.config.ts. All fields are optional. The SDK infers sensible defaults from your code.
On this pageCheck a config field's name, type and default · See the file format and how older config is migrated · Find options for target, routing, output and more
Config file format
Use void.config.ts to configure your app. For a guided introduction, start with What is the SDK?.
Put void.config.ts at the project root. Import defineConfig from void/config.
void init, void deploy and void migrate can convert older SDK and Cloudflare JSON config files. They save backups in .void/config-migration/.
Commit void.config.ts. Also commit void.lock.json once the SDK creates it. The lock records resource IDs and migration history.
// void.config.ts
import { defineConfig } from 'void/config';
export default defineConfig({
sourceDir: 'src',
target: 'cloudflare',
auth: { providers: ['email', 'github', 'google'] },
routing: {
revalidate: { '/': 60, '*': 30 },
headers: {
'/assets/*': ['Cache-Control: public, max-age=31536000, immutable'],
'/*': ['X-Frame-Options: DENY', 'X-Content-Type-Options: nosniff'],
},
},
inference: {
bindings: { db: true, kv: false },
appType: 'spa',
outputDir: 'dist',
},
worker: {
compatibility_date: '2025-12-01',
compatibility_flags: ['nodejs_compat'],
},
cloudflare: {
name: 'my-app',
account_id: 'your-account-id',
},
});TypeScript gives you editor completion and checks. The SDK checks the config again when it runs, including values computed at runtime.
The short JSON fragments below show single fields. Place them inside defineConfig({ ... }).
Fields
sourceDir
The folder that holds the SDK source conventions, relative to the project root.
If you leave it out, the SDK uses these folders and files at the project root: pages/, routes/, middleware/, crons/, queues/, db/, auth.ts, and env.ts.
If you set it, those conventions move into that folder:
{ "sourceDir": "src" }With that config, the SDK reads src/pages, src/routes, src/db/schema.ts, src/db/migrations, src/auth.ts and src/env.ts.
Project files stay at the project root. These include void.config.ts, vite.config.ts, package.json, tsconfig.json, public/ and the local-only .env.
The SDK never scans both places. If source files exist in both during dev or build, remove one copy.
ai
ai.gateway names the Cloudflare AI Gateway for ai.provider().fetch(). It applies when your app runs on your own Cloudflare account. ai.run(), ai.stream() and ai.image() use the Workers AI binding and need no gateway. On an SDK platform, the platform picks the gateway and ignores this setting.
{ "ai": { "gateway": "my-gateway" } }See AI on your Cloudflare account.
auth
High-level Better Auth configuration.
Use auth.providers to pick the built-in auth providers the SDK enables. Include "email" for email and password sign-in.
Social providers read credentials from AUTH_<PROVIDER>_CLIENT_ID and AUTH_<PROVIDER>_CLIENT_SECRET. Store these as secrets, never in config. cognito also needs a domain, region and user pool ID. Set AUTH_COGNITO_DOMAIN, AUTH_COGNITO_REGION and AUTH_COGNITO_USER_POOL_ID, or set them in auth.ts.
{
"auth": {
"providers": ["email", "github", "google", "discord"]
}
}If you leave out auth but import auth, the SDK enables email and password only. For provider options, scopes, plugins or custom OAuth flows, use a root auth.ts with defineAuth(...).
Supported values for auth.providers:
emailappleatlassiancognitodiscorddropboxfacebookfigmagithubgitlabgooglehuggingfacekakaokicklinelinearlinkedinmicrosoftnavernotionpaybinpaypalpolarrailwayredditrobloxsalesforceslackspotifytiktoktwitchtwittervercelvkzoom
database
The database backend. Leave it out for D1/SQLite, the default. Set "pg" or "mysql" for an external database through Hyperdrive.
{
"database": "pg"
}| Value | Description |
|---|---|
| (omitted) | D1 (SQLite), fully managed by the SDK with no extra config |
"pg" | PostgreSQL via Hyperdrive; bring your own database |
"mysql" | MySQL via Hyperdrive; bring your own database |
With an external database:
- Add
DATABASE_URLto.envfor local dev - Import schema helpers from
void/schema-pgorvoid/schema-mysql void/dbconnects through Hyperdrive in production and directly in local devvoid gen modelgenerates table and column builders for your dialect
email
Configuration for the email integration.
{
"email": {
"from": "Acme <noreply@mail.acme.com>"
}
}| Field | Type | Description |
|---|---|---|
from | string | Default sender for sendEmail(): one address, optionally with a display name (Acme <noreply@mail.acme.com>) |
In dev, email.from is the default sender in the local inbox. On an SDK platform, use your project's shared sender or registered domain.
For direct Cloudflare deploys, this address sets the mail domain and default sender. Use a zone or subdomain in your account. MX records from an existing mail provider are kept.
Without email.from, the SDK skips automatic setup. It keeps any cloudflare.send_email bindings and cloudflare.addresses you wrote. Check that they are ready yourself. --require-email still blocks the deploy when setup cannot be verified. See Email setup.
head
Site-wide HTML <head> defaults for pages mode. It sets a title template, default meta tags, links, scripts and HTML/body attributes. Per-page head() exports and middleware defaults merge on top. The order of precedence is page > middleware > config.
| Field | Type | Description |
|---|---|---|
title | string | Default page title (overridden by page head()) |
titleTemplate | string | Wraps the resolved title. %s is replaced with the page title. |
meta | Array<object> | Default <meta> tags (deduped by name/property, page wins) |
link | Array<object> | Default <link> tags (concatenated: config, middleware, page) |
script | Array<object> | Default <script> tags (concatenated: config, middleware, page) |
htmlAttrs | Record<string, string> | Attributes on <html> (shallow merge, page wins) |
bodyAttrs | Record<string, string> | Attributes on <body> (shallow merge, page wins) |
{
"head": {
"titleTemplate": "%s | My Site",
"htmlAttrs": { "lang": "en" },
"meta": [
{ "charset": "utf-8" },
{ "name": "viewport", "content": "width=device-width, initial-scale=1" }
],
"link": [{ "rel": "icon", "href": "/favicon.svg" }]
}
}See Head Management for merge rules, the HeadDescriptor shape and per-page use.
output
The output mode. It sets the default rendering for pages.
| Value | Default prerender | Per-page override | Prerender timing |
|---|---|---|---|
"server" (default) | false | export const prerender = true | Deploy-time (platform ISR) |
"static" | true | export const prerender = false | Build or deploy post-build |
With "static", pages are prerendered to HTML in dist/client/. A page can opt out with export const prerender = false. Dynamic pages need getPrerenderPaths() to be prerendered.
With "server" or no value, pages render on the server for each request. A page can opt into prerendering at deploy time with export const prerender = true. It can skip server-rendered component HTML with export const ssr = false.
{ "output": "static" }remote
Use a deployed SDK project's D1, KV and R2 resources in local dev. Writes change real data. See Remote Development.
{ "remote": true }Requirements:
- You are signed in (
void account login). - The project is linked (
void project link). - Only D1 (
DB), KV (KV) and R2 (STORAGE) bindings are affected.
You can also turn on remote mode with the VOID_REMOTE=1 env variable. This leaves void.config.ts unchanged.
Requires: login to your deploy target
void auth login opens your browser to sign in to Cloudflare or your team's platform. No token goes in your project.
Now you will run the dev server against remote resources.
VOID_REMOTE=1 pnpm devExample output: the dev server starts and uses remote bindings.
sandbox
Turns on and configures Cloudflare Sandboxes. Importing from void/sandbox turns it on for you.
{
"sandbox": {
"image": "./Dockerfile.sandbox",
"platformImage": "registry.cloudflare.com/<account-id>/sandbox@sha256:<digest>",
"instanceType": "standard-1"
}
}| Field | Type | Default |
|---|---|---|
binding | string | SANDBOX |
className | string | SandboxV1 |
containerName | string | void-sandbox-v1 |
image | string | Packaged Node.js Dockerfile |
imageBuildContext | string | Directory of image |
platformImage | string | cloudflare/debian-trixie |
instanceType | string | lite |
Sizes are lite and standard-1 through standard-4. Native deploys build Dockerfiles with Docker. Registry images must be digest-pinned references from the Cloudflare managed registry. Custom images must include the matching Sandbox SDK helper. For a local Dockerfile, set platformImage when you deploy to a managed platform. See Sandboxes for image setup and runtime examples.
Sandbox needs Workers Paid and Containers access. Managed runtime tokens need Account / Containers: Edit and Account / Cloudchamber: Edit. See Sandbox deployment.
target
The runtime to deploy to. The default is "cloudflare".
| Value | Description |
|---|---|
"cloudflare" | Cloudflare Workers (default), with full platform features |
"node" | Node.js, using @hono/node-server |
"bun" | Bun, using Bun.serve() |
"deno" | Deno, using Deno.serve() |
Other targets turn off the Cloudflare binding imports: void/db, void/kv, void/auth, void/storage, void/ai, void/sandbox and void/env. Importing one causes a compile-time error. File-based routing, middleware and void/client work as usual.
WebSocket route files (*.ws.ts) only work on Cloudflare, because they compile to Durable Objects. The void/ws subpath is not blocked on other targets. Client-side connect() code can still be bundled where a browser-like WebSocket exists.
{ "target": "node" }worker
A curated set of Cloudflare Workers settings. void init pins compatibility_date here. Binding arrays such as d1_databases, kv_namespaces and r2_buckets go in cloudflare. The SDK keeps custom settings and resolved IDs there during deploys.
| Field | Type | Description |
|---|---|---|
compatibility_date | string | Cloudflare Workers compatibility date |
compatibility_flags | string[] | Cloudflare Workers compatibility flags |
vars | object | Plain-text worker variables |
limits | object | Worker resource limit overrides |
{
"worker": {
"compatibility_date": "2025-12-01",
"compatibility_flags": ["nodejs_compat"],
"vars": {
"PUBLIC_API_BASE": "https://api.example.com"
}
}
}worker.vars values must be strings. Root .env values override them in local dev only. Production builds do not load .env. They reject any worker.vars name that env.ts declares as a server key. Use void secret put for production server values.
Managed deploys include worker.vars as plain-text bindings. A remote secret with the same name wins. To use the variable instead, remove that secret and deploy again. Deleting a secret changes the running Worker at once. Plan this change for a maintenance window.
worker.limits.cpu_ms sets the CPU time limit per request, from 1 to 300000 ms. On an SDK platform, the deploy fails if the limit is above the account plan. Lower it in void.config.ts, then retry. A rollback caps the old limit at the current plan ceiling. On direct deploys, Cloudflare enforces the value. Workers Free allows up to 10 ms per request. Workers Paid allows up to 300000 ms.
{
"worker": {
"limits": {
"cpu_ms": 30000
}
}
}cloudflare
The full Cloudflare Worker config for direct Cloudflare builds and deploys. Put custom bindings, name, account_id, routes, services, vars, migrations and other Cloudflare fields here. The SDK passes them to the generated Cloudflare build config. Your settings here win over values recorded earlier in void.lock.json.
// Inside defineConfig({ ... })
cloudflare: {
name: 'my-app',
d1_databases: [{ binding: 'ANALYTICS', database_name: 'analytics', database_id: '...' }],
vars: { PUBLIC_API_BASE: 'https://api.example.com' },
},The SDK writes a generated Cloudflare config for its tools. It records new resource IDs and the append-only migration history in void.lock.json. Commit the lock when it changes. void init and void deploy migrate a root wrangler.jsonc or wrangler.json. Backups go to .void/config-migration/.
To remove a value the SDK generated earlier, delete it from the resolved object in void.lock.json. The SDK refreshes the generated Cloudflare file on the next command. Keep lasting custom settings in cloudflare in void.config.ts.
deploy.cloudflare.mode
Direct Cloudflare deploys use staged by default. The SDK checks the new Worker before it gets production traffic. Some existing Durable Object Workers cannot be staged. For those, review the pending Worker and database changes, then choose atomic:
// Inside defineConfig({ ... })
deploy: { cloudflare: { mode: 'atomic' } },This choice applies to all later void deploy runs. An atomic deploy sends traffic to the new Worker before the readiness check. A Durable Object class migration cannot be rolled back past its migration boundary. For a single deploy, use --atomic instead. deploy is an SDK setting. It is not passed to Cloudflare's Worker config.
routing
Routing and edge settings for headers, redirects, rewrites and caching.
routing.headers
Custom response headers. Keys are URL patterns. Values are arrays of "Name: value" strings. See Custom Headers for details.
{
"routing": {
"headers": {
"/assets/*": ["Cache-Control: public, max-age=31536000, immutable"],
"/*": ["X-Frame-Options: DENY", "X-Content-Type-Options: nosniff"]
}
}
}routing.redirects
URL redirects. Keys are source URL patterns. Values are destination strings, which use 302, or objects with to and an optional status (301, 302, 303, 307, 308). See Redirects for details.
{
"routing": {
"redirects": {
"/old": "/new",
"/blog/*": { "to": "/posts/:splat", "status": 301 }
}
}
}routing.rewrites
URL rewrites. Keys are source URL patterns. Values are destination paths. The destination's content is served and the browser URL stays the same. See Rewrites for details.
{
"routing": {
"rewrites": {
"/": "/en",
"/docs/*": "/en/docs/:splat"
}
}
}Destinations use the RewriteDestination shape: typed route patterns plus string. Known routes autocomplete, and dynamic paths still work. See Programmatic rewrites.
routing.fallbacks
Fallback rewrites. The shape matches rewrites. Rules only apply when no static asset or route matched, so the request would otherwise return 404. Use them for SPA shells or default-locale catch-alls that must not hide real routes. See Rewrites → Fallbacks for details.
{
"routing": {
"fallbacks": {
"/*": "/index.html"
}
}
}routing.isr
A switch for the SDK's ISR caching. With true or no value, the SDK infers caching from your revalidate and prerender settings. false turns off ISR and edge prerendering for the whole app, including per-page exports:
{
"routing": {
"isr": false
}
}Build-time HTML from output: "static" is not affected. Set isr to true to turn your cache policies back on.
routing.revalidate
The ISR revalidation TTL in seconds, for the whole app or per path. See Revalidation for details.
{
"routing": {
"revalidate": {
"/": 60,
"/docs/*": 31536000,
"*": 30
}
}
}routing.revalidateQueryAllowlist
Query parameters to keep in ISR cache keys. Keys are source URL patterns. Values are the parameter names to keep. Listed parameters join the cache key for every request to a matching path. All other parameters are left out. Without this field, cache keys drop every query parameter. This stops the cache from growing without limit.
{
"routing": {
"revalidateQueryAllowlist": {
"/search": ["q"],
"/products/*": ["variant", "currency"],
"*": []
}
}
}routing.prerender
Paths to prerender as static HTML at deploy time. Each path must start with /. See Edge Prerendering for details.
{
"routing": {
"prerender": ["/", "/about", "/pricing"]
}
}routing.notFound
Sets how the asset layer answers a request that matched no asset and no Worker route. Use "single-page-application", "404-page" or "none". Without a value, the SDK infers it. See Static Assets.
{
"routing": {
"notFound": "404-page"
}
}Use "404-page" for a static site with a 404.html. Use "single-page-application" for a client router. Use "none" to keep the Worker's own 404. API and middleware routing do not change.
SvelteKit, Nuxt, Analog and Astro use their own error pages and ignore this setting. For direct Cloudflare deploys with TanStack Start, React Router or vinext, set a full asset policy in your framework's Cloudflare config. See Static Assets.
inference
Settings for build-time inference. They control how the SDK detects your app type, bindings and build.
inference.bindings
Controls inferred Cloudflare bindings. Without it, the SDK infers bindings by scanning your source files. Values you set override those bindings. Bindings you leave out are still inferred. Auth and committed migrations still need a database, even when db is false.
Each binding takes true (default name), false (off) or a string (custom name):
{ "inference": { "bindings": { "db": true, "kv": false, "storage": "MY_BUCKET", "ai": "MY_AI" } } }| Key | Default Binding | Type | Custom Name |
|---|---|---|---|
db | DB | D1Database | "db": "MY_DB" |
kv | KV | KVNamespace | "kv": "MY_KV" |
storage | STORAGE | R2Bucket | "storage": "MY_BUCKET" |
ai | AI | Ai | "ai": "MY_AI" |
email | — | — | Boolean only |
Custom names work with void/db, void/kv and void/storage in dev and deploys. Custom AI names also work with void/ai on direct Cloudflare deploys. Managed Workers AI uses the platform's proxy.
email is an on/off switch. true turns on email and the void dev inbox without an import from void/email. false turns them off. On an SDK platform, sending uses the platform proxy. Direct Cloudflare setup records a SEND_EMAIL binding in void.lock.json. See email.from.
inference.build
Overrides the build command. Use it for frameworks with their own CLI, such as Nuxt or Astro, or for static apps with custom build scripts. Without it, the CLI uses the default command for the detected app type. It applies to every app type, including "void", where the default is vite build.
{ "inference": { "build": "nuxt build --preset cloudflare-module" } }The command runs in a shell from the project root. The project's node_modules/.bin is on PATH, so bare binaries and && chains work:
{ "inference": { "build": "vitepress build && vite build" } }This chain lets a static site generator and an SDK backend ship together. See Adding a backend to a static site.
inference.scanDirs
Sets which folders are scanned for binding inference. Paths are relative to the project root. The list replaces the default folders for the current mode.
{ "inference": { "scanDirs": ["src", "lib", "workers"] } }inference.appType
The app type. Without it, the SDK detects it on the first deploy. See Supported App Types.
"void": a Worker plus assets, including API routes, SSR and jobs"framework": a meta-framework app such as TanStack Start, React Router, SvelteKit, Nuxt or Astro"static": fully rendered HTML from VitePress, plain HTML or other tools. Unknown paths return404.htmlor a 404 response."spa": static output with an SPA fallback. Paths that are not files returnindex.htmlwith status200, so client-side routing just works.
inference.outputDir
The output folder for static deploys, relative to the project root. It only applies when inference.appType is "spa" or "static". The default is "dist".