---
title: "Integrations"
description: "Register services once, get owned routes, typed callers, agent runtimes, providers, middleware, database models, lifecycle hooks, and validation."
canonical_url: "https://mcp.farmjs.dev/docs/integrations"
markdown_url: "https://mcp.farmjs.dev/docs/integrations.md"
last_updated: "2026-10-20"
x_farming_labs_generated_preamble: true
---

# Integrations
URL: /docs/integrations
LLM index: /llms.txt
Description: Register services once, get owned routes, typed callers, agent runtimes, providers, middleware, database models, lifecycle hooks, and validation.

# Integrations

Integrations are the Farm layer for connecting product services to your app. A payment provider, auth system, email service, job runner, API-key platform, UI registry, or internal company SDK can register everything it owns in one place: route handlers, typed client/server callers, request middleware, React providers, database schemas, lifecycle hooks, and runtime logs.

Farm treats every integration as a small server plugin. That means an integration can participate in framework startup and shutdown, own HTTP routes, and still expose a compact typed API to the rest of the app.

A plugin extends framework behavior; an integration owns a configured service capability, such
as its SDK client, lifecycle, models, or providers. They can share route machinery without being
the same public contract. Their API routes can be consumed through one [shared API setup](/docs/api-client#integration-callers).

## Import the dedicated adapter package

New applications should import each Farm adapter from its dedicated package, such as
`@farm.js/stripe`, `@farm.js/clerk`, or `@farm.js/jobs`. These are the same packages installed by
`farm add integration` and used by the generated starter files.

The older `@farm.js/integrations/*` paths are compatibility re-exports for existing applications.
They are not a separate ownership model, and new documentation does not use them.

A Farm adapter function such as `stripe(...)` registers routes, typed callers, webhooks, and
integration lifecycle behavior. It is not the Stripe SDK instance. When the application supplies
`instance`, the adapter uses that exact object instead of constructing another provider client.

Provider adapters support two first-class ownership modes. Pass credentials when the adapter should
construct its default SDK client, or pass the vendor SDK object through `instance` when the
application should own that client. These are alternatives; an application normally registers only
one Stripe integration for a given billing namespace.

## Register integrations

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";
import { stripe } from "@farm.js/stripe";
import { betterAuth as createBetterAuth } from "better-auth";
import { betterAuth } from "@farm.js/better-auth";

const auth = createBetterAuth({
  baseURL: process.env.BETTER_AUTH_URL,
  secret: process.env.BETTER_AUTH_SECRET,
});

export default defineConfig({
  integrations: {
    billing: stripe({
      secretKey: process.env.STRIPE_SECRET_KEY,
      webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
    }),
    auth: betterAuth({
      instance: auth,
    }),
  },
});
```

The object key is the application namespace. Registering Stripe as `billing` exposes
`api.integrations.billing` with `createApiClients`, or `api.billing` with the integration-only
`createIntegrations` factory. The same distinction applies to `apiClient`; registering it as
`stripe` changes the `billing` segment to `stripe`.

## Own the provider instance in application code

When an integration uses an in-process vendor SDK, pass a configured client through `instance`.
Farm uses that exact object and does not create a second client. This lets the application control
SDK versions, transports, retries, telemetry, test doubles, and provider-specific options without
waiting for the Farm adapter to expose every constructor option.

**src/lib/stripe-client.ts**

```ts
import Stripe from "stripe";

export const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  maxNetworkRetries: 2,
});
```

**src/lib/integrations.ts**

```ts
import { stripe } from "@farm.js/stripe";
import { stripeClient } from "./stripe-client";

