Skip to content

Related

A single query can also fetch entries related to the ones it finds: their parents, children and siblings, their translations, and the entries they link to. Put a relation in select (or in include when you don't select), and it's resolved in the same query.

import {cms} from '@/cms'
import {Blog, BlogPost} from '@/schema'
import {Query} from 'alinea'

const blog = await cms.get({
  type: Blog,
  locale: 'en',
  select: {
    title: Query.title,
    posts: Query.children({
      type: BlogPost,
      select: {title: Query.title, url: Query.url, date: BlogPost.publishDate},
      orderBy: {desc: BlogPost.publishDate},
      take: 5
    })
  }
})

Every relation takes the same options as a query, such as type, filter, select, orderBy, skip and take. parent, previous and next return a single entry, the others a list. Add count: true to get the number of matches instead, as in Query.children({count: true}), or first: true to get a single entry (or null). Relations can be nested, for example children with their own children.

Tree relations

  • Query.parent(...): the parent entry, or nothing for entries at the top of a root.

  • Query.parents(...): all ancestors, starting at the top of the root. Pass depth to get only the nearest ones: depth: 1 is the parent.

  • Query.children(...): the direct children. Pass depth to include deeper descendants too, returned as one flat list.

  • Query.siblings(...): the other children of the same parent. Add includeSelf: true to include the entry itself.

  • Query.previous(...) and Query.next(...): the sibling right before or after the entry, in sidebar order, or nothing at the ends.

  • Query.translations(...): the same entry in the other locales of the root. Add includeSelf: true to include the current locale, listed first.

Tree relations stay in the locale of the entry they start from (except translations, of course) and use the status of the outer query.

import {cms} from '@/cms'
import {BlogPost} from '@/schema'
import {Query} from 'alinea'

export async function postPage(url: string) {
  return cms.first({
    type: BlogPost,
    url,
    select: {
      title: Query.title,
      body: BlogPost.body,
      // Breadcrumbs, from the top of the root down
      breadcrumbs: Query.parents({
        select: {title: Query.title, url: Query.url}
      }),
      // Pagination at the bottom of the post
      previous: Query.previous({
        type: BlogPost,
        select: {title: Query.title, url: Query.url}
      }),
      next: Query.next({
        type: BlogPost,
        select: {title: Query.title, url: Query.url}
      }),
      // A language switcher
      translations: Query.translations({
        select: {locale: Query.locale, url: Query.url}
      })
    }
  })
}

siblings, previous and next find entries that share a parent. For an entry at the top level of a root, they find the other top-level entries of the same root (and locale).

Linked entries

Entry, Link, Image and File fields return the basics of what they link to. To get other fields of the linked entries, query through the field:

  • a single link field has .first(query).

  • a multiple link field has .find(query), .first(query) and .count(query). Results keep the order the editor gave the links.

import {cms} from '@/cms'
import {Author, BlogPost} from '@/schema'
import {Query} from 'alinea'

const post = await cms.get({
  type: BlogPost,
  path: 'hello-world',
  select: {
    title: Query.title,
    author: BlogPost.author.first({
      select: {name: Query.title, bio: Author.bio, avatar: Author.avatar}
    }),
    categories: BlogPost.categories.find({
      select: {title: Query.title, url: Query.url}
    })
  }
})

Linked entries are looked up in the locale of the entry you queried, falling back to entries without a locale, so a link to an untranslated author keeps working from every translation of a post.

The other direction

Links are stored on the entry that links, so "all posts by this author" is a filter on the posts. Filter on the id of the target, stored as _entry:

import {cms} from '@/cms'
import {Author, BlogPost} from '@/schema'
import {Query} from 'alinea'

export async function authorPage(path: string) {
  const author = await cms.get({
    type: Author,
    path,
    select: {id: Query.id, name: Query.title, bio: Author.bio}
  })
  const posts = await cms.find({
    type: BlogPost,
    // Single link: has, multiple links: includes
    filter: {author: {has: {_entry: author.id}}},
    select: {title: Query.title, url: Query.url},
    orderBy: {desc: BlogPost.publishDate}
  })
  return {author, posts}
}

For multiple links, such as categories, use {categories: {includes: {_entry: id}}}. More combinations are in Filtering and the examples.