Skip to content

Link

A link field lets editors link to a page in the CMS, an external url or an uploaded file, whichever they need. Use it for buttons and calls to action. When a link can only be one kind, use the Entry, Url, File or Image field instead: they have a simpler picker and a narrower type.

import {Field} from 'alinea'

Field.link('Button link')

Field.link.multiple('Related links', {max: 5})

Options

  • fields: extra fields stored on each link, such as a label or an "open in new tab" toggle. Pass an object of fields or a type.

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

  • allowDuplicates (multiple only): allow the same link more than once. Default true for Field.link.multiple.

  • condition and location: limit which entries the page picker offers and where it opens, see the Entry field. They don't affect the file picker.

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

Field.link accepts all three kinds. To accept a single kind, use the field made for it:

Each has the same .multiple variant, a narrower value type and a picker without the tabs for the other kinds.

Field.link has no option to hide one of its kinds. To accept two of them, such as a page or a url but no file, reject the third in validate. The stored link's _type is 'entry', 'url' or 'file':

import {Field} from 'alinea'

// A page or an external url, but no file
Field.link('Button link', {
  validate(link) {
    if (link?._type === 'file') return 'Link to a page or a url'
  }
})

To offer only some pages, pass a condition. It applies to the page picker, the url and file pickers are unaffected:

import {Field} from 'alinea'

Field.link('Button link', {
  // Only offer pages of these types in the page picker
  condition: {_type: {in: ['Page', 'BlogPost']}}
})

Value

When you query a link field, every link is resolved to what you need to render it. Check _type to see which kind it is:

  • 'entry': a page. href and url hold its current url (so the link survives moves and renames), plus title, path, entryId and entryType.

  • 'url': an external link with the url and href (the same value), title and target ('_blank' or '_self') the editor entered.

  • 'file': an uploaded file with its url and href (the same value), title, extension and size in bytes.

The values of the extra fields are on link.fields. A single link is null while it's empty, a multiple link field is an array.

import type {Link as LinkValue} from 'alinea'
import Link from 'next/link'

interface ButtonProps {
  link: LinkValue<{label: string}> | null
}

export function Button({link}: ButtonProps) {
  if (!link?.href) return null
  const label = link.fields.label || link.title
  if (link._type === 'url')
    return (
      <a href={link.href} target={link.target || undefined}>
        {label}
      </a>
    )
  return <Link href={link.href}>{label}</Link>
}

Links to entries that were deleted, or that aren't published, are dropped from multiple link fields. A single link to such an entry keeps its stored reference but has no href, so always check href before rendering.

Extra fields

import {Field} from 'alinea'

Field.link('Call to action', {
  fields: {
    label: Field.text('Label', {width: 0.5}),
    style: Field.select('Style', {
      width: 0.5,
      initialValue: 'primary',
      options: {primary: 'Primary', secondary: 'Secondary'}
    })
  }
})

Editors fill in these fields in the link's row after picking the target. LinkValue<{label: string}> in the example above types them; see Infer to derive the type from the fields instead.