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.

Every cms.find(), cms.get(), cms.first(), and cms.count() call accepts a filter key alongside the optional orderBy, take, and skip keys. Together these let you retrieve precisely the entries you need without post-processing in JavaScript.

The filter object

The filter key accepts a Filter<Fields> value — a plain object whose keys match the fields of the content type you are querying. Each value is either a direct match (equality) or a condition object that names the operator. Custom type fields use their bare name (e.g. slug, title, publishDate). Alinea’s built-in entry meta-fields — _id, _type, _status, _parentId, _locale, _path, _url — use an underscore prefix in the filter object.
app/blog/page.tsx
import {cms, BlogPost} from '@/cms'

// Simple equality filter on a custom type field
const posts = await cms.find({
  type: BlogPost,
  filter: {slug: {is: 'hello-world'}}
})

// Filter on an entry meta-field (underscore-prefixed)
const drafts = await cms.find({
  type: BlogPost,
  filter: {_status: {is: 'draft'}}
})

Scalar condition operators

For string, number, boolean, and null fields, the following operators are available:
OperatorDescription
isExact equality
isNotNot equal
inValue is in the array
notInValue is not in the array
gtGreater than
gteGreater than or equal
ltLess than
lteLess than or equal
startsWithString starts with the given prefix
Scalar filter examples
// Equality
filter: {title: {is: 'Hello World'}}

// Inequality
filter: {title: {isNot: 'Draft'}}

// One of several values
filter: {category: {in: ['news', 'tutorial']}}

// Numeric comparison
filter: {views: {gte: 100}}

// Date comparison (dates are stored as ISO strings)
filter: {publishDate: {gte: '2024-01-01', lte: '2024-12-31'}}

// Prefix match
filter: {slug: {startsWith: 'getting-started'}}

Array field operators

For array-typed fields, use includes to test whether the array contains an item that matches the inner condition:
// Tags field is Field.list(...)
filter: {tags: {includes: {label: {is: 'typescript'}}}}

Object field operators

For nested object fields, use has to filter on the nested shape:
filter: {author: {has: {name: {is: 'Alice'}}}}

Combining conditions with and / or

Multiple field conditions inside a single filter object are implicitly combined with AND. Use the top-level and or or keys to compose explicit boolean logic.
Combining conditions
import {cms, BlogPost} from '@/cms'

// Implicit AND: published AND in 2024
// Entry meta-fields (id, status, type, etc.) use underscore-prefixed keys in filter
await cms.find({
  type: BlogPost,
  filter: {
    _status: {is: 'published'},
    publishDate: {gte: '2024-01-01'}
  }
})

// Preferred: use the top-level status option for lifecycle filtering
await cms.find({
  type: BlogPost,
  status: 'published',
  filter: {publishDate: {gte: '2024-01-01'}}
})

// Explicit OR
await cms.find({
  type: BlogPost,
  filter: {
    or: [
      {category: {is: 'news'}},
      {category: {is: 'announcement'}}
    ]
  }
})

// Nested AND inside OR
await cms.find({
  type: BlogPost,
  filter: {
    or: [
      {
        and: [
          {category: {is: 'tutorial'}},
          {publishDate: {gte: '2024-01-01'}}
        ]
      },
      {featured: {is: true}}
    ]
  }
})
and and or accept arrays of Filter objects. Passing undefined in the array is safe — Alinea ignores undefined entries, so you can conditionally include conditions without extra null-checks.

Top-level shorthand filters

In addition to the filter key, several entry-level fields can be filtered directly on the query object itself:
import {cms, BlogPost} from '@/cms'

// Filter by entry id
await cms.get({id: 'abc123', type: BlogPost})

// Filter by parentId
await cms.find({parentId: 'parent-id', type: BlogPost})

// Filter by URL
await cms.get({url: '/blog/hello-world'})

// Filter by path segment
await cms.find({path: {startsWith: 'blog-'}})
These shorthand fields accept either a plain value (equality) or a full Condition object using any of the operators listed above.

Ordering results

The orderBy key accepts a single Order object or an array of them. Each Order is either {asc: Expr} or {desc: Expr}, where the expression is a field reference from your type or from Query.*.
Ordering examples
import {Query} from 'alinea'
import {cms, BlogPost} from '@/cms'

// Order by a custom field descending
const posts = await cms.find({
  type: BlogPost,
  orderBy: {desc: BlogPost.publishDate}
})

// Order by title ascending, then by date descending
const posts = await cms.find({
  type: BlogPost,
  orderBy: [
    {asc: BlogPost.title},
    {desc: BlogPost.publishDate}
  ]
})

// Order by the built-in index (editor-defined sort order)
const posts = await cms.find({
  type: BlogPost,
  orderBy: {asc: Query.index}
})
Use {asc: Query.index} to respect the manual ordering editors set in the dashboard. This is the default sort order when no orderBy is specified.

Pagination with take and skip

Use take to limit the number of results and skip to offset the window. This maps directly to SQL LIMIT / OFFSET.
app/blog/page.tsx
import {cms, BlogPost} from '@/cms'

const PAGE_SIZE = 10

async function getBlogPage(page: number) {
  const [posts, total] = await Promise.all([
    cms.find({
      type: BlogPost,
      orderBy: {desc: BlogPost.publishDate},
      take: PAGE_SIZE,
      skip: (page - 1) * PAGE_SIZE
    }),
    cms.count({type: BlogPost})
  ])
  return {posts, total, pages: Math.ceil(total / PAGE_SIZE)}
}
take
number
Return at most this many results.
skip
number
Skip this many results from the beginning of the result set.

Projecting with select

Adding a select object limits which fields are deserialised and returned. This is purely a performance and type-narrowing tool — it does not affect query execution time for the filter itself, but it reduces the amount of data transferred from the in-memory database to your component.
Selective projection
import {cms, BlogPost} from '@/cms'

const posts = await cms.find({
  type: BlogPost,
  orderBy: {desc: BlogPost.publishDate},
  take: 10,
  select: {
    title: BlogPost.title,
    slug: BlogPost.slug,
    publishDate: BlogPost.publishDate
    // body (large rich text) is omitted entirely
  }
})

Full-text search with search and Query.snippet()

Pass search to execute a full-text search across all searchable fields of the queried type. Combine it with Query.snippet() in your select to include a highlighted excerpt in the results.
Full-text search
import {Query} from 'alinea'
import {cms, BlogPost} from '@/cms'

const results = await cms.find({
  type: BlogPost,
  search: 'typescript generics',
  select: {
    title: BlogPost.title,
    url: Query.url,
    // snippet() wraps matched terms in <mark> by default
    excerpt: Query.snippet()
  }
})
Query.snippet() accepts four optional parameters:
Query.snippet(
  start = '<mark>',   // Opening tag around matched terms
  end = '</mark>',    // Closing tag around matched terms
  cutOff = '...',     // Separator between non-contiguous fragments
  limit = 64          // Approximate character budget for the snippet
)
Custom snippet delimiters
// Use bold tags instead of mark
excerpt: Query.snippet('<strong>', '</strong>', ' … ', 80)
Full-text search works over the searchableText index that Alinea builds at generate time. Fields are included in this index when they are marked as searchable in the field definition (the default for Field.text and Field.richText).

Build docs developers (and LLMs) love