Farm.js is a full-stack application framework in active development. Treat the checked-out repository as the source of truth; do not infer current APIs from an older beta or another framework:
- Docs:
README.md,docs/src/app/docs/** - Core package:
packages/farm/src/** - CLI:
packages/cli/src/** - Integration package:
packages/farm-integrations/src/** - Examples:
examples/** - Current version: read
packages/farm/package.jsonand keep all@farm.js/*packages aligned
Use rg first when locating files. Read the closest example before adding new code.
Default Workflow
- Inspect
package.json, the lockfile,farm.config.ts, andsrc/app:- App config:
farm.config.ts - Pages/layouts:
src/app/** - Integration setup:
src/lib/integrations.ts - Typed client export:
src/lib/api.ts - Generated project types:
src/farm.d.ts
- App config:
- Identify the selected renderer and deployment target before generating component code.
- Preserve the app's package manager, source layout, config, renderer, and deployment contract.
- Match existing patterns in the nearest current example.
- For provider integrations, verify both static types and the target-specific production build.
- If touching Prisma, run
prisma generateimmediately beforetscorfarm build. - Use
farm doctororfarm explain <path>instead of guessing resolved runtime behavior.
App Structure
Farm.js uses an app directory:
src/app/
layout.tsx # Root or nested layout
page.tsx # Route page
loading.tsx # Optional loading boundary
error.tsx # Optional error boundary
users/[id]/page.tsx # Dynamic route
Core imports:
import type { PageProps, LayoutProps } from "@farm.js/core";
import { Link, createApiClients } from "@farm.js/core/client";
Use "use client" when a component uses React hooks, browser APIs, or client-only integration calls.
Create, Upgrade, and Renderers
Scaffold with pnpm create, not pnpm add:
pnpm create @farm.js/app my-app --template basic --typescript
pnpm create @farm.js/app my-app --template basic --renderer vue --typescript
pnpm create @farm.js/app --list-templates
React is the default renderer. The Basic and Better Auth starters support react, preact,
solid, vue, and svelte; other integration starters may remain React-specific. Select a
non-default renderer through its adapter in farm.config.ts:
import { defineConfig } from "@farm.js/core";
import { vue } from "@farm.js/vue";
export default defineConfig({ renderer: vue() });
React and Preact routes use .tsx/.jsx, Solid uses .tsx/.jsx, Vue uses .vue, and Svelte
uses .svelte. Do not mix renderer component formats in one route tree. Use renderer-native client
bindings from @farm.js/solid/bindings, @farm.js/vue/bindings, or
@farm.js/svelte/bindings; keep APIs, middleware, secrets, storage, and deployment renderer-neutral.
Upgrade every published Farm package together:
farm upgrade --beta
farm upgrade --latest
farm upgrade --beta --dry-run
Preserve local workspace:, file:, link:, portal:, and catalog: dependencies.
Config Spec
Farm apps use defineConfig. The older defineFarmConfig name remains available as a deprecated exact alias:
import { defineConfig } from "@farm.js/core";
import { appIntegrations } from "./src/lib/integrations.ts";
export default defineConfig({
experimental: {
serverComponents: true,
},
integrations: appIntegrations,
});
Common config fields:
extends: compose local or package layers with project-first overridessrcDir: app source directory; omit it to use the defaultsrcrenderer: React by default or a Preact, Solid, Vue, or Svelte adapterapi: typed browser API origin and base pathexperimental.serverComponents: enables server component behaviorintegrations: provider integrations objectauth: built-in email/password auth and sessionsmcp: authenticated MCP transport for opted-in typed API routesagent: agent readiness: anllmsTxtindex,sitemap, and read-onlymcpserver of static pages and schema.orgjsonLd(on by default), AIcrawlersrobots.txt rules (opt-in)theme: light, dark, and system behaviorstorage.mounts: named storage instancesmigrations: one-shot schema and provider commandsi18n: locale routes, detection signals, typed ICU catalogs, formatting, and RTLcron: named portable UTC schedules mapped to GET API routesdocs,md,mdx: docs runtime, markdown mirrors, and MDX componentsdeploy: first-class target, Nitro preset, and outputrouteRules: rendering, cache, redirects, CORS, and headers by route patternsecurity: application CSP policyserverActions: trusted origins and action body limitsimages,performance: image policy and preload budgetsopenapi: generated API referenceplugins: Farm pluginsredirects(),headers(),rewrites(): route behaviorvite: underlying Vite configsuppressLintOnLink: relax generated route typing forLink href
Read docs/src/app/docs/configuration/page.md before adding an unfamiliar option.
Routing Spec
src/app/page.tsxmaps to/src/app/about/page.tsxmaps to/aboutsrc/app/users/[id]/page.tsxmaps to/users/:idsrc/app/docs/[...slug]/page.tsxmaps to catch-all docs paths[[...slug]]is optional catch-all; route groups do not appear in URLs- A route group's
layout,loading, anderrorfiles apply only to the pages inside that group, nested inside the parent layouts - Named slots and intercepted routes are supported
- Farm writes typed
Link hrefdeclarations intosrc/farm.d.ts - Typed
hrefsupports query strings and hashes, for example/users/123?tab=profile - File boundaries include
loading,error, andnot-found - Metadata route files
sitemap.ts,robots.ts,manifest.ts,llms.ts, andllms-full.tsserve/sitemap.xml,/robots.txt,/manifest.webmanifest,/llms.txt, and/llms-full.txt - Route exports or route rules select dynamic, static, ISR, PPR, runtime, and cache behavior
Page shape:
import type { PageProps } from "@farm.js/core";
export default function UserPage({ params, searchParams }: PageProps) {
return <div>User: {params?.id}</div>;
}
Layout shape:
import type { LayoutProps } from "@farm.js/core";
export default function RootLayout({ children }: LayoutProps) {
return <main>{children}</main>;
}
Farm.js owns the <html>, <head>, and <body> document shell. Root layouts return application
UI or a fragment; metadata exports and framework configuration control the managed document.
Internationalization Spec
Configure i18n directly in farm.config.ts; do not add a second i18n config file:
export default defineConfig({
i18n: {
locales: ["en", "fr", "ar"],
defaultLocale: "en",
fallbackLocale: "en",
routing: "prefix-except-default",
strict: true,
},
});
Put nested JSON catalogs at src/messages/<locale>.json. Farm writes their declarations into
src/farm.d.ts, so message keys and ICU variables are checked by TypeScript.
Server modules import from @farm.js/core/i18n/server:
import { getLocale, t } from "@farm.js/core/i18n/server";
const locale = getLocale();
const label = t("cart.items", { count: 3 });
Client components import from @farm.js/core/i18n/client:
"use client";
import { useLocale, useTranslations } from "@farm.js/core/i18n/client";
const { locale, locales, setLocale } = useLocale();
const t = useTranslations();
Explicit locale URLs win over the locale cookie and weighted Accept-Language. Farm preserves the
active locale in Link, redirects, middleware matching, page-data navigation, cache keys, metadata
image URLs, and generated lang, dir, and hreflang markup. API routes stay available at their
normal /api/** paths and can call the same server APIs. Read
docs/src/app/docs/internationalization/page.md and examples/i18n before changing this feature.
Caller header defaults
createApiClients, createAPIClient, createIntegrations, and integration-only factories accept
headers as a string-valued object or a sync/async function returning one. Resolve once per call
before cache lookup/dispatch; retries reuse the snapshot. Per-call headers stay objects and
override defaults case-insensitively. Integration operation headers override instance defaults;
integrations.headers and separate integration server header options replace shared defaults.
Resolvers are not a server-only boundary: keep secrets and provider imports out of shared caller
modules. See docs/src/app/docs/api-client/page.md#header-defaults and the integrations guide.
Typed APIs and Server Data
Caller instances support onRequest, onResponse, and onError observers. Shared hooks are
invoked before per-call hooks; their return values are ignored and failures are reported without
breaking calls. Attempt hooks skip cache hits and deduplicated transports; final error hooks run
per logical call, including background failures. Integration/server overrides replace the
corresponding default hook, not per-call composition. Shared data is unknown; per-call data stays
typed. Do not use non-awaited observers for required work or import server secrets into them.
Caller fetch options replace HTTP only, including integration server fallback. Local route and
registered integration dispatch remain local. Forward the supplied RequestInit/signal and return
a Web Response. Custom-transport route caches are instance-private even with shared scope;
reflect hidden identity changes in header defaults or create a new caller instance.
Caller instances accept timeoutMs (0 disables it); individual calls accept signal and a
deadline override. The budget includes header resolution, dispatch, decoding, and retry waits.
Cancelled app routes return aborted/timeout errors without retries or optimistic commits;
cancellable requests do not share in-flight work. Server dispatch combines the incoming and
per-call signals. Handlers must cooperate: abort cannot undo external side effects. Streams/raw
responses end the deadline at handoff, while the caller signal still reaches HTTP body reads.
API routes live under src/app/api/**/route.ts. Prefer createEndpoint from
@farm.js/core/api when input validation and generated caller types matter; plain HTTP method
exports remain supported. Endpoint input accepts Zod or standard-schema validators for body, query,
and headers. Farm also supports typed HTTP QUERY, multipart uploads, and streamed JSON results.
Use one shared src/lib/api.ts with createApiClients<APIRouter>({ routes: apiRoutes }) from
@farm.js/core/client, returning { api, apiClient }. Import the generated schema-free manifest,
not server endpoint modules. api dispatches locally during Farm server requests; apiClient
uses HTTP. Both call the same route definition and return { data, error, key }. Local calls run
endpoint validation/middleware, not outer HTTP/plugin lifecycle middleware; put shared
authorization in endpoint middleware. Server caches are request-local and there is no HTTP
fallback. The older createAPIClient and createServerAPIClient factories remain supported.
Calls such as apiClient.products.get(...) resolve to { data, error, key } results for HTTP failures and support caching,
invalidation, retries, callbacks, optimistic updates, useMutation, and useFetcher.
If the app also uses integrations, add the registry type to the same factory instead of creating a second pair:
import { createApiClients } from "@farm.js/core/client";
import { apiRoutes, type APIRouter } from "./api.generated";
import type { AppIntegrations } from "./integrations";
export const { api, apiClient } = createApiClients<APIRouter, AppIntegrations>({
routes: apiRoutes,
});
Integration calls use api.integrations.billing / apiClient.integrations.billing. They retain
their existing { data, error } results and server-side HTTP fallback; the app-route cache and
no-fallback guarantees above do not apply to integrations. Keep the registry value and provider
SDKs server-only; import only AppIntegrations into the shared module. Shared integration
defaults belong under the factory's integrations option and must be browser-safe.
createIntegrations<AppIntegrations>() remains supported and is not deprecated. No migration is
required. Use it for integration-only callers or deliberately separate modules; in the latter
case, set integrations: false on the app-route createApiClients() setup. This only disables
that caller namespace, not the configured integration or its HTTP routes.
The separate factory exposes api.billing / apiClient.billing directly. Do not mix
its call paths with the paired route factory's reserved .integrations namespace.
Keep createIntegrations() when explicit source maps or separate setup-level server options
(request, forwardHeaders, or server defaults) are needed. Shared baseURL, headers,
credentials, and data defaults alone do not require it; the paired factory accepts those
under integrations and supports server integration overrides per call. Keep request-bound
callers inside server request scope. A separate server-options argument does not make an import
server-only or protect secrets placed in a browser-reachable module.
Use createServerFn for typed mutations/actions and createServerQuery for typed reads,
deduplication, prefetch, stale-while-revalidate, focus/reconnect refresh, and structured invalidation.
Browser server query/action references require Farm's documented server-function transform. Without
it, keep the handler server-only and expose an API route instead.
Parse route and search values with loadRouteParams, loadSearchParams, and the parsers in
@farm.js/core/query/server. React client URL state uses useQueryState or useQueryStates from
@farm.js/core/query/client. Preserve repeated values with asArrayOf.
Integration Spec
Define integrations in a server-only module and attach the registry in farm.config.ts:
import { stripe } from "@farm.js/stripe";
export const appIntegrations = {
billing: stripe({
secretKey: process.env.STRIPE_SECRET_KEY,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
}),
} as const;
export type AppIntegrations = typeof appIntegrations;
The object key is the application namespace, so billing becomes api.integrations.billing
with createApiClients, or api.billing with integration-only createIntegrations. An integration
may contribute routes/endpoints, typed callers, middleware, React providers, database schemas,
validated config, plugins, setup/ready/dispose hooks, and runtime logs. Prefer integrationRoute.*
or endpoint.* when the integration owns handlers and should generate caller types. An explicit
api field describes callers only; it does not mount HTTP routes.
Provider Integrations and Auth
New apps import dedicated adapter packages such as @farm.js/stripe, @farm.js/clerk,
@farm.js/resend, or @farm.js/jobs. The older @farm.js/integrations/* paths are
compatibility re-exports, not the preferred authoring surface.
Use the CLI to keep package versions and wiring aligned:
farm add integration --list
farm add integration stripe
farm add integration stripe --ui
farm add integration jobs-trigger
Current integration families include AI, Stripe, Autumn, Polar, Resend, Trigger.dev, Inngest, Unkey, Cloudflare agents, Eve, Auth0, Auth.js, Better Auth, Clerk, Supabase, WorkOS, custom integrations, UI registries, and ORM-backed data.
Provider adapters support two ownership modes: pass credentials so the adapter constructs its
default SDK, or pass an app-owned vendor client through instance. Keep the vendor instance in a
server-only module. Export the integration registry type so the shared caller factory can infer
integration operations without importing provider code at runtime.
For ordinary email/password auth, install @farm.js/auth and use top-level auth: true. Read
sessions through auth.session() or auth.user() from @farm.js/auth/server, and run
farm auth migrate before production traffic. Use an explicit auth integration when the app must
own Better Auth plugins/adapters or a provider-specific SDK. Never configure top-level auth and
integrations.auth together.
Custom integrations use defineIntegration, integrationRoute or endpoint, schema-validated
config, lifecycle hooks, middleware, providers, database schemas, and typed caller contracts. Read
docs/src/app/docs/integrations/custom/page.md and the closest dedicated provider guide before
implementing one.
Storage Spec
Storage helpers live under @farm.js/core/storage:
import { getStorage, sqliteStorage, redisStorage } from "@farm.js/core/storage";
const sqlite = sqliteStorage({
path: "./.farm/storage/app.sqlite",
tableName: "app_store",
});
export default defineConfig({
storage: {
mounts: {
app: sqlite,
},
},
});
const appStore = getStorage("app");
await appStore.setItem("settings", { theme: "light" });
Known helpers include memory, local, sqlite, postgres, mysql, redis, mongodb, s3, upstash, vercelKV, pglite, and libsql.
Cron Spec
Framework Cron maps portable five-field UTC schedules to ordinary GET API routes. Keep timing in farm.config.ts and business logic in the route:
import { defineConfig } from "@farm.js/core";
export default defineConfig({
cron: {
dailyCleanup: {
schedule: "0 2 * * *",
path: "/api/maintenance/cleanup",
},
},
});
import { cronRoute } from "@farm.js/core/cron";
export const GET = cronRoute(async () => {
await deleteExpiredSessions();
return Response.json({ ok: true });
});
Use farm cron list, farm cron run dailyCleanup, or the opt-in local scheduler farm dev --cron. cronRoute() verifies CRON_SECRET when configured and fails closed outside development and test when it is missing.
Builds always emit .farm/cron-manifest.json. Vercel compiles entries to Build Output API crons, Cloudflare Workers using cloudflare-module get Wrangler triggers, and Node/Bun/Deno targets use Nitro scheduling. Treat delivery as at least once: make handlers idempotent and use uniqueness keys or distributed locks where overlap matters. Use the Jobs integration for durable retries, steps, queues, status, or long-running work. The older defineCron() API is compatibility-only; new apps use config plus a route.
Plugin Spec
Use definePlugin for custom behavior:
import { definePlugin } from "@farm.js/core";
export const plugin = definePlugin({
name: "acme:request-context",
runtime: {
context({ request, req }) {
const path = new URL(request.url).pathname;
req.set("demo.path", path, { exposeToPage: true });
return { path };
},
},
});
Hooks are grouped: runtime (endpoints, start, context, before, after, error, close), router, render (before, html), build, dev, and client. Flat hooks such as beforeRequest, afterResponse, and transformHTML are deprecated; see PLUGIN_SYSTEM.md for every option.
Built-in server plugins are imported from @farm.js/core/plugin/server, including logger and compression helpers. Plugins run in order; use enforce: "pre" or enforce: "post" when order matters.
Content, Runtime, and Deployment
- Configure fonts through Farm so local or remote assets are self-hosted and hashed.
- Configure
themefor no-flash light/dark/system rendering and typed client/server access. - Use Farm image helpers plus the
imagesallowlist and format policy for optimization. - Use
docsfor human docs, shared search, markdown,llms.txt, sitemap, robots, and agent APIs. A rootsitemap.ts,robots.ts,llms.ts, orllms-full.ts(or the same file inpublic/) takes its path from the docs engine. - Use
agent.llmsTxtfor an app's own/llms.txtand/llms-full.txt(full: falseskips the second). A staticpublic/llms.txtorpublic/llms-full.txtoverrides each, and so dosrc/app/llms.tsandsrc/app/llms-full.ts, which return the whole file as a string or the structured format and receive the generatedpagesanddefaults. - Apps without docs serve a generated
/sitemap.xmlof static pages (agent.sitemap, withinclude/exclude;falseturns it off).public/sitemap.xmlorsrc/app/sitemap.tswins. - Apps without docs serve a read-only MCP server at
/mcp(agent.mcp):list_pages,search_pages, andread_pageover the public static pages, read anonymously as Markdown. It steps aside for an app route at/mcp, an API route at/api/mcp,@farm.js/mcp, or aregisterFarmMCPServerentry (the docs MCP);agent: { mcp: false }turns it off. - Use
agent.crawlers: { search, training }("allow"or"block", plusrulesandsitemap) for a generated/robots.txtnaming AI search and training crawlers.public/robots.txt, thensrc/app/robots.ts, win over it; it wins over the docs engine's robots.txt. - Use
md/mdxfor page mirrors and content routes; useopenapifor API references. - Every app serves a public
/.well-known/farmjs.jsonmanifest (Farm version and packages, MCP tools and schemas, WebMCP tools, integration providers, plugin names; never config or env). MCP servers withauthorizeare listed without their tools.agent.manifest: falseturns it off. - Use
agent.noindexPreviews: trueto sendX-Robots-Tag: noindex, nofollowon preview deployments (FARM_PREVIEW=1,VERCEL_ENV=preview, Netlify deploy-preview/branch-deploy at build time, Render PR previews). Test locally withFARM_PREVIEW=1 farm dev. - Use
after()only for short post-response work and a jobs integration for durable work. - Configure OpenTelemetry and Farm runtime events for correlated traces.
farm preview exposes an already-running local app; it does not build or deploy. Bind remote
sandbox development servers with farm dev --host 0.0.0.0 --port <port>. For deployment,
prefer deploy.target for first-class targets and deploy.preset only for Nitro pass-through. A
preset overrides a target. Use farm deploy --plan before shipping and run the exact target build.
Current diagnostic and migration commands include:
farm doctor
farm doctor --offline
farm doctor --fix
farm explain /products/42
farm generate --check
farm migrate inspect
farm migrate next --write
farm migrate tanstack --write
farm telemetry status
Verification Commands
For an application, use its declared package manager and scripts:
pnpm farm generate --check
pnpm typecheck
pnpm build
Run the target-specific build when deployment behavior matters. For framework work, start focused and then broaden:
pnpm --filter @farm.js/core test
pnpm --filter @farm.js/cli test
pnpm build
pnpm lint
Use the nearest current example's scripts instead of remembered example paths. If Prisma is involved, run its client generation immediately before type checking or building.
Common Pitfalls
- Import
Link,createApiClients, andcreateIntegrationsfrom current documented client entries. - Do not put server SDKs or secrets in
"use client"modules. - Keep
AppIntegrationsexported socreateApiClients<APIRouter, AppIntegrations>()can infer integration types. - For Supabase/custom route APIs, method calls may be nested, for example
.login.post(...), not.login(...). - Do not mix renderer component formats, top-level auth with
integrations.auth, or mismatched Farm package versions. - Do not import server query handlers into the browser without the server-function transform.
- Do not use compatibility
@farm.js/integrations/*imports for new code when a dedicated adapter exists. - Cron is not a durable workflow engine; protect production routes with
CRON_SECRETand design handlers for repeated or overlapping delivery. - In monorepos with pnpm and Prisma, generated clients can be stale or shared; run
prisma generateimmediately before builds. - Feature packages are optional peers of
@farm.js/core; install the ones the app uses:@tailwindcss/vitefor built-in Tailwind CSS,@vercel/ogfor generated metadata images and@farm.js/core/og,sharpfor image optimization,@farming-labs/docsfor the docs engine.farm doctorreports a missing one. - Run
pnpm formatbeforepnpm lintifoxfmt --checkfails. - Existing lint warnings may be present; treat nonzero exit codes as failures.