Docs/cms/SDK Client

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:

OptionTypeDescription
typestringFilter by content type
statusstringFilter by status (draft, published, archived)
pagenumberPage number (1-based)
limitnumberResults per page
sortstringSort field (prefix with - for descending)
searchstringFull-text search query
draftbooleanInclude draft documents (requires admin API key)
filtersobjectAdvanced 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

OptionEnvironment VariableDescription
--output=<path>N/AOutput file path (default: ./cms-types.ts)
--api-key=<key>CMS_API_KEYCMS API key
--url=<url>CMS_API_URLCMS API URL
--website-id=<id>CMS_WEBSITE_IDWebsite 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:

OptionTypeDescription
typestringFilter by media type (e.g., image, file)
searchstringSearch by filename
limitnumberResults per page
offsetnumberPagination 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();
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.