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