API Reference
Query and mutate content with the type-safe tRPC API.
Overview
Schemaful uses tRPC for end-to-end type safety. Your IDE knows the exact shape of every response—no codegen required.
Client Setup
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "@schemaful/trpc";
const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: "https://your-cms.com/api/trpc",
}),
],
});Entries
CRUD operations for content entries.
List Entries
const { entries, total } = await trpc.cms.entries.list.query({
schema: "blog-post",
locale: "en",
limit: 10,
offset: 0,
orderBy: "createdAt",
orderDir: "desc",
});Get Entry
const entry = await trpc.cms.entries.get.query({
id: "abc123",
locale: "en",
});
// Access typed content
entry.data.title; // string
entry.data.author; // EntryLinkGet Entry with Resolved Links
const entry = await trpc.cms.entries.resolve.query({
id: "abc123",
locale: "en",
depth: 2, // Resolve nested links up to 2 levels
});
// Links are replaced with full entry/asset data
entry.data.author.data.name; // Resolved author entryCreate Entry
const entry = await trpc.cms.entries.create.mutate({
schemaId: "blog-post",
locale: "en",
data: {
title: "My First Post",
slug: "my-first-post",
content: { nodeType: "document", content: [] },
},
publish: true, // Publish immediately
});Update Entry
const entry = await trpc.cms.entries.update.mutate({
id: "abc123",
locale: "en",
data: {
title: "Updated Title",
},
publish: true,
});Delete Entry
await trpc.cms.entries.delete.mutate({
id: "abc123",
allLocales: true, // Delete all locale versions
});Schemas
Manage content type definitions.
// List all schemas
const schemas = await trpc.cms.schemas.list.query();
// Get schema with fields
const schema = await trpc.cms.schemas.getWithFields.query({
id: "blog-post",
});
// Create schema (admin only)
await trpc.cms.schemas.create.mutate({
id: "blog-post",
name: "Blog Post",
description: "Articles for the blog",
});Fields
Define and manage schema fields.
// Add field to schema
await trpc.cms.fields.create.mutate({
schemaId: "blog-post",
fieldId: "title",
name: "Title",
type: "Symbol",
required: true,
localized: true,
validations: [
{ type: "size", min: 1, max: 100 }
],
});
// List fields for schema
const fields = await trpc.cms.fields.listBySchema.query({
schemaId: "blog-post",
});Assets
Manage media files and metadata.
// List assets
const { assets, total } = await trpc.cms.assets.list.query({
locale: "en",
contentType: "image", // Filter by type
limit: 20,
});
// Get asset
const asset = await trpc.cms.assets.get.query({
id: "img-001",
locale: "en",
});
// Create asset metadata
await trpc.cms.assets.create.mutate({
locale: "en",
title: "Hero Image",
fileName: "hero.jpg",
contentType: "image/jpeg",
url: "https://cdn.example.com/hero.jpg",
width: 1920,
height: 1080,
});Locales
Manage available languages.
// List locales
const locales = await trpc.cms.locales.list.query();
// Get default locale
const defaultLocale = await trpc.cms.locales.getDefault.query();
// Create locale (admin only)
await trpc.cms.locales.create.mutate({
code: "fr",
name: "French",
fallbackCode: "en",
});Authentication
API endpoints require authentication for mutations. Pass the session token in headers:
const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: "https://your-cms.com/api/trpc",
headers: () => ({
authorization: `Bearer ${sessionToken}`,
}),
}),
],
});Roles:
- • Admin — Full access (schemas, fields, settings)
- • Editor — Create/edit entries and assets
- • Viewer — Read-only access
Error Handling
tRPC throws typed errors you can catch:
import { TRPCClientError } from "@trpc/client";
try {
await trpc.cms.entries.create.mutate({ ... });
} catch (error) {
if (error instanceof TRPCClientError) {
console.log(error.message); // Error message
console.log(error.data?.code); // "UNAUTHORIZED", "NOT_FOUND", etc.
}
}