Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/alineacms/alinea/llms.txt

Use this file to discover all available pages before exploring further.

Alinea exposes a set of mutation methods directly on the cms object. These methods let you create new entries, update field values, change lifecycle status (publish, unpublish, archive), reorder entries in the tree, and delete them — all from regular TypeScript code running on the server.
Mutation methods (cms.create, cms.update, cms.publish, cms.unpublish, cms.archive, cms.move, cms.remove, cms.upload, cms.discard) require an authenticated backend connection. They cannot be called from statically generated pages or React Server Components that run at build time. Use them inside API routes, Server Actions, or scripts that run against a live Alinea backend.
The Edit namespace (imported from alinea) provides helper utilities for building complex field values — lists, rich text, and links — before passing them to set in a create or update query.

cms.create(query)

Creates a new entry and returns the fully-typed result. A unique id is generated automatically unless you provide one.
type
Type<Fields>
required
The content type to create.
set
Partial<StoredRow<Fields>>
required
The field values for the new entry.
id
string
Override the auto-generated entry id.
parentId
string | null
Place the new entry under this parent. Pass null to create a root-level entry.
workspace
string
Target workspace name. Defaults to the first workspace in the config.
root
string
Target root name within the workspace.
locale
string | null
Locale for multi-language setups.
status
'draft' | 'published' | 'archived'
Initial status. Defaults to 'published'.
insertOrder
'first' | 'last'
Where to insert the new entry among its siblings.
Creating a new blog post
import {cms, BlogPost} from '@/cms'

const post = await cms.create({
  type: BlogPost,
  parentId: 'blog-section-id',
  insertOrder: 'first',
  status: 'draft',
  set: {
    title: 'My New Post',
    slug: 'my-new-post',
    excerpt: 'A short introduction.',
    publishDate: new Date().toISOString().slice(0, 10)
  }
})

console.log(post._id)    // generated entry id
console.log(post.title)  // 'My New Post'

cms.update(query)

Updates the fields of an existing entry. The set value is merged — fields not present in set are left unchanged.
id
string
required
The id of the entry to update.
set
Partial<StoredRow<Fields>>
required
Partial field values to apply.
type
Type<Fields>
Providing the type enables TypeScript to check set against the type’s field definitions.
status
'draft' | 'published' | 'archived'
Which status version to update. Defaults to 'published'.
locale
string | null
Locale of the version to update.
Updating a post's title and excerpt
import {cms, BlogPost} from '@/cms'

const updated = await cms.update({
  type: BlogPost,
  id: 'existing-post-id',
  status: 'draft',
  set: {
    title: 'Updated Title',
    excerpt: 'New introduction copy.'
  }
})

cms.publish(query)

Publishes a draft or archived entry, making it visible to status: 'published' queries.
id
string
required
The id of the entry to publish.
status
'draft' | 'archived'
required
The current status of the entry being promoted to published.
locale
string | null
Locale of the version to publish.
Publishing a draft
await cms.publish({
  id: 'post-id',
  status: 'draft'
})

cms.unpublish(query)

Reverts a published entry back to draft status.
id
string
required
The id of the entry to unpublish.
locale
string | null
Locale of the version to unpublish.
Unpublishing an entry
await cms.unpublish({id: 'post-id'})

cms.archive(query)

Moves an entry to the archived state. Archived entries are not included in default queries (use status: 'archived' or status: 'all' to retrieve them).
id
string
required
The id of the entry to archive.
locale
string | null
Locale of the version to archive.
Archiving an entry
await cms.archive({id: 'post-id'})

cms.move(query)

Moves an entry to a different position in the tree — to a new parent, a new root, or a different sibling order.
id
string
required
The id of the entry to move.
after
string | null
Place the moved entry after this sibling id. Pass null to move it to the top.
toParent
string
Move the entry under a different parent entry.
toRoot
string
Move the entry into a different root.
Reordering siblings
// Move 'post-id' after 'other-post-id' within the same parent
const result = await cms.move({
  id: 'post-id',
  after: 'other-post-id'
})
// Returns the updated { index } for the moved entry
Moving to a different parent
await cms.move({
  id: 'post-id',
  toParent: 'new-section-id',
  after: null  // insert at the top
})

cms.remove(...ids)

Permanently deletes one or more entries by id. This operation is irreversible.
Deleting entries
await cms.remove('post-id-1', 'post-id-2')

cms.upload(query)

Uploads a file and creates a MediaFile entry in the media library. Returns the newly created MediaFile entry.
file
File | [string, Uint8Array]
required
The file to upload. Pass a browser File object or a [filename, bytes] tuple for server-side uploads.
workspace
string
Target workspace. Defaults to the first workspace in the config.
root
string
Target media root within the workspace.
parentId
string | null
Parent folder entry id.
replaceId
string
Id of an existing MediaFile entry to replace. When provided the upload overwrites the existing entry.
Uploading a file from a server action
import {cms} from '@/cms'

