Skip to content

Related

A single query can also fetch entries related to the ones it finds: their parents, children and siblings, their translations, the entries they link to and the entries that link to them. 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.

Links are stored on the entry that links. Query.referencedBy finds the entries that link to the entry, through any of their fields, including links in rich text. Pass type to only find entries of that type, and field to only follow one link field of it. Like the other relations it takes the query options, count: true and first: true:

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

export async function authorPage(path: string) {
  return cms.get({
    type: Author,
    path,
    select: {
      name: Query.title,
      bio: Author.bio,
      // The posts whose author field links to this author
      posts: Query.referencedBy({
        type: BlogPost,
        field: BlogPost.author,
        select: {title: Query.title, url: Query.url},
        orderBy: {desc: BlogPost.publishDate}
      }),
      // Every entry linking to the author, in any field
      mentions: Query.referencedBy({count: true})
    }
  })
}

The entries that link to a translated entry are looked up in its locale. For an entry without a locale, such as a shared author, the linking entries of every locale are returned: pass locale to only get those of one locale.

The same relation is a filter condition: _referenced holds for entries that an entry of the query links to. {_referenced: true} matches entries that anything links to and {_referenced: false} entries nothing links to, such as unused media files:

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

// Authors of at least one featured post
const featuredAuthors = await cms.find({
  type: Author,
  filter: {
    _referenced: Query.referencedBy({
      type: BlogPost,
      field: BlogPost.author,
      filter: {featured: true}
    })
  },
  select: Query.title
})

The other way around, "the posts by this author" is a filter on the posts: links store the id of their target as _entry. This is the way to go when you start from the posts, for example to page through them:

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

export async function postsBy(authorId: string, page: number) {
  return cms.find({
    type: BlogPost,
    // Single link: has, multiple links: includes
    filter: {author: {has: {_entry: authorId}}},
    select: {title: Query.title, url: Query.url},
    orderBy: {desc: BlogPost.publishDate},
    skip: page * 10,
    take: 10
  })
}

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