SDK Client
TypeScript SDK for consuming the R3 CMS API with type-safe content fields, codegen, and React components.
Installation
Install the SDK via npm or yarn:
yarn add @r3lab/cms-api
Or with npm:
npm install @r3lab/cms-api
Peer Dependencies
If using the React component entry point (@r3lab/cms-api/react), React 19+ is required as a peer dependency.
Quick Start
Initialize the Fetcher
import { Fetcher } from '@r3lab/cms-api';
const cms = new Fetcher({
apiKey: 'your-api-key',
websiteId: 'your-website-id',
baseUrl: 'https://cms.r3lab.com',
});
Environment Variables
Instead of hardcoding credentials, use environment variables:
CMS_API_KEY=your-api-key
CMS_WEBSITE_ID=your-website-id
CMS_API_URL=https://cms.r3lab.com
Then initialize without options:
const cms = new Fetcher();
The SDK reads from environment variables automatically. You can also override specific options:
const cms = new Fetcher({ apiKey: process.env.CMS_API_KEY });
Fetch Content
// List all blog posts
const response = await cms.getContent({ type: 'blog_post' });
console.log(response.docs); // Array of blog post documents
// Get a single post by ID
const post = await cms.getContentById('post-123');
console.log(post?.data); // Document data
// Get by slug
const post = await cms.getContentBySlug('blog_post', 'hello-world');
Content Types SDK Methods
Methods for managing content type schemas.
List All Content Types
const response = await cms.getContentTypes();
console.log(response.data);
// [
// {
// id: 'uuid',
// name: 'blog_post',
// label: 'Blog Post',
// description: 'Blog articles',
// fields: [...]
// }
// ]
Get a Single Content Type
const response = await cms.getContentType('blog_post');
console.log(response?.data);
// {
// id: 'uuid',
// name: 'blog_post',
// label: 'Blog Post',
// fields: [...]
// }
Returns null if the content type does not exist.
Create a Content Type
const response = await cms.createContentType({
name: 'product',
label: 'Product',
description: 'E-commerce products',
slugField: 'title',
titleField: 'title',
fields: [
{
name: 'title',
label: 'Title',
type: 'text',
required: true,
},
{
name: 'price',
label: 'Price',
type: 'number',
required: true,
},
],
});
Update a Content Type
const response = await cms.updateContentType('product', {
label: 'Item',
fields: [
{
name: 'title',
label: 'Product Name',
type: 'text',
required: true,
},
{
name: 'price',
label: 'Price (USD)',
type: 'number',
required: true,
},
{
name: 'stock',
label: 'Stock',
type: 'number',
required: false,
},
],
});
Delete a Content Type
await cms.deleteContentType('product');
Content Documents SDK Methods
Methods for querying, creating, updating, and publishing content documents.
List Content with Filters and Pagination
const response = await cms.getContent({
type: 'blog_post',
status: 'published',
page: 1,
limit: 10,
sort: '-publishedAt',
search: 'nextjs',
});
console.log(response.docs); // Array of documents
console.log(response.totalDocs); // Total count
console.log(response.hasNextPage); // Pagination info
Query options:
| Option | Type | Description |
|---|---|---|
type | string | Filter by content type |
status | string | Filter by status (draft, published, archived) |
page | number | Page number (1-based) |
limit | number | Results per page |
sort | string | Sort field (prefix with - for descending) |
search | string | Full-text search query |
draft | boolean | Include draft documents (requires admin API key) |
filters | object | Advanced field-level filters |
Get a Document by ID
const response = await cms.getContentById('doc-uuid');
if (response) {
console.log(response.data);
}
Returns null if the document does not exist.
Get a Document by Slug
const response = await cms.getContentBySlug('blog_post', 'hello-world');
if (response) {
console.log(response.data);
}
Returns null if the document does not exist.
Create a Document
const response = await cms.createContent(
'blog_post',
{
title: 'Hello World',
body: { type: 'doc', content: [] },
tags: ['nextjs', 'cms'],
},
'hello-world' // optional slug
);
console.log(response.data.id); // New document ID
console.log(response.data.status); // 'draft'
New documents are created with status: 'draft' by default.
Update a Document
const response = await cms.updateContent('doc-uuid', {
data: {
title: 'Hello World (Updated)',
tags: ['nextjs', 'cms', 'tutorial'],
},
});
Delete a Document
await cms.deleteContent('doc-uuid');
Publish a Document
const response = await cms.publishContent('doc-uuid');
console.log(response.data.status); // 'published'
console.log(response.data.publishedAt); // ISO timestamp
Unpublish a Document
const response = await cms.unpublishContent('doc-uuid');
console.log(response.data.status); // 'draft'
List Document Versions
const response = await cms.getContentVersions('doc-uuid');
console.log(response.data);
// [
// {
// id: 'version-uuid',
// contentId: 'doc-uuid',
// snapshot: {...},
// createdAt: '2026-03-25T10:00:00.000Z',
// isLatest: true
// }
// ]
Restore a Document Version
const response = await cms.restoreContentVersion('doc-uuid', 'version-uuid');
console.log(response.data);
Restoring creates a new version snapshot so the restore is tracked in history. The publish status does not change.
Type-Safe Content Fields
Use TypeScript generics to type your content fields.
Manual Type Definition
Define an interface for your content structure:
interface BlogPost {
title: string;
body: { type: string; content: any[] };
tags: string[];
featured?: boolean;
}
Use with SDK Methods
Pass the generic type to SDK methods:
const response = await cms.getContent<BlogPost>({
type: 'blog_post',
});
// TypeScript knows the shape of response.docs[0].data
response.docs[0].data.title; // string
response.docs[0].data.tags; // string[]
const post = await cms.getContentById<BlogPost>('post-uuid');
post?.data.title; // string
const newPost = await cms.createContent<BlogPost>(
'blog_post',
{
title: 'My Post',
body: { type: 'doc', content: [] },
tags: ['tag1'],
}
);
IContentTyped<T>
All documents are wrapped in the IContentTyped<T> type, which extends the base IContent type with typed data:
import { IContentTyped } from '@r3lab/cms-api';
type BlogPostContent = IContentTyped<BlogPost>;
// Includes: id, type, slug, status, publishedAt, data, createdAt, updatedAt
Codegen CLI
Automatically generate TypeScript interfaces from your CMS content types.
Run the Generator
npx @r3lab/cms-api generate-types --output=./src/cms-types.ts
Options
| Option | Environment Variable | Description |
|---|---|---|
--output=<path> | N/A | Output file path (default: ./cms-types.ts) |
--api-key=<key> | CMS_API_KEY | CMS API key |
--url=<url> | CMS_API_URL | CMS API URL |
--website-id=<id> | CMS_WEBSITE_ID | Website ID |
Example with Environment Variables
CMS_API_KEY=your-key CMS_WEBSITE_ID=your-id npx @r3lab/cms-api generate-types
Generated Output
The generator creates TypeScript interfaces for each content type:
// Auto-generated by @r3lab/cms-api generate-types
import type { IContentTyped } from '@r3lab/cms-api/types';
/** Content type: Blog Post (`blog_post`) */
export interface BlogPost {
title: string;
body: { type: string; content: any[] };
tags?: string[];
featured?: boolean;
}
export type BlogPostContent = IContentTyped<BlogPost>;
Use the generated types immediately:
import { BlogPost, BlogPostContent } from './cms-types';
const post = await cms.getContentById<BlogPost>('post-uuid');
Webhooks SDK Methods
Configure event notifications for your CMS.
List All Webhooks
const response = await cms.getWebhooks();
console.log(response.data);
Create a Webhook
const response = await cms.createWebhook({
name: 'Deploy on publish',
url: 'https://your-app.com/webhooks/cms',
secret: 'your-webhook-secret',
events: ['entry.publish', 'entry.unpublish'],
contentTypes: ['blog_post'],
isActive: true,
});
Update a Webhook
const response = await cms.updateWebhook('webhook-uuid', {
events: ['entry.publish', 'entry.unpublish', 'entry.delete'],
});
Delete a Webhook
await cms.deleteWebhook('webhook-uuid');
Media SDK Methods
Retrieve uploaded media files.
Get Media
const response = await cms.getMedia({
type: 'image',
search: 'logo',
limit: 20,
});
console.log(response.items); // Array of media files
console.log(response.total); // Total count
Query options:
| Option | Type | Description |
|---|---|---|
type | string | Filter by media type (e.g., image, file) |
search | string | Search by filename |
limit | number | Results per page |
offset | number | Pagination offset |
Legacy Blog SDK Methods
These methods are kept for backward compatibility. For new projects, use the getContent<T>() methods instead.
Get Blog Posts
const response = await cms.getBlogPosts({ limit: 10 });
console.log(response.data);
Get a Blog Post by Slug
const response = await cms.getBlogPost('hello-world');
Get Blog Categories
const response = await cms.getBlogCategories();
Get Related Blog Posts
const response = await cms.getRelatedBlogPosts('hello-world', { limit: 5 });
Create a Lead
await cms.createLead({
firstName: 'John',
lastName: 'Doe',
email: '[email protected]',
website: 'your-website-id',
company: 'Acme Inc',
source: 'contact-form',
});
React Component
The SDK includes a React component for rendering rich text content.
BlogPostContent Component
Import from the React entry point:
import { BlogPostContent } from '@r3lab/cms-api/react';
Usage example:
'use client';
import { BlogPostContent } from '@r3lab/cms-api/react';
export default function BlogPost({ post }) {
return (
<article>
<h1>{post.data.title}</h1>
<BlogPostContent content={post.data.body} />
</article>
);
}
The component renders TipTap rich text blocks (headings, paragraphs, lists, images, links).
Error Handling
The SDK throws FetcherError for failed requests.
FetcherError
import { FetcherError } from '@r3lab/cms-api';
try {
await cms.getContentById('invalid-id');
} catch (error) {
if (error instanceof FetcherError) {
console.error(error.message); // Error message
console.error(error.status); // HTTP status code
console.error(error.data); // Error response data
}
}
404 Pattern
Methods that query by ID or slug return null on 404 instead of throwing an error:
const post = await cms.getContentById('nonexistent-id');
if (post === null) {
console.log('Post not found');
}
Server Actions
The SDK provides safe action clients for use with Next.js server actions.
actionClient
For basic server actions:
import { actionClient } from '@r3lab/cms-api';
const myAction = actionClient
.schema(z.object({ id: z.string() }))
.action(async ({ parsedInput }) => {
const cms = new Fetcher();
const post = await cms.getContentById(parsedInput.id);
return post;
});
rateLimitedActionClient
For rate-limited actions with Redis:
import { rateLimitedActionClient } from '@r3lab/cms-api';
const limitedAction = rateLimitedActionClient
.schema(z.object({ email: z.string().email() }))
.action(async ({ parsedInput }) => {
const cms = new Fetcher();
// Action will be rate-limited per email
});
See the next-safe-action documentation for complete usage details.