export const billing = stripe({
  instance: stripeClient,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
});
```

The vendor SDK comes from the vendor package, while the Farm adapter comes from the dedicated Farm
package. Keep the provider client in server-only application code and reuse it wherever direct SDK
access is needed. The adapter still owns Farm-specific configuration such as webhook routes,
product metadata, billing ownership, and typed callers.

Credential-based construction and instance injection are both supported. When `instance` is
omitted, an adapter such as `@farm.js/stripe` can construct its default SDK client from `secretKey`
or the matching environment variable. When both are supplied, `instance` wins. The injected object
must still implement the SDK methods the Farm adapter calls; a vendor breaking those methods can
still require an adapter update.

| Integration kind                             | Application ownership boundary                                                                                                           |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Stripe, Autumn, Polar, Resend, Clerk, WorkOS | `instance`: a vendor SDK object constructed in application code.                                                                         |
| Auth.js and Better Auth                      | `instance`: the application-owned auth object. These adapters have no Farm-constructed fallback.                                         |
| Auth0                                        | `instance`: a compatible application middleware adapter, not the Auth0 SDK. It replaces Farm's built-in route flow.                      |
| Unkey                                        | `instance`: an application-owned `UnkeyClient`; `createUnkeyClient` is an optional convenience constructor.                              |
| Supabase SSR                                 | `instance`: a request-scoped factory that receives Farm's cookie-aware client options. Never share one SSR auth client between requests. |
| AI                                           | `model` plus optional AI SDK function overrides. The model is already the injected provider object.                                      |
| Trigger.dev and Inngest jobs                 | `runtime: trigger(...)` or `runtime: inngest(...)`. Farm talks to the selected external runtime and does not construct its SDK.          |
| Eve and Cloudflare Agents                    | `origin` for an external runtime, or Farm's managed development process. Agent SDKs remain application-owned.                            |

Provider pages show the exact constructor and any integration-owned options that are still required.

## Create callers

### One setup for app routes and integrations

If the app uses file or plugin routes as well as integrations, use `createApiClients` once in
`src/lib/api.ts`. Export the configured registry's type from a server-only module:

**src/lib/integrations.ts**

```ts
import { billing } from "../integrations/billing";

export const appIntegrations = { billing } as const;
export type AppIntegrations = typeof appIntegrations;
```

Here `billing` is the integration defined in the [custom integration guide](/docs/integrations/custom#choose-the-http-surface).
`appIntegrations` is the real server-side object containing it. `AppIntegrations` is only a
TypeScript description of that object: `typeof` does not create another integration, and
`as const` preserves its literal types rather than freezing it at runtime.

Register that object with Farm once:

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";
import { appIntegrations } from "./src/lib/integrations";

export default defineConfig({
  integrations: appIntegrations,
});
```

This is where Farm registers the integrations and their routes. Keep provider instances and
credentials in the server-only registry. Next, create callers for those already-configured
services in the shared module; this does not create new provider instances:

**src/lib/api.ts**

```ts
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,
  integrations: {
    data: { appName: "farm-dashboard" },
  },
});
```

The inputs have different jobs:

| Input             | What it supplies                                                                      | Present in browser JavaScript?                              |
| ----------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `APIRouter`       | Generated types for file and plugin routes.                                           | No; it is a type.                                           |
| `AppIntegrations` | Types for the configured integration operations.                                      | No; `import type` is erased.                                |
| `apiRoutes`       | Generated paths and methods used to resolve app-route URLs, including dynamic params. | Yes; it is schema-free route metadata, not server handlers. |

The `integrations.data` option above is optional request metadata, not another integration
registration. You can omit it. The factory returns `api` for server calls and `apiClient` for
browser calls; both are exported from this one module.

App routes use paths such as `apiClient.hello.get(...)`; integration calls use
`apiClient.integrations.billing.checkout.post(...)` or `api.integrations.billing.checkout.post(...)`.
`farm generate`, development startup, and builds emit the route manifest. Import only the
integration registry's type; the configured registry supplies integration metadata at runtime.
No second `createIntegrations()` call is needed.

Shared setup does not unify result or transport behavior: integrations retain `{ data, error }`
results and server-side HTTP fallback, while app-route `api` calls require a Farm request and
return `{ data, error, key }` without HTTP fallback. See [Integration callers](/docs/api-client#integration-callers)
for the full contract.

### Integration-only setup

`createIntegrations()` remains supported and is not deprecated. There is no required migration:
keep it when you prefer separate route and integration caller modules, or when only integration
callers are needed. For separate modules, set `integrations: false` on the app-route
`createApiClients()` setup and keep `createIntegrations()` for the integration callers. This
does not change integration registration in `farm.config.ts`.

Existing caller options can also be a reason to keep `createIntegrations()`. The factories do
not have interchangeable option signatures:

| Need                                                                                         | `createIntegrations()`                    | `createApiClients()`                                                                              |
| -------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Shared `baseURL`, `headers`, `credentials`, or `data` defaults                               | Pass them in `clientOptions`.             | Pass them under `integrations`. These options alone do not require a separate factory.            |
| Setup-level `request`, `forwardHeaders`, or separate server defaults                         | Pass a separate `serverOptions` argument. | No separate server-options argument; server integration calls support per-call overrides instead. |
| Explicit integration definitions or API contracts, for example in isolated packages or tests | Use the source-map overload.              | Uses the configured integration registry.                                                         |

For example, a request-scoped server helper can bind a request and its forwarding policy once:

**src/lib/api.server.ts**

```ts
import { createIntegrations } from "@farm.js/core/client";
import type { AppIntegrations } from "./integrations";

export function createRequestIntegrationApi(request: Request) {
  const { api } = createIntegrations<AppIntegrations>(
    { data: { appName: "farm-dashboard" } },
    { request, forwardHeaders: ["cookie", "authorization"] },
  );

  return api;
}
```

Keep this helper in server-only code and call it per request; do not cache a request-bound
caller globally or import it into browser code. A `serverOptions` argument is not a bundler
security boundary: private headers, tokens, and requests must stay out of shared client modules.
Forward credentials only to trusted destinations.

The examples below use this integration-only setup and therefore omit `.integrations`. In an app using the
shared factory above, reuse that pair and add `.integrations` to these integration call paths
instead of creating another pair. Integration defaults such as `data` go inside its
`integrations` option.

**src/lib/api.ts**

```ts
import { createIntegrations } from "@farm.js/core/client";
import type { AppIntegrations } from "./integrations";

export const { api, apiClient } = createIntegrations<AppIntegrations>({
  data: {
    appName: "farm-dashboard",
  },
});
```

`apiClient` is the browser caller. `api` is the server caller. Both preserve the same integration namespace so a route like `/api/billing/checkout` can become `api.billing.checkout.post(...)`, and a single-method endpoint can be called directly when there is only one method.

HTTP calls use the same API-root rules on both sides. For example, `baseURL:
"https://example.com/backend/v2"` maps `/api/billing/status` to
`https://example.com/backend/v2/billing/status` for browser calls and server HTTP fallback.
A root-relative base such as `/backend/v2` uses the browser origin or the server request's origin.
Server-specific and per-call `baseURL` overrides follow the same rules. Registered local handlers
still dispatch at their canonical route path without an HTTP round trip; a gateway prefix does
not remount them. Only forward credentials to trusted HTTP destinations.

## Integration surface

An integration can contribute any of these pieces. The three HTTP fields are alternatives for
different authoring needs; you normally do not define all three.

| HTTP field  | Handles requests? | Produces typed callers?                    | Use it when                                                                                                                             |
| ----------- | ----------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `routes`    | Yes               | Yes, when entries use `integrationRoute.*` | The integration owns the handlers and a flat array or route factory is easiest to read. This is the recommended default.                |
| `endpoints` | Yes               | Yes, when entries use `endpoint.*`         | The integration owns the handlers, but a nested object makes a large set of routes easier to organize. Farm flattens it into `routes`.  |
| `api`       | No                | Yes                                        | The HTTP handlers already exist elsewhere, or you deliberately need a custom caller tree. It is a caller contract, not a route handler. |

For typed `routes` and `endpoints`, Farm derives the caller tree from each URL. For example,
`/api/billing/checkout` becomes `api.billing.checkout.post(...)` after the integration is registered
as `billing`. The object keys inside `endpoints` only organize the source code; they do not rename
the derived caller.

Plain route objects still mount handlers, but they do not carry typed caller metadata. Use
`integrationRoute.*` or the `endpoint.*` factory when you want Farm to infer request and response
types for `api` and `apiClient`.

An explicit `api` field replaces that derived caller tree for the integration. It does not mount an
HTTP handler, so every operation in it must point to a route implemented by `routes`, `endpoints`,
the app, or another server.

The `api:` field in an integration definition is the **caller contract**. The `api` returned by
`createIntegrations` is the **server caller** built from that contract; `apiClient` is its browser
counterpart. See [Choosing `routes`, `endpoints`, or `api`](/docs/integrations/custom#choose-the-http-surface)
for side-by-side examples.

Other integration fields are independent of that HTTP choice:

| Surface               | What it is for                                                                                      |
| --------------------- | --------------------------------------------------------------------------------------------------- |
| `middleware`          | Integration-owned request behavior for matchers outside a single endpoint.                          |
| `providers`           | React provider metadata and optional wrapper components.                                            |
| `schema`              | Database models used by `ctx.args.db` through the integration ORM.                                  |
| `config`              | Schema-validated config from defaults, env, input, and resolver output.                             |
| `validate`            | Early checks before the integration starts.                                                         |
| `setup`               | Bootstrapping work such as database checks or webhook registration.                                 |
| `ready`               | Post-start work once the app is ready.                                                              |
| `dispose`             | Shutdown cleanup.                                                                                   |
| `log`                 | Runtime events for registration, request start, request end, request error, and lifecycle messages. |
| `plugins`             | Extra Farm plugins that ship with the integration.                                                  |
| `documentNavigations` | Route matchers that tell docs/navigation systems where the integration owns pages.                  |

## Route ownership

Integration handlers declared through either `routes` or `endpoints` use the same web primitives as
normal route handlers. The handler receives the `Request` and a context object with params, parsed
input, request metadata, shared request context, integration metadata, and `args` for database access.

**src/integrations/local-demo.ts**

```ts
import { defineIntegration, integrationRoute } from "@farm.js/core";
import { z } from "zod";

export const localDemo = defineIntegration({
  category: "custom",
  type: "local-demo",
  instance: {},
  routes: [
    integrationRoute.post<
      "/api/local-demo/messages",
      { message: string },
      { message: string; count: number },
      { count: number }
    >("/api/local-demo/messages", {
      body: z.object({
        message: z.string().min(2),
      }),
      query: z.object({
        count: z.coerce.number().int().positive(),
      }),
      handler(_request, ctx) {
        return Response.json({
          message: ctx.input.body?.message,
          count: ctx.input.query?.count ?? 1,
        });
      },
    }),
  ],
});
```

Farm validates the body and query before route middleware, `before` hooks, or the handler run. Invalid input returns a `400` response with validation issues.

## Caller shape

Typed routes derive their caller tree from their path, so this integration does not need an explicit
`api` field:

**src/lib/api.ts**

```ts
import { createIntegrations } from "@farm.js/core/client";
import { localDemo } from "../integrations/local-demo";

export const { api, apiClient } = createIntegrations({
  localDemo,
});
```

**client component**

```tsx
"use client";

import { apiClient } from "../lib/api";

export function SendMessageButton() {
  async function send() {
    const result = await apiClient.localDemo.messages.post({
      body: {
        message: "hello",
      },
      query: {
        count: 2,
      },
    });

    if (result.error) {
      throw result.error;
    }

    console.log(result.data.message);
  }

  return <button onClick={send}>Send</button>;
}
```

**server code**

```ts
import { api } from "../lib/api";

export async function loadMessage() {
  const result = await api.localDemo.messages.post({
    body: {
      message: "server hello",
    },
  });

  return result.data;
}
```

On the server, Farm first tries to dispatch to the registered integration runtime directly. If no runtime is available, it falls back to `fetch` with forwarded request headers.

## Agent runtimes

Agent integrations connect a provider runtime to the Farm lifecycle without replacing that provider's SDK. Farm starts the runtime beside the app in development, owns its same-origin route prefixes, and composes supported production output. The client still uses the provider-native hooks and typed RPC APIs.

```ts
import { defineConfig } from "@farm.js/core";
import { eve } from "@farm.js/eve";

export default defineConfig({
  integrations: {
    agent: eve(),
  },
});
```

`agent` is a conventional namespace, not a reserved key. Agent runtime routes do not generate `api` or `apiClient` callers because streaming and WebSocket protocols are handled by the provider SDK.

| Runtime                                          | Farm manages                                                                                             | Application uses                                                        |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [Eve](/docs/integrations/eve)                    | Eve development process, `/eve` and workflow routes, Vercel build composition.                           | `agent/` files and `useEveAgent()`.                                     |
| [Cloudflare Agents](/docs/integrations/cf-agent) | Wrangler development process, `/agents` WebSocket proxy, combined Worker output and deployment metadata. | Agent classes, Durable Object bindings, `useAgent()`, and callable RPC. |

Same-origin routing is not an authorization boundary. Authenticate agent HTTP and WebSocket requests in application middleware or provider routing hooks, and authorize every sensitive tool or callable method on the server.

## Header defaults

`createIntegrations` supports static headers and sync or async header resolvers, just like
[`createApiClients`](/docs/api-client#header-defaults). Use a resolver when defaults must be read
at call time rather than captured when the module loads:

```ts
import { createIntegrations } from "@farm.js/core/client";
import type { AppIntegrations } from "./integrations";

export const { api, apiClient } = createIntegrations<AppIntegrations>({
  headers: () => ({
    "Accept-Language":
      typeof document === "undefined" ? "en" : document.documentElement.lang || "en",
  }),
});
```

`headers: async () => ({ ... })` is also supported. Each operation resolves its instance headers
once, for both browser HTTP calls and server dispatch (including the server HTTP fallback).
The existing separate factories and explicit-source overloads accept the same option.

Custom header precedence, lowest to highest, is: forwarded server request headers, instance
defaults, operation-definition headers, then per-call `headers`. Overrides are case-insensitive;
per-call headers remain plain objects. Farm still controls protocol/body headers, such as its
integration marker and JSON/form encoding. Resolver failures return `{ data: null, error }`
without dispatching or fetching.

The second, server-options argument to `createIntegrations` can provide a different `headers`
resolver for `api`; it replaces the first argument's header defaults rather than merging them.
Keep any server-only version in a server-only module. A function or the server-options argument
does not itself hide secrets from a browser bundle. For ordinary session cookies, retain Farm's
existing request forwarding and browser credential behavior instead of copying secrets into
shared defaults. The example's English server default overrides a forwarded language; omit that
default if the incoming request should decide it.

## Cancellation and deadlines

`createIntegrations<AppIntegrations>({ timeoutMs: 10_000 })` sets one whole-call deadline for
both callers. The separate server-options argument can override it for `api`. The same option
works in the existing integration-only factories and in `createApiClients`'s `integrations`
options. A call can pass `{ signal, timeoutMs }` as its second argument:

```ts
const controller = new AbortController();
const pending = apiClient.billing.checkout(
  { body: { priceId: "price_123" } },
  { signal: controller.signal, timeoutMs: 5_000 },
);
controller.abort();
const { error } = await pending;
```

The deadline includes header resolution, HTTP or local dispatch, and response decoding.
`0` disables it; use an integer between `0` and `2147483647`. Cancellation returns the normal
`{ data: null, error }` result. Farm deadlines produce an error named `TimeoutError`; ordinary
`controller.abort()` produces `AbortError`. A custom abort reason is normalized to an error.

Direct server handlers receive the combined per-call and incoming request signal on their
`Request`, just as HTTP calls receive the call signal. Cancellation stops waiting, not side
effects: handlers must pass the signal to their own work when supported. A handler ignoring it
can still finish or write data. For raw `Response` operations the deadline ends when the response
is returned; use the signal to cancel subsequent HTTP body consumption.

## Custom HTTP transport

`createIntegrations<AppIntegrations>({ fetch: customFetch })` accepts the same fetch-compatible
function as [`createApiClients`](/docs/api-client#custom-http-transport). Both HTTP callers and
the server HTTP fallback use it; registered local integration handlers still dispatch directly.
Farm supplies the resolved URL and `RequestInit`, including the cancellation signal.

The separate server-options argument can replace `fetch` for `api`, and the existing
integration-only factories accept it too. With combined route/integration callers, shared
`fetch` is inherited unless `integrations.fetch` overrides it. Keep shared wrappers browser-safe
and preserve request credentials, headers, and signals. Return a Web `Response`; do not put
provider SDKs or server credentials in a shared module.

## Shared lifecycle hooks

Integration callers accept the same [`onRequest`, `onResponse`, and `onError`
observers](/docs/api-client#shared-lifecycle-hooks) on the instance and in the second, per-call
argument. Shared hooks run before per-call hooks; neither replaces the other. Per-call
`onResponse` data is inferred from the operation's response type, while instance data is `unknown`.
On failure the observer receives `undefined` data; the operation result still uses `{ data: null, error }`.

Hooks cover browser HTTP, server HTTP fallback, and registered local dispatch. Response events
include the path, method, request ID, timestamp, and available response/status. Integrations
have one attempt (`attempt: 0`); these hooks do not introduce automatic retries. Resolver and
cancellation failures are reported too, even when no HTTP request was sent.

`onError` runs on final failure. Hook return values are ignored; throwing or rejecting hooks are
reported without changing the result, and promises are not awaited. Do not rely on observer
completion for authorization, transactions, or required background work.

The separate server-options argument replaces the corresponding instance hook for `api`;
`createApiClients`'s `integrations` options can override shared defaults in the same way.
Per-call hooks still compose after the effective instance hook. Shared observer modules must
remain browser-safe and should log only intentional, non-sensitive metadata.

## Shared data

`createIntegrations({ data })` adds small per-call metadata to integration requests. It is useful for tenant IDs, locale, analytics context, or feature flags.

```ts
export const { apiClient } = createIntegrations<AppIntegrations>({
  data: {
    tenantId: "tenant_123",
    locale: "en",
  },
});

await apiClient.billing.checkout.post(
  {
    body: {
      priceId: "price_123",
    },
  },
  {
    data: {
      source: "settings-page",
    },
  },
);
```

The route reads that value from `ctx.data`. Browser-provided data is client controlled, size limited, and sanitized before it reaches the handler. Validate it before using it for authorization.

## Built-in groups

| Group     | Built-ins                                                                                                                |
| --------- | ------------------------------------------------------------------------------------------------------------------------ |
| Payment   | Stripe, Autumn, and Polar expose checkout, subscription, portal, webhook, entitlement, and billing snapshot patterns.    |
| Auth      | Better Auth, Auth.js, Clerk, Auth0, WorkOS, and Supabase expose routes, providers, session helpers, and auth middleware. |
| Messaging | Resend sends transactional mail, previews templates, and receives provider webhooks.                                     |
| Workflows | Trigger.dev and Inngest expose trigger, schedule, batch, status, and cancel APIs.                                        |
| Agents    | Eve and Cloudflare Agents run beside Farm in development and compose with supported deployment targets.                  |
| API Keys  | Unkey can create, verify, revoke, update, and delete customer or service keys.                                           |
| Interface | UI registry entries scaffold shadcn-style integration screens when `--ui` is enabled.                                    |
| Database  | Integration schemas give database-backed integrations a provider-neutral `ctx.args.db`.                                  |

## When to build your own

Create a custom integration when a service needs one or more of these:

- Routes or webhooks that should be mounted automatically.
- Typed client/server functions that should not be hand-written in each app.
- Config validation that should fail during startup.
- Database models shared by routes, hooks, and built-in UI.
- Request middleware that belongs to the service.
- Setup or teardown work such as webhook registration, queue consumers, or health checks.

For the full authoring interface, see [Custom Integrations](/docs/integrations/custom).

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
