Skip to content

Entry

An entry field links to other entries in the CMS: the author of a post, related articles, the categories of a product. Editors pick entries in a dialog, and you can limit what they can choose.

import {Field} from 'alinea'

Field.entry('Author', {
  condition: {_type: 'Author'}
})

Field.entry.multiple('Related posts', {
  condition: {_type: 'BlogPost'},
  max: 3
})

Options

  • condition: only offer entries that match this filter. The picker then shows all matches in one flat list, across locations. Can be a function, see below.

  • location: the workspace and root (and optionally parentId and locale) the picker opens in, for example {workspace: 'main', root: 'pages'}. Can be a function.

  • limitLocations: an array of {workspace, root} locations editors can browse. Others are hidden in the picker.

  • pickChildren: offer only the direct children of the entry being edited, in a flat list.

  • defaultView: show the picker's results as 'row' (default) or 'thumb'.

  • fields: extra fields stored on each link, like the Link field.

  • max (multiple only): the maximum number of entries.

  • allowDuplicates (multiple only): allow the same entry more than once. Default false.

  • The common options help, width, inline, initialValue, required, validate, readOnly, hidden and shared.

Conditions

A condition is a filter on the entries that can be picked, written with the _ prefixed entry properties: _type, _workspace, _root, _parentId, _status, _locale, _path and _url. It can also filter on the fields of those entries, such as {_type: 'BlogPost', category: 'news'}. All operators work, such as in and startsWith:

import {Field} from 'alinea'

// One type
Field.entry('Author', {condition: {_type: 'Author'}})

// Several types
Field.entry('Parent page', {
  condition: {_type: {in: ['Page', 'Blog']}}
})

// Only posts below /blog in the pages root
Field.entry.multiple('Highlights', {
  condition: {_type: 'BlogPost', _root: 'pages', _url: {startsWith: '/blog/'}}
})

Where the picker opens

Without a condition, editors browse the content tree in the picker, starting in the root of the entry being edited. location sets another starting point: a workspace and root, optionally with a parentId to open inside a specific entry, and a locale. limitLocations lists the workspace and root pairs editors can switch to. Without it, they can browse every workspace and root.

import {Field} from 'alinea'

declare const blogId: string

// Open the picker below the blog entry
Field.entry('Related post', {
  location: {workspace: 'main', root: 'pages', parentId: blogId}
})

// Only let editors browse the pages roots of two workspaces
Field.entry('Page', {
  location: {workspace: 'main', root: 'pages'},
  limitLocations: [
    {workspace: 'main', root: 'pages'},
    {workspace: 'docs', root: 'pages'}
  ]
})

With a condition, the picker shows a flat list of the matching entries instead of the tree. Without a location, that list covers every workspace and root. With a location, it starts in that workspace and root, and when you set a parentId it only lists entries below that entry.

A location outside limitLocations isn't used: the picker opens in the first allowed location of the same workspace instead, or else the first one in the list.

Combining the options

Each option restricts one thing, so combine them to get exactly the entries you want:

  • Types: condition: {_type: 'BlogPost'}, or {_type: {in: [...]}} for several.

  • Workspaces: condition: {_workspace: 'main'} limits what can be selected, limitLocations limits what editors can browse.

  • Roots: condition: {_root: 'pages'}, or limitLocations to hide other roots.

  • Starting point: location, with a parentId to start inside an entry. pickChildren uses the entry being edited as the parent.

  • Number of entries: max on Field.entry.multiple, required to demand at least one.

This field accepts up to three blog posts, picked from below the blog entry:

import {Field} from 'alinea'

declare const blogId: string

Field.entry.multiple('Related posts', {
  // Only blog posts can be selected
  condition: {_type: 'BlogPost'},
  // List the posts below the blog entry in the pages root
  location: {workspace: 'main', root: 'pages', parentId: blogId},
  // Don't offer other workspaces and roots
  limitLocations: [{workspace: 'main', root: 'pages'}],
  // Pick up to three
  max: 3
})

condition and location also work on the Link field, where they apply to its page picker.

Dynamic options

condition and location also accept a function. It receives the entry being edited (id, type, workspace, root, parentId and locale) and a graph to run queries with, and can be async. That's useful when the same type lives in several workspaces:

import {Field} from 'alinea'

Field.entry('Author', {
  // Open the picker in the authors root of the workspace being edited
  location: ({entry}) => ({workspace: entry.workspace, root: 'authors'}),
  // And only offer authors from that workspace
  condition: ({entry}) => ({_type: 'Author', _workspace: entry.workspace})
})

Picking children

With pickChildren the picker lists the children of the entry being edited, for example to choose a featured item among the entries of a collection:

import {Field} from 'alinea'

Field.entry('Featured book', {
  condition: {_type: 'Book'},
  pickChildren: true
})

Value

When you query an entry field, each link is resolved to the target's current data: entryId, entryType, title, path, and its url (also as href), with the values of any extra fields on link.fields. A single entry field is null while it's empty, a multiple one is an array.

Targets are looked up in the locale of the entry you queried (or entries without a locale) and with the same status. Links to entries that were deleted or aren't published are dropped from multiple fields; a single link to one has no url.

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

const post = await cms.get({type: BlogPost, path: 'hello-world'})
post.author?.title // "Jane Doe"
post.author?.url // "/authors/jane-doe"
post.categories.map(category => category.title)

Querying fields of the linked entries

The resolved link only holds the basics. To get other fields of the target, query through the field: .first() on a single entry field, .find(), .first() or .count() on a multiple one. They take the same options as a query, including select, filter and orderBy:

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, avatar: Author.avatar, bio: Author.bio}
    }),
    categories: BlogPost.categories.find({
      select: {title: Query.title, url: Query.url},
      orderBy: {asc: Query.title}
    })
  }
})

To go the other way, from an author to their posts, filter on the field. See Related content.