goribu.config.js
goribu.config.js tells Goribu what to deploy: the Worker's name, its address and any database, key-value store, or file storage it needs. Goribu turns it into Cloudflare's own Wrangler configuration (Workers configuration), so you don't write that by hand. A minimal app needs only a name and a date:
import { defineConfig } from "goribu/server";
export default defineConfig({
name: "my-app",
compatibilityDate: "2026-09-01",
});
- Plain JavaScript: Node loads this file directly, even in TypeScript apps. Don't import JSX, components or Vite aliases here.
- One place to configure: Goribu generates
goribu.wrangler.jsoncfrom this file on every run. Editgoribu.config.js, never the generated file. - Changes in development: Saving the file restarts
pnpm dev. If the new config is invalid, the error is shown and the running server keeps the old one until you fix it.
Options
| Option | What it does | Default |
|---|---|---|
name | The Worker's name on Cloudflare. Deploying with an existing name updates that Worker. | Required |
compatibilityDate | Which Workers runtime behavior your app expects, as YYYY-MM-DD. Use today's date for a new app. | Required |
compatibilityFlags | Extra Workers compatibility flags to turn on. | None |
domain | Your app's own hostname, such as app.example.com. See custom domain. | Only the workers.dev address |
workersDev | Set false to turn off the workers.dev address. See workers.dev address. | true |
database | A D1 database, or your own Postgres database. | None |
kv | A KV namespace. | None |
r2 | R2 buckets for files and uploads. | None |
responseHeaders | Default headers for responses from your Worker. See response headers. | None |
wrangler | Cloudflare features Goribu doesn't manage, such as Workers AI or service bindings, written as Wrangler fields. See Wrangler bindings. | None |
Resources
Declare a resource by name and Goribu does the rest: deploy creates it if it's missing, connects it to your Worker, and your handlers use it as req.d1, req.kv, or req.r2. You never copy resource IDs around:
import { defineConfig } from "goribu/server";
export default defineConfig({
name: "my-app",
compatibilityDate: "2026-09-01",
database: { type: "d1", name: "my-app-db" },
kv: { name: "my-app-kv" },
r2: { name: "my-app-files" },
});
- One of each: An app has at most one database (D1 or Postgres), one KV namespace and the two R2 buckets described on the R2 page.
- Names choose resources: Changing a name points the app at a different resource; the old one isn't renamed or deleted. Removing a declaration also leaves the resource and its data in place.
- Development:
pnpm devuses local copies of each resource, so you never touch production data while developing. - Raw bindings: The underlying Cloudflare bindings are
req.env.DB,req.env.KV, andreq.env.R2, for libraries that expect them. - Other Cloudflare features: Workers AI, service bindings, Vectorize and similar go in a
wranglerblock. Goribu passes them through without creating anything. - Postgres is yours: Goribu doesn't create a Postgres database. You bring one, and deploy sets up the Cloudflare connection to it. See Postgres.
- Extra settings: D1 also accepts
replication(D1) and R2 an option to locate both buckets in the EU. Addjurisdiction: "eu"tor2to do that, but decide before storing files, as changing it later points the app at new, empty buckets.
Custom domain
By default your app is live at a workers.dev address. To serve it from your own hostname, add domain and deploy connects it for you:
import { defineConfig } from "goribu/server";
export default defineConfig({
name: "my-app",
compatibilityDate: "2026-09-01",
domain: "app.example.com",
});
- What you need: The domain must be an active zone in the Cloudflare account you deploy with. Use either the domain itself (
example.com) or a subdomain (app.example.com). - What deploy does: Cloudflare creates the DNS record and TLS certificate automatically. The address can take a few minutes to start working after the first deploy.
- Preview first:
pnpm checkshows whether the domain can be connected, without changing anything. wwwand redirects: Goribu connects exactly one hostname. To set upwww, redirects, and extra hostnames in the Cloudflare dashboard.- Changing it later: Changing or removing
domaindoesn't disconnect the old hostname. Remove it in the Worker's Settings > Domains & Routes. - Files domain: If you also serve public files from R2, use a different hostname for
r2.domain, such asmedia.example.com.
workers.dev address
As mentioned above, your app gets https://<name>.<your-subdomain>.workers.dev. Once your own domain works, a second address can cause problems: sign-in callbacks, cookies and allowed-origin rules usually expect one host. Turn it off with workersDev: false:
import { defineConfig } from "goribu/server";
export default defineConfig({
name: "my-app",
compatibilityDate: "2026-09-01",
domain: "app.example.com",
workersDev: false,
});
- Wait for your domain: Keep
workers.devon until your domain is connected and loading. It's the address that works right away. - Needs a domain:
workersDev: falserequiresdomain, so the app always has an address. - Config wins: Every deploy applies this setting. To turn
workers.devback on, remove the field rather than changing it in the dashboard. - Preview URLs: Deploys always turn off Cloudflare's per-version preview URLs. Goribu deploys straight to production, so they would only be extra public copies of your app.
Environment variables
Use environment variables for API keys and settings that differ between your machine and production, such as a payment provider's test and live keys. Most apps need two files in the app root:
.env: Values for local development..env.production: Values for production. Deploy uploads them to Cloudflare as Worker secrets.
CATALOG_API_TOKEN=local-test-token
PUBLIC_API_ORIGIN=http://localhost:4000
CATALOG_API_TOKEN=live-token
PUBLIC_API_ORIGIN=https://api.example.com
Keep both files out of Git. If you work with a team, you can commit a .env.example with placeholder values instead, so others know which keys the app needs.
Reading values
Server code reads every value from req.env, both locally and in production:
import type { Handler } from "goribu/server";
type Env = { CATALOG_API_TOKEN: string };
export const GET: Handler<Record<string, unknown>, Env> = async (req, res) => {
const response = await fetch("https://catalog.example.com/items", {
headers: { Authorization: `Bearer ${req.env.CATALOG_API_TOKEN}` },
});
if (!response.ok) {
return res.status(502).json({ error: "Catalog unavailable." });
}
return res.json(await response.json());
};
export const GET = async (req, res) => {
const response = await fetch("https://catalog.example.com/items", {
headers: { Authorization: `Bearer ${req.env.CATALOG_API_TOKEN}` },
});
if (!response.ok) {
return res.status(502).json({ error: "Catalog unavailable." });
}
return res.json(await response.json());
};
Browser code only sees names that start with PUBLIC_, through import.meta.env:
// Anyone can read this value in the page's JavaScript.
const apiOrigin = import.meta.env.PUBLIC_API_ORIGIN;
// Anyone can read this value in the page's JavaScript.
const apiOrigin = import.meta.env.PUBLIC_API_ORIGIN;
- Never prefix secrets:
PUBLIC_values are copied into your JavaScript at build time, where anyone can read them. Never give an API key that prefix. - Rebuild to change them: Because they're copied at build time, changing a
PUBLIC_value means building and deploying again.
Production secrets
Deploy checks .env.production, then uploads each key as a Worker secret.
- Without the file: Deploy keeps the secrets already on the Worker. It never uploads your local
.env. So you are free to add the secrets directly in the Cloudflare dashboard and avoid the.env.productionfile. - Removing a key: Deleting a line doesn't delete the secret on Cloudflare. Secrets stay as they were and have to be removed manually.
- Preview:
pnpm checksays whether deploy will upload the file or keep existing secrets. It never prints values.
Overrides and other files
The two files above cover most apps. These come up when you need more:
.env.local: Overrides.envon your machine, such as your own API key in a team that shares a.env. Keep it out of Git.- Shell variables: A
PUBLIC_value already set in your shell, such as in CI, wins over the files. PUBLIC_values in production: A production build readsPUBLIC_values from.envtoo, unless.env.productionsets them. Set everyPUBLIC_key in.env.productionso local values don't end up in the live app..env.development: Only affectsPUBLIC_values in development. Server values in it never reachreq.env. Put them in.envor.env.local..dev.vars: Cloudflare's own local file. If it exists, even empty, the server ignores.envand.env.local, so use one or the other.goribu.config.js: Doesn't load any of these files. If it needs to read aprocess.env, set the variable in your shell.
Development-only code
import.meta.env.DEV, PROD, and MODE also work in server code. Production builds remove code that only runs when DEV is true, which makes them safe for shortcuts such as a local sign-in:
import type { Handler } from "goribu/server";
export const POST: Handler = async (req, res) => {
if (!import.meta.env.DEV) {
return res.notFound();
}
req.cookies.set("session", await createDevSession(req), {
path: "/",
httpOnly: true,
});
return res.redirect("/");
};
export const POST = async (req, res) => {
if (!import.meta.env.DEV) {
return res.notFound();
}
req.cookies.set("session", await createDevSession(req), {
path: "/",
httpOnly: true,
});
return res.redirect("/");
};
Response headers
Use responseHeaders to add the same headers to every response from your Worker, such as security headers:
import { defineConfig } from "goribu/server";
export default defineConfig({
name: "my-app",
compatibilityDate: "2026-09-01",
responseHeaders({ request, env, mode, nonce }) {
return {
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
};
},
});
- Arguments: The function receives the request,
env,mode(development or production), and the request'snoncefor a Content Security Policy. Return an object of header values; it can't be async. - Your routes win: Headers set by a route or by Goribu override these defaults.
- Prerendered pages don't get them: Static files and prerendered pages are served without running the Worker, so they don't receive these headers. Use a handler or
prerender = falsefor pages that need them. - No default security policy: Goribu doesn't set a Content Security Policy or CORS headers for you. See Allow cross-origin requests for CORS.
- Not for everything: Headers such as
Content-Type,Location, andSet-Cookiebelong to the response itself and can't be set here.
Vite settings
Build tooling such as Tailwind, import aliases, and other Vite plugins goes in vite.config.js, not goribu.config.js. Keep goribu() in the plugins list.
To import src/components/button as @/components/button, add an alias with Vite's resolve.alias:
import { fileURLToPath } from "node:url";
import { goribu } from "goribu/vite";
export default {
plugins: [goribu()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
};
- Where aliases work: In application code during development and builds. Not in
goribu.config.js, which Node loads without Vite.