zucms
v0.0.9
Published
Typed TypeScript SDK for the Zu CMS API.
Maintainers
Readme
Zucms TypeScript SDK
Typed TypeScript SDK for the Zu CMS API with schema sync and generated request types.
Install
pnpm add zucmsWhat You Get
- Schema-driven request typing from your CMS
- Small runtime client with predictable methods
- Automatic type inference for known model keys
- Manual generic fallback for unknown or custom models
- CLI workflow for config bootstrap and schema sync
Recommended Workflow
- Install the package.
- Run
pnpm zucms init. - Put your API token into
zucms.config.ts. - Run
pnpm zucms sync. - Import
generated/zucms/typesonce in your app. - Use
createClient()without passing the token again.
CLI
pnpm zucms init
Creates a zucms.config.ts file in the current project root.
Example output:
import type { ZucmsConfig } from "zucms";
const zucmsConfig: ZucmsConfig = {
token: process.env.ZUCMS_API_KEY,
generatedPath: "generated/zucms/types.ts",
};
export default zucmsConfig;Rules:
- The command fails if
zucms.config.tsalready exists. - The config file is the source of truth for schema sync and the default client setup.
pnpm zucms sync
Loads zucms.config.ts, fetches the schema from the CMS, normalizes it, and generates:
generated/zucms/types.tsThis generated file should not be edited manually.
Before loading the config, the command also reads a local .env file if present.
That makes token: process.env.ZUCMS_API_KEY work out of the box.
Optional CLI flags:
--configto use a custom config file path--out-fileto override the generated types path
Config
The config file must default-export a zucmsConfig object.
import type { ZucmsConfig } from "zucms";
const zucmsConfig: ZucmsConfig = {
token: process.env.ZUCMS_API_KEY,
};
export default zucmsConfig;Config Shape
type ZucmsConfig = {
token: string | undefined;
baseUrl?: string;
authMode?: "bearer" | "x-api-key";
schemaUrl?: string;
generatedPath?: string;
};Defaults
baseUrldefaults tohttps://api.zucms.coauthModedefaults to"bearer"schemaUrldefaults to"/schema"generatedPathdefaults to"generated/zucms/types.ts"
Quick Start
Import the generated types once before using the client.
import "generated/zucms/types";
import { createClient } from "zucms";
const client = createClient();
const products = await client.model("products").list();
const product = await client.model("products").get("entry_1");createClient()
Config-driven client
If zucms.config.ts exists, createClient() will use it automatically.
import "generated/zucms/types";
import { createClient } from "zucms";
const client = createClient();Explicit overrides
You can still override values directly.
import { createClient } from "zucms";
const client = createClient({
apiKey: "zw_...",
baseUrl: "https://api.example.com",
authMode: "x-api-key",
});Client override type
type CreateClientOverrides = {
apiKey?: string;
authMode?: "bearer" | "x-api-key";
baseUrl?: string;
fetch?: typeof globalThis.fetch;
headers?: HeadersInit;
};Notes:
- If
apiKeyis omitted, the client tries to read it fromzucms.config.ts. - If both
apiKeyand config are missing, the client throws a helpful error. fetchcan be overridden for tests or custom runtimes.headersare merged into every request.baseUrlmay include or omit a trailing slash.
Generated Typing Model
After pnpm zucms sync, the generated file contributes:
ZucmsModelKeyZucmsModels- per-model interfaces like
Products,Authors,Documents - relation helper types
- locale union types
zucmsModelMeta
It also augments the SDK so that known model keys become typesafe automatically.
Typed Requests
Model-scoped API
const products = client.model("products");
const list = await products.list();
const single = await products.get("entry_1");
await products.create({
slug: "zucms",
website: "https://zucms.co",
});
await products.update("entry_1", {
twitter_handle: "@zucms",
});
await products.delete("entry_1");When the model key exists in the generated schema:
list()returnsSdkListResponse<ZucmsModels["products"]>get()returnsSdkSingleResponse<ZucmsModels["products"]>create()requiresZucmsModels["products"]update()requiresPartial<ZucmsModels["products"]>- query keys like
fields,include,filter,sort, andlocaleare typed from the generated schema
Top-level API
const authors = await client.list("authors");
const author = await client.get("authors", "entry_1");
await client.create("authors", {
first_name: "Levi",
slug: "levi",
});
await client.update("authors", "entry_1", {
last_name: "Hessmann",
});
await client.delete("authors", "entry_1");Fallback API For Unknown Models
If a model key is not part of the generated schema, the SDK falls back to the generic API.
const customEntries = await client.model("custom").list();In that case the response data is unknown unless you provide a generic.
type CustomEntry = {
title: string;
};
const customEntries = await client.model("custom").list<CustomEntry>();
await client.model("custom").create<CustomEntry>({
title: "Hello",
});This is useful when:
- you have not synced the schema yet
- you work against dynamic model keys
- you want to prototype before generating types
Localized Fields
Generated model types use Localized<TValue, TLocale> for localized CMS fields.
import type { Localized } from "zucms";
type Article = {
title: Localized<string, "de" | "en">;
description: Localized<string, "de" | "en">;
};Relations And Files
Generated relations are represented through SDK helper types:
type ZucmsRelation<TModelKey> = {
id: string;
modelKey: TModelKey;
};Files use the built-in SdkFile type:
type SdkFile = {
id: string;
filename: string;
mimeType: string;
sizeBytes: number;
storagePath: string;
uploadedAt: string;
url: string;
};Queries
List requests support query parameters for pagination, search, sorting, field selection, includes, locale handling, and filters.
const response = await client.model("products").list({
page: 1,
pageSize: 25,
search: "zucms",
sort: ["slug", "-createdAt"],
fields: ["slug", "website"],
include: ["documents"],
locale: "de",
fallback: true,
filter: {
slug: { contains: "zu" },
},
});For generated model keys, query values are constrained:
fieldsonly accepts field keys of that modelincludeonly accepts relation keys of that modelfilteronly accepts field keys of that modelsortonly accepts field keys or-fieldKeylocaleonly accepts generated locales
Query shape
type SdkListQuery = {
page?: number;
pageSize?: number;
search?: string | null;
sort?: string[];
fields?: string[];
include?: string[];
locale?: string | null;
fallback?: boolean;
filter?: Record<string, SdkFilterCondition>;
};Supported filter operators
eqnecontainsstartsWithendsWithinningtgteltlteexists
Filter serialization
filter[slug][contains]=zu
filter[status][eq]=publishedResponses
List
type SdkListResponse<TData> = {
data: Array<{
id: string;
tenantId: string;
modelKey: string;
data: TData;
createdAt: string;
updatedAt: string;
}>;
meta: {
tenantId: string;
modelKey: string;
page: number;
pageSize: number;
total: number;
totalPages: number;
sort: string[];
filters: Record<string, unknown>;
search: string | null;
fields: string[];
include: string[];
};
};Single
type SdkSingleResponse<TData> = {
data: {
id: string;
tenantId: string;
modelKey: string;
data: TData;
createdAt: string;
updatedAt: string;
};
};Delete
type SdkDeleteResponse = {
data: {
id: string;
deleted: boolean;
};
};Error Handling
All non-2xx responses are mapped to SdkClientError.
import { SdkClientError } from "zucms";
try {
await client.model("products").get("missing");
} catch (error) {
if (error instanceof SdkClientError) {
console.log(error.status);
console.log(error.code);
console.log(error.message);
console.log(error.details);
console.log(error.payload);
}
}Auth Modes
Bearer token
const client = createClient({
apiKey: "zw_...",
authMode: "bearer",
});x-api-key
const client = createClient({
apiKey: "zw_...",
authMode: "x-api-key",
});Request Options
Every request method accepts optional request options as the last argument.
const controller = new AbortController();
await client.model("products").list(
{ page: 1 },
{
signal: controller.signal,
headers: {
"x-request-id": "req_123",
},
},
);type SdkRequestOptions = {
signal?: AbortSignal;
headers?: HeadersInit;
};Programmatic Schema Utilities
The package also exports the schema helpers used by the CLI.
loadConfig()loadConfigSync()fetchSchema()normalizeSchema()renderTypes()syncSchema()
Example:
import { syncSchema } from "zucms";
await syncSchema();Covered Endpoints
GET /api/models/{modelKey}/entriesPOST /api/models/{modelKey}/entriesGET /api/models/{modelKey}/entries/{entryId}PATCH /api/models/{modelKey}/entries/{entryId}DELETE /api/models/{modelKey}/entries/{entryId}
Important Notes
- Run
pnpm zucms syncwhenever the CMS schema changes. - Commit
generated/zucms/types.tsif your app depends on generated compile-time types. - Import
generated/zucms/typesbefore using schema-aware request inference. - Do not edit generated files by hand.
- Unknown model keys remain supported through the generic API.