async function uploadAvatar(file: File) {
  const mediaEntry = await cms.upload({file})
  return mediaEntry  // MediaFile entry with .location, .url, etc.
}

cms.discard(query)

Discards a specific status version (draft, published, or archived) of an entry without affecting other versions. Unlike cms.remove(), this removes only the chosen version — the entry continues to exist in other statuses.
id
string
required
The id of the entry whose version to discard.
status
'draft' | 'published' | 'archived'
required
The version to discard.
locale
string | null
Locale of the version to discard.
Discarding a draft without deleting the published version
import {cms} from '@/cms'

await cms.discard({
  id: 'post-id',
  status: 'draft'
})

Building field values with Edit

The Edit namespace provides builder utilities for field types that have structured values: lists, rich text, and links.

Edit.list(field, current?)

Creates a ListEditor for a Field.list(...) field. Chain .add(type, row) calls to append rows, then call .value() to get the serialisable array.
Building a list field value
import {Edit} from 'alinea'
import {cms, Page} from '@/cms'

const editor = Edit.list(Page.sections)
  .add('hero', {
    heading: 'Welcome',
    subheading: 'Get started today'
  })
  .add('cta', {
    label: 'Read the docs',
    url: '/docs'
  })

await cms.create({
  type: Page,
  set: {
    title: 'Home',
    sections: editor.value()
  }
})
The ListEditor also exposes insertAt(index, type, row) and removeAt(index) when you need to edit an existing list:
Editing an existing list
import {Edit} from 'alinea'
import {cms, Page} from '@/cms'

const existing = await cms.get({
  type: Page,
  id: 'page-id',
  select: {sections: Page.sections}
})

const updated = Edit.list(Page.sections, existing.sections)
  .add('text', {content: 'New paragraph added at the end'})

await cms.update({
  type: Page,
  id: 'page-id',
  set: {sections: updated.value()}
})

Edit.richText(field?, current?)

Creates a RichTextEditor for a Field.richText(...) field. Use .addHtml(html) to parse an HTML string into Alinea’s rich-text document format.
Building a rich-text field value
import {Edit} from 'alinea'
import {cms, BlogPost} from '@/cms'

const body = Edit.richText(BlogPost.body)
  .addHtml('<h2>Introduction</h2><p>Welcome to the post.</p>')
  .addHtml('<p>More content here.</p>')

await cms.create({
  type: BlogPost,
  set: {
    title: 'Programmatic Post',
    body: body.value()
  }
})
Creates a LinkEditor (single link) or LinksEditor (list of links) for link fields. LinkEditor methods:
MethodDescription
.addUrl({url, title, target?}, fields?)Add an external URL link
.addEntry(entryId, fields?)Add an internal entry link
.addImage(entryId, fields?)Add an image link
.addFile(entryId, fields?)Add a file link
.value()Return the serialisable link object
LinksEditor extends ListEditor with the same addUrl, addEntry, addImage, and addFile methods.
Building a single link field
import {Edit} from 'alinea'
import {cms, Page} from '@/cms'

const cta = Edit.link(Page.ctaLink)
  .addUrl({url: 'https://example.com', title: 'Visit site'})

await cms.update({
  type: Page,
  id: 'page-id',
  set: {ctaLink: cta.value()}
})
Building a list of links
import {Edit} from 'alinea'
import {cms, Page} from '@/cms'

const nav = Edit.links(Page.navLinks)
  .addEntry('about-page-id')
  .addEntry('blog-index-id')
  .addUrl({url: 'https://github.com/org/repo', title: 'GitHub'})

await cms.update({
  type: Page,
  id: 'home-id',
  set: {navLinks: nav.value()}
})

Committing multiple operations at once

For bulk operations, use cms.commit(...operations) to batch multiple mutations into a single write. Import the individual operation constructors from alinea/core/db/Operation or use the functional helpers re-exported from alinea:
Batching mutations
import {Edit} from 'alinea'
import {cms, BlogPost} from '@/cms'

// Edit exports create, update, publish, archive, move, remove, upload
await cms.commit(
  Edit.create({
    type: BlogPost,
    set: {title: 'Post A', slug: 'post-a'}
  }),
  Edit.create({
    type: BlogPost,
    set: {title: 'Post B', slug: 'post-b'}
  })
)
cms.commit() serialises all operations into a single git commit on the backend. Batching related mutations reduces the number of commits in your content repository and ensures atomicity — either all mutations succeed or none do.

Build docs developers (and LLMs) love