Laizy CMS
Client

Mutations

Create, update, and delete content programmatically using the Laizy TypeScript client.

Mutations

The ManagementClient provides three mutation methods for each content model: createContentData() for creating entries, updateContentData() for partial updates, and deleteContentData() for removing entries. All mutations require an admin-scoped token.

Setup

Mutations use the ManagementClient directly rather than the generated LaizyClient (which is read-only). Create an instance with your admin token:

import { ManagementClient } from '@/lib/management-client';

const client = new ManagementClient({
  baseUrl: process.env.LAIZY_BASE_URL!,
  apiToken: process.env.LAIZY_API_TOKEN!, // Admin token
  projectId: 'your-project-id',
});

Mutation operations require an admin-scoped token (laizy_eyJ... generated from Dashboard > Developer). Frontend tokens with content:read scope cannot create, update, or delete content.

Creating Content

Use createContentData() to add a new entry to a content model. The data object must contain at least one field, and required fields defined in your schema must be present unless they have default values.

Signature

async createContentData(options: {
  modelName: string;
  data: Record<string, any>;
  status?: 'draft' | 'published' | 'archived';
}): Promise<ContentEntry>

Basic Usage

const post = await client.createContentData({
  modelName: 'BlogPost',
  data: {
    title: 'Getting Started with Laizy CMS',
    content: 'This is your first post...',
    slug: 'getting-started-with-laizy',
  },
});

console.log(post.id);        // "abc123"
console.log(post.status);    // "draft" (default)
console.log(post.createdAt); // Date

Publishing on Create

By default, new entries are created with draft status. Pass status: 'published' to publish immediately:

const post = await client.createContentData({
  modelName: 'BlogPost',
  data: {
    title: 'Breaking News',
    content: 'This goes live immediately.',
    slug: 'breaking-news',
  },
  status: 'published',
});

Creating Content via CLI

The CLI also supports content creation with schema validation:

# Pipe JSON via stdin
echo '{"title": "Hello World", "content": "My first post", "slug": "hello-world"}' \
  | pnpm laizy content create BlogPost --status published

# Use --json flag
pnpm laizy content create BlogPost \
  --json '{"title": "Hello World", "content": "My first post", "slug": "hello-world"}'

# Validate without creating
pnpm laizy content create BlogPost \
  --json '{"title": "Hello World", "content": "Test"}' \
  --dry-run

The CLI loads your schema from laizy/schema.laizy and validates the JSON data against it before sending the request.

Updating Content

Use updateContentData() for partial updates. Only include the fields you want to change -- all other fields remain untouched.

Signature

async updateContentData(options: {
  id: string;
  data?: Record<string, any>;
  status?: 'draft' | 'published' | 'archived';
}): Promise<ContentEntry>

Update Field Data

const updated = await client.updateContentData({
  id: 'abc123',
  data: {
    title: 'Updated Title',
    content: 'Revised content goes here.',
  },
});

Change Status Only

You can update the status without touching any content fields:

// Publish a draft
const published = await client.updateContentData({
  id: 'abc123',
  status: 'published',
});

// Archive old content
const archived = await client.updateContentData({
  id: 'abc123',
  status: 'archived',
});

Update Data and Status Together

const result = await client.updateContentData({
  id: 'abc123',
  data: { title: 'Final Version' },
  status: 'published',
});

Updating via CLI

# Update fields
pnpm laizy content update BlogPost abc123 \
  --json '{"title": "Updated Title"}'

# Update status
pnpm laizy content update BlogPost abc123 \
  --status published

# Update both
pnpm laizy content update BlogPost abc123 \
  --json '{"title": "Final Version"}' \
  --status published

At least one of data or status must be provided. The API returns a BAD_REQUEST error if neither is included.

Deleting Content

Use deleteContentData() to permanently remove a content entry. This operation cannot be undone.

Signature

async deleteContentData(params: {
  modelName: string;
  id: string;
}): Promise<{ success: boolean }>

Basic Usage

await client.deleteContentData({
  modelName: 'BlogPost',
  id: 'abc123',
});

Deleting via CLI

The CLI shows entry details and asks for confirmation before deleting:

# Interactive with confirmation prompt
pnpm laizy content delete BlogPost abc123

# Skip confirmation
pnpm laizy content delete BlogPost abc123 --force

Status Management

Content entries have three possible statuses that control visibility:

StatusDescriptionVisible to Frontend Tokens
draftWork in progress, not yet visibleNo
publishedLive content, visible to allYes
archivedHidden from queries, preserved in databaseNo

Publishing Workflow

A typical content lifecycle looks like this:

// 1. Create as draft (default)
const entry = await client.createContentData({
  modelName: 'BlogPost',
  data: {
    title: 'Work in Progress',
    content: 'Still editing...',
    slug: 'work-in-progress',
  },
});

// 2. Publish when ready
await client.updateContentData({
  id: entry.id,
  status: 'published',
});

// 3. Archive when outdated
await client.updateContentData({
  id: entry.id,
  status: 'archived',
});

Error Handling

All mutation methods throw errors for authentication failures, validation issues, and server errors. Wrap calls in try/catch for robust error handling:

try {
  const entry = await client.createContentData({
    modelName: 'BlogPost',
    data: { title: 'New Post', content: 'Content', slug: 'new-post' },
    status: 'published',
  });
  console.log('Created:', entry.id);
} catch (error) {
  if (error instanceof Error) {
    if (error.message.includes('NOT_FOUND')) {
      // Content model does not exist
      console.error('Model not found. Did you run laizy sync?');
    } else if (error.message.includes('UNAUTHORIZED')) {
      // Token is invalid or lacks admin scope
      console.error('Authentication failed. Check your API token.');
    } else if (error.message.includes('BAD_REQUEST')) {
      // Missing required fields or empty data
      console.error('Validation error:', error.message);
    } else {
      console.error('Unexpected error:', error.message);
    }
  }
}

Common Error Codes

CodeCause
NOT_FOUNDContent model or entry does not exist
BAD_REQUESTEmpty data, missing required fields, or invalid status
UNAUTHORIZEDMissing token or insufficient scope
INTERNAL_SERVER_ERRORServer-side failure

Live Preview Events

Every create, update, and delete operation automatically publishes a live preview event via Redis pub/sub. If you have live preview configured, the dashboard and any connected clients receive real-time updates without polling.

// This create call automatically publishes:
// { projectId, modelName: "BlogPost", operation: "create", entryId, timestamp }
const entry = await client.createContentData({
  modelName: 'BlogPost',
  data: { title: 'New Post', content: 'Content', slug: 'new-post' },
});

See Live Preview for setup instructions.

Next Steps

On this page