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); // DatePublishing 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-runThe 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 publishedAt 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 --forceStatus Management
Content entries have three possible statuses that control visibility:
| Status | Description | Visible to Frontend Tokens |
|---|---|---|
draft | Work in progress, not yet visible | No |
published | Live content, visible to all | Yes |
archived | Hidden from queries, preserved in database | No |
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
| Code | Cause |
|---|---|
NOT_FOUND | Content model or entry does not exist |
BAD_REQUEST | Empty data, missing required fields, or invalid status |
UNAUTHORIZED | Missing token or insufficient scope |
INTERNAL_SERVER_ERROR | Server-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
- Queries -- Read content with
findMany(),findById(), andcount() - Content Data API -- Raw API endpoint reference
- Live Preview -- Real-time content updates in your app