Client
Typed JavaScript client for the CMS REST API.
@cms/client is a lightweight fetch wrapper for the CMS API. It works in any JavaScript runtime — browsers, Node, Deno, edge workers, and SSR frameworks.
Setup
import { createClient } from "@cms/client";
const cms = createClient({
url: "https://cms.example.com/api",
apiKey: "your-api-key",
});
Options
| Option | Type | Description |
|---|---|---|
| url | string | Base URL of the CMS API (required) |
| apiKey | string | API key for authentication |
| headers | Record<string, string> \| () => Record<string, string> | Custom headers (static object or function) |
| credentials | RequestCredentials | Fetch credentials mode (e.g. "include" for cookies) |
| fetch | typeof fetch | Custom fetch implementation |
| signal | AbortSignal | Default abort signal for all requests |
Typing responses
Every read method accepts a generic type parameter R that types the response data. There are two ways to provide it.
Option A: Pass a generic
When you already have a type and trust the API response shape, pass it as a generic:
type Post = {
id: string;
title: string;
status: "draft" | "published";
};
const { data: posts } = await cms.list<Post>("posts");
// ^? Post[]
const { data: post } = await cms.find<Post>("posts", {
filter: { status: "published" },
});
// ^? Post | null
This is a cast — the client does not validate the response at runtime.
Option B: Pass a Zod schema
When you need runtime validation, pass a Zod schema via the schema param. The client calls schema.parse() on each record and infers R from the schema automatically:
import { z } from "zod";
const postSchema = z.object({
id: z.string(),
title: z.string(),
status: z.enum(["draft", "published"]),
});
const { data: posts } = await cms.list("posts", { schema: postSchema });
// ^? { id: string; title: string; status: "draft" | "published" }[]
If the response doesn't match the schema, Zod throws a ZodError at runtime.
Using entity schemas
Entity constructors return a .schema property that is a Zod object — see Typing & Schemas for details. You can pass it directly to the client:
import { page } from "./cms.config";
const { data } = await cms.find("page", {
filter: { slug: "/about" },
schema: page.schema,
});
// ^? z.infer<typeof page.schema> | null
When to use which
- Generic — you have a hand-written type, you're in a context where Zod isn't available, or you don't need runtime checks.
- Schema — you want runtime validation, or you already have a Zod schema from entity constructors and want types to flow automatically.
Both can be used across the same client — they're per-call, not per-client.
Methods
find
Returns the first record matching the query, or null.
find<R>(name: string, params?: FindParams): Promise<{ data: R | null }>
const { data: post } = await cms.find<Post>("posts", {
filter: { slug: { $eq: "/hello-world" } },
fields: ["id", "title", "slug"],
});
list
Returns a paginated list of records.
list<R>(name: string, params?: ListParams): Promise<{
data: R[];
meta: { total: number; limit: number; offset: number };
}>
const { data: posts, meta } = await cms.list<Post>("posts", {
filter: { status: "published" },
sort: "-createdAt",
limit: 10,
offset: 0,
});
get
Returns a single record by ID (collection) or the singleton value (global).
// Collection — by ID
get<R>(name: string, id: string, params?: GetParams): Promise<{ data: R }>
// Global — no ID
get<R>(name: string, params?: GetParams): Promise<{ data: R }>
// Collection
const { data: post } = await cms.get<Post>("posts", "some-uuid");
// Global
const { data: settings } = await cms.get<Settings>("settings");
// Global with locale
const { data: settings } = await cms.get<Settings>("settings", { locale: "pt" });
create
Creates a new record.
create<R>(name: string, body: Partial<R>, params?: MutateParams): Promise<{ data: R }>
const { data: post } = await cms.create<Post>("posts", {
title: "New post",
status: "draft",
});
update
Updates an existing record by ID (collection) or upserts the singleton (global).
// Collection — by ID
update<R>(name: string, id: string, body: Partial<R>, params?: MutateParams): Promise<{ data: R }>
// Global — no ID
update<R>(name: string, body: Partial<R>, params?: MutateParams): Promise<{ data: R }>
// Collection
const { data: post } = await cms.update<Post>("posts", "some-uuid", {
status: "published",
});
// Global
const { data: settings } = await cms.update<Settings>("settings", {
siteName: "New name",
});
delete
Deletes a record by ID.
delete<R>(name: string, id: string, params?: MutateParams): Promise<{ data: R }>
await cms.delete("posts", "some-uuid");
request
Low-level method for custom endpoints. Accepts its own schema option for response validation.
request<T>(path: string, options?: RequestOptions<T>): Promise<T>
const health = await cms.request<{ status: string }>("/health");
// With schema validation
const health = await cms.request("/health", {
schema: z.object({ status: z.string() }),
});
Query parameters
Read methods (find, list, get) accept these query parameters:
| Param | Type | Description |
|---|---|---|
| filter | Filter | Filter records — see Querying |
| sort | string | Sort field, prefix with - for descending (e.g. "-createdAt") |
| fields | string[] | Select specific fields to return |
| resolve | Record<string, string[] \| "*"> | Resolve relation fields — see Relations |
| limit | number | Max records to return (list only) |
| offset | number | Skip N records (list only) |
| signal | AbortSignal | Abort signal for this request |
| schema | ZodType | Zod schema for runtime validation |
Runtime configuration
Use config() to update client options after creation — useful for setting auth tokens dynamically:
const cms = createClient({ url: "/api" });
// Later, after authentication
cms.config({ apiKey: token });
Error handling
Failed requests throw a ClientError with structured error data:
import { ClientError } from "@cms/client";
try {
await cms.get("posts", "bad-id");
} catch (error) {
if (error instanceof ClientError) {
console.log(error.status); // HTTP status code
console.log(error.code); // Error code (e.g. "NOT_FOUND")
console.log(error.message); // Human-readable message
console.log(error.details); // Optional additional details
}
}
Previous
REST API
Next
Field Types Reference