Configuration
Use farm.config.ts as the single project control plane for source paths, integrations, docs, KV storage, database clients, deployment, and framework behavior.
Define config
import { defineConfig } from "@farm.js/core";export default defineConfig({ deploy: { target: "vercel", }, docs: { entry: "/docs", }, md: { expose: ["/", "/pricing"], cache: 60, }, mdx: { components: "./src/markdown-components.tsx", },});
srcDir defaults to "src". Set it only when the application source lives somewhere else.
defineConfig is the canonical Farm helper. defineFarmConfig remains available as a deprecated exact alias for existing applications.
Docs config
Configure the docs runtime directly in farm.config.ts:
import { defineConfig } from "@farm.js/core";export default defineConfig({ docs: { entry: "/docs", metadata: { description: "Product guides and API reference.", }, nav: { title: "Acme Docs", }, search: { provider: "simple", enabled: true, }, pageActions: { copyMarkdown: { enabled: true, }, }, llmsTxt: true, sitemap: true, robots: true, },});
This single property enables human-readable pages, markdown mirrors, search metadata, and
agent-readable docs routes. A separate docs.config.* or docs.json file is optional and intended
only for large serializable configurations; inline values always take priority. See
Docs Engine for content layout, generated routes, and API overrides.
The same search option configures the search provider and the docs interface. When it is enabled,
Farm mounts the shared Omni React search from @farming-labs/theme. The sidebar control and
Cmd+K on macOS or Ctrl+K elsewhere open the same search interface used by the other Farming Labs
framework adapters. Set search: false or search.enabled: false to remove the control, client
mount, and shortcut together.
Important options
| Option | Use it for |
|---|---|
| extends | Composing local or package Farm layers with project-first overrides. |
| srcDir | Changing the app source folder from the default src. |
| integrations | Registering built-in or custom integrations. |
| auth | Enabling Farm's built-in email/password auth, sessions, helpers, and hooks. |
| storage | Configuring KV drivers/mounts and, in the current beta, an integration DB client. |
| migrations | Running one-shot schema/provider commands with farm migrate. |
| cron | Mapping portable UTC schedules to ordinary GET API routes. |
| i18n | Configuring locale routes, detection, message catalogs, typing, and direction. |
| docs | Serving the built-in docs runtime and docs API. |
| md | Restricting or disabling automatic markdown mirrors like /pricing.md. |
| mdx | Rendering page.md and page.mdx app routes, plus MDX components. |
| deploy | Selecting a target, preset, and output directory. |
| deploymentId | Detecting stale browser requests during rolling deployments. |
| routeRules | Applying rendering, cache, redirect, CORS, and header behavior to route patterns. |
| security | Applying an app-wide CSP with an enforcing or report-only response header. |
| serverActions | Restricting trusted action origins and request body size. |
| images | Configuring responsive widths, remote allowlists, formats, and optimizer limits. |
| performance | Budgeting image and font preload hints without changing the rendered resources. |
| openapi | Publishing API reference docs. |
Images
Farm optimizes local and allowlisted remote images through the same runtime on development and production deployments.
import { defineConfig } from "@farm.js/core";export default defineConfig({ images: { remotePatterns: [ { protocol: "https", hostname: "images.example.com", pathname: "/catalog/**", }, ], qualities: [75, 90], formats: ["image/avif", "image/webp"], maximumResponseBody: "10mb", },});
Remote sources are denied by default. See Images for static imports, responsive layouts, provider selection, caching, and security behavior.
Preload budgets
Farm keeps one image preload—the explicitly high-priority hint first—and two font preloads by
default. Lower-priority hints above those budgets are removed from buffered HTML and Link response
headers, while the actual image and font elements remain unchanged and load normally. Route scripts,
stylesheets, and module preloads are not removed.
import { defineConfig } from "@farm.js/core";export default defineConfig({ performance: { preload: { mode: "enforce", maxImages: 1, maxFonts: 2, }, },});
Farm prints one actionable warning when a route exceeds a budget. Use mode: "warn" to audit an
existing application without removing any hints. Mark the likely LCP image with preload (or
fetchPriority="high") and set preload: false on font declarations that are not needed above the
fold.
Layers
Use extends to compose ordinary Farm-shaped directories and packages. Entries apply from left to right, and project files and configuration have final priority.
export default defineConfig({ extends: ["@company/farm-base", "./layers/commerce"],});
A layer may contain an optional plain farm.config.ts plus its own src/app, components, middleware, APIs, and programmatic routes. It does not use a separate layer registration function. See Layers for package structure, merge rules, aliases, generated types, and override behavior.
Content Security Policy
Configure an app-wide Content Security Policy under security.csp. Farm applies it to pages, API responses, and pre-rendered output through the same response-header pipeline in development and production.
import { defineConfig } from "@farm.js/core";export default defineConfig({ security: { csp: { directives: { defaultSrc: ["'self'"], baseUri: ["'self'"], objectSrc: ["'none'"], frameAncestors: ["'none'"], formAction: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "blob:"], fontSrc: ["'self'", "data:"], connectSrc: ["'self'", "https:", "wss:"], }, }, },});
Directive names may use camelCase or kebab-case. Farm rejects duplicate normalized names, newlines, and directive values containing semicolons so configuration cannot accidentally create a second policy directive.
Use report-only mode while auditing an existing application:
security: { csp: { reportOnly: true, directives: { defaultSrc: ["'self'"], reportTo: ["csp-endpoint"], }, },}
You can also pass an already serialized policy as csp: "default-src 'self'; object-src 'none'". The longer contentSecurityPolicy config name is intentionally unsupported; use csp.
Farm currently emits small inline hydration and route-state bootstraps, so the compatible example allows inline scripts and styles. A stricter policy must supply correct hashes or renderer-generated nonces for every trusted inline bootstrap. Start with reportOnly, inspect violations, and enforce only after the deployed HTML and every third-party integration satisfy the policy.
Server HTTP policy
Farm applies one request-body limit to API routes, integrations, workflow HTTP triggers, and uploads handled by those surfaces. The default is 10 MB.
import { defineConfig } from "@farm.js/core";export default defineConfig({ server: { bodySizeLimit: "10mb", trustProxy: false, headersTimeout: "60s", requestTimeout: "5m", keepAliveTimeout: "5s", gracefulShutdownTimeout: "30s", health: { livenessPath: "/_farm/health/live", readinessPath: "/_farm/health/ready", }, },});
Farm checks Content-Length when present and also counts the received bytes, so chunked requests cannot bypass bodySizeLimit. Oversized requests receive 413 Payload Too Large before the route or integration handler runs. Server Actions keep their separate, tighter serverActions.bodySizeLimit setting.
trustProxy defaults to false. Enable it only when the app is behind a trusted reverse proxy that removes client-supplied forwarding headers and writes its own X-Forwarded-For value. A directly exposed Farm server must leave it disabled so a client cannot spoof the address used by rate limits, logs, or access policy.
Workflow runner secrets are accepted only through Authorization: Bearer <secret> or X-Farm-Workflow-Secret. Farm does not accept secrets in query strings because URLs are commonly retained in logs, browser history, and referrer data.
The long-running Node adapter applies headersTimeout, requestTimeout, and keepAliveTimeout to its HTTP server. headersTimeout limits how long a client can occupy a connection while sending headers, and requestTimeout limits receipt of the complete request. These are transport timeouts, not limits on route-handler or database execution. Durations accept milliseconds or strings such as "15s", "2m", and "1h".
On SIGTERM or SIGINT, Node output immediately fails readiness, stops accepting connections, drains active responses and streams through Nitro, and then runs Farm integration and plugin cleanup. gracefulShutdownTimeout is the maximum drain period before remaining connections are forced closed. The process starts plugin and integration runtime state before it begins listening, so a successful readiness response means startup completed.
Farm exposes two non-cacheable production health handlers by default:
GET /_farm/health/livereports whether the process is alive. It stays successful while the process drains.GET /_farm/health/readyreports whether the instance should receive traffic. It returns503before startup completes and after shutdown begins.
Customize both paths through server.health, or set health: false when an adapter supplies its own probes. Long-running Node output guarantees the shutdown sequence. Request-driven serverless and edge environments may not expose a reliable process shutdown event, so cleanup there remains platform-specific and must not be required for data correctness.
Server action security
Server actions are same-origin application RPC endpoints. Farm rejects cross-origin action requests by default and limits the encoded request body to 1 MB.
import { defineConfig } from "@farm.js/core";export default defineConfig({ experimental: { serverComponents: true, serverActions: true, }, serverActions: { allowedOrigins: [], bodySizeLimit: "1mb", },});
allowedOrigins adds trusted origins when a reverse proxy or multi-origin deployment makes the browser origin differ from the server request origin. Entries can be exact origins, hosts, or leftmost-subdomain wildcards:
serverActions: { allowedOrigins: [ "https://app.example.com", "proxy.internal:8443", "https://*.preview.example.com", ],}
Do not use allowedOrigins as a replacement for CORS or as a public API allowlist. Browser action requests must provide a matching Origin or Referer; Farm accepts Sec-Fetch-Site: same-origin when both are unavailable. Explicitly configured origins can cross a trusted proxy boundary.
bodySizeLimit accepts bytes or strings such as "500kb", "2mb", and "2MiB". Farm checks Content-Length when present and also counts streamed bytes, so chunked requests cannot bypass the limit.
Rejected requests use generic, non-cacheable responses: 403 for origin failures, 413 for oversized bodies, and 415 for unsupported content types. Detailed parsing or execution errors stay in server logs.
Next-style route exports
Farm route modules can expose compact rendering options directly on the page when the behavior belongs to that route.
export const dynamic = "force-static";export const revalidate = 60;export default async function BlogPage() { return <main>...</main>;}
Route rules
Use routeRules when behavior belongs to a URL pattern instead of one page file. Rules are normalized into Farm redirects/headers and passed to Nitro route rules for production adapters.
import { defineConfig } from "@farm.js/core";export default defineConfig({ routeRules: { "/": { prerender: true }, "/blog/**": { swr: 3600 }, "/admin/**": { render: "dynamic" }, "/api/**": { cors: true }, "/assets/**": { headers: { "Cache-Control": "public, max-age=31536000, immutable", }, }, "/old": { redirect: "/new" }, },});
render: "static" maps to prerendering. render: "dynamic" forces a dynamic response. swr and isr accept true or a TTL in seconds. cors: true applies permissive API CORS headers; pass an object when you need a specific origin, methods, or headers.
Rules can also provide runtime, regions, and maxDuration defaults. File pages, API routes, and layouts can override them with named exports. See Route Runtime for inheritance and deployment behavior.
Prefer route-level exports when one page owns the behavior. Prefer routeRules for broad groups, deployment-facing cache policy, API CORS, static asset headers, and legacy redirects.
Minimal project layout
Farm keeps the base project small:
farm.config.tssrc/ app/ page.tsx
Add optional files only when the app needs them:
docs.config.ts # Optional split for a large docs configurationdocs.json # Optional serializable docs configurationsrc/app/api/**/route.tssrc/app/**/middleware.tssrc/lib/integrations.ts
Cron in config
Cron entries keep timing policy in farm.config.ts while application work stays in an ordinary API route.
export default defineConfig({ cron: { dailyCleanup: { schedule: "0 2 * * *", path: "/api/maintenance/cleanup", }, },});
See Cron for route protection, local commands, UTC syntax, deployment behavior, and reliability boundaries.
Integrations in config
import { defineConfig } from "@farm.js/core";import { stripe } from "@farm.js/integrations/stripe";import { supabase } from "@farm.js/integrations/supabase";export default defineConfig({ integrations: { billing: stripe({ secretKey: process.env.STRIPE_SECRET_KEY, }), auth: supabase({ url: process.env.SUPABASE_URL, anonKey: process.env.SUPABASE_ANON_KEY, }), },});
The keys become typed namespaces. billing becomes api.billing, and auth becomes api.auth.
One-shot migrations
Use migrations.commands when the app needs a predictable command before build or deploy. This keeps schema setup close to the database and integration config without turning the framework into a migration engine.
import { defineConfig } from "@farm.js/core";export default defineConfig({ migrations: { commands: [ "pnpm drizzle-kit migrate", { name: "integration schema", command: "farm generate --orm sqlite --output ./farm-integrations.sql", env: { FARM_SCHEMA: "integrations", }, }, ], },});
Run them with:
farm migrate
Each command runs from the project root unless it sets cwd. Commands run in order and the CLI stops on the first failure.
Deployment config
export default defineConfig({ deploy: { target: "vercel", outputDir: ".vercel/output", },});
deploy.target selects the deployment provider. Farm resolves that to the matching Nitro preset and output shape unless you override it.
Deployment identity
Farm assigns one deployment ID to the server and browser output so requests from an older open page can be detected safely.
export default defineConfig({ deploymentId: process.env.RELEASE_ID,});
When deploymentId is omitted, Farm checks FARM_DEPLOYMENT_ID, VERCEL_GIT_COMMIT_SHA, and CF_PAGES_COMMIT_SHA, then calls generateBuildId for production builds. Development uses "development".
For a custom build ID, return one stable value for every instance of the same release:
export default defineConfig({ generateBuildId: async () => process.env.GIT_SHA || `build-${Date.now()}`,});
Prefer a CI release or commit identifier when a deployment runs on multiple servers. See Deployment for mismatch behavior.
Production notes
- Keep secrets in environment variables, not committed config.
- Use
storage.driverandstorage.mountsfor KV data read throughgetStorage(). - Use a raw object at
storage.clientonly when schema-backed integrations need a database client; see Database and ORM Clients. - Use
migrations.commandsfor schema setup that should be explicit in CI. - Use
docs.entrywhen the docs runtime should be mounted automatically. - Prefer route-level exports such as
dynamic,revalidate, andpprwhen behavior belongs to one page. - Prefer
routeRulesfor broad URL patterns and platform-level cache/header behavior. - Keep
serverActions.allowedOriginsempty unless the deployment has a known proxy-origin mismatch. - Give every rolling release one stable
deploymentId; do not generate a different value per server instance. - Treat every server action as a public endpoint and authorize the current user inside the action or middleware.
- Keep
farm.config.tsas the single control plane instead of spreading framework behavior across many root files.