OpenAPI Reference
Generate and publish API reference docs from Farm API route metadata, with Scalar-style presentation.
Enable OpenAPI
export default defineConfig({
openapi: {
enabled: true,
route: "/docs/reference",
title: "Farm API",
version: "1.0.0",
},
});Reference route
The OpenAPI route can be included in generated route types so docs navigation and Link hrefs stay aware of the reference page.
Use an absolute pathname, such as /docs/reference. Farm rejects query strings, fragments,
backslashes, encoded separators, and dot segments such as .. or %2e%2e that browsers
would reinterpret. Integration route paths and workflows.route use the same validation.
What gets documented
Farm scans API route files and generates an OpenAPI 3.0.3 spec from the discovered routes, methods, route params, query schemas, body schemas, and response metadata it can infer.
import { createEndpoint } from "@farm.js/core/api";
import { z } from "zod";
export const GET = createEndpoint(
{
method: "GET",
query: z.object({
limit: z.coerce.number().int().positive().default(20),
}),
},
async ({ query }) => {
return Response.json({
users: await listUsers(query.limit),
});
},
);The route appears in the generated reference with a typed limit query parameter.
Dynamic API segments use OpenAPI path parameters. Optional catch-all routes such as
/api/files/[[...slug]] produce both /files and /files/{slug} operations because OpenAPI path
parameters themselves must always be required. Required catch-all routes produce only the
parameterized operation.
QUERY routes include their request-body schema too. Farm currently emits OpenAPI 3.0.3, whose
Path Item Object does not have a native query field. To keep the document valid, Farm places each
QUERY Operation Object under the registered x-oai-additionalOperations.QUERY extension. This
maps directly to the native query field available in OpenAPI 3.2 without mislabeling the route as
GET or POST.
Add metadata
export default defineConfig({
openapi: {
enabled: true,
route: "/docs/reference",
title: "Acme API",
description: "Public and internal API routes for Acme.",
version: "1.0.0",
servers: [
{
url: "https://api.acme.com",
description: "Production",
},
],
contact: {
name: "API Support",
email: "support@acme.com",
},
},
});Serve the reference
The configured route serves a Scalar-powered reference page with the generated spec embedded in the page. The route can be visited during development and included in production docs.
Farm pins the Scalar browser assets to the reviewed version shipped with the framework and verifies CDN responses with subresource integrity. Scalar upgrades therefore arrive with Farm releases instead of floating independently in production.
Choose the API server URL
Set openapi.servers when the document must advertise an explicit public server. Otherwise Farm
uses an absolute api.baseURL when configured. During development, a root-relative API base is
joined to the current request origin (or http://localhost:3000 when generating without a
request). Production documents keep a root-relative API base relative, so deployed clients and the
interactive reference stay on the deployment's current origin instead of pointing at localhost.
The precedence is openapi.servers, an absolute Farm api.baseURL, the current development
request origin plus Farm's API path, then the mode-specific default.
Describe authentication
OpenAPI operations are public by default because Farm cannot infer authorization from arbitrary
middleware. Set a document-wide default with openapi.security, then override individual typed
endpoints when public and protected routes share one document:
export default defineConfig({
openapi: { enabled: true, security: "cookie" },
});
export const GET = createEndpoint({ method: "GET", openapi: { security: "public" } }, async () => ({
status: "ok",
}));
export const POST = createEndpoint(
{ method: "POST", openapi: { security: "bearer" } },
async () => ({ created: true }),
);Supported modes are public, bearer, cookie, and either. This metadata documents the
contract; authentication middleware must still enforce it at runtime.
Describe responses
Farm does not guess status codes or body formats from arbitrary handler code. Add response metadata to a typed endpoint when clients need an exact contract:
export const GET = createEndpoint(
{
method: "GET",
openapi: {
responses: {
200: {
body: "json",
description: "Current account",
schema: {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
},
},
404: { body: "empty", description: "Account not found" },
},
},
},
async () => Response.json(await getAccount()),
);Use body: "empty" for responses without content, body: "stream" with an explicit
contentType for streams, and body: "binary" for files. JSON and binary responses default to
application/json and application/octet-stream. When response metadata is absent, Farm emits an
unspecified default response instead of claiming an unknown status code or JSON schema.
farm generate
farm buildfarm dev and farm generate keep route types aware of the reference URL. farm build includes the reference route in the app output when OpenAPI is enabled.
Production notes
- Enable OpenAPI for APIs you want to document publicly or internally.
- Keep private admin routes out of public docs unless the site is protected.
- Use Zod schemas on API routes for better generated parameter and body details.
- Farm recognizes Zod schemas structurally, including Zod 3/4 schemas loaded from another package copy. Standard Schema validators without portable schema metadata remain valid at runtime, but the generated document uses an explicit metadata-unavailable fallback instead of inventing a shape.
- Pair OpenAPI with typed callers so server/client code and published docs describe the same route surface.