Skip to content

Image

An image field links to an image in the media library. Queries return everything you need to render it well: the url, dimensions, alt text, the focal point the editor set and a tiny placeholder to show while it loads.

import {Field} from 'alinea'

Field.image('Cover image')

Field.image.multiple('Gallery')

Options

  • fields: extra fields stored on each image, such as a caption or a credit.

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

  • location and limitLocations: open the picker in, or limit it to, specific media roots or folders, like the Entry field.

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

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

The picker only offers images: files with an extension such as .jpg, .png, .webp, .gif, .avif or .svg.

Value

Each image is resolved to an ImageLink:

  • src: the url of the image on your site, such as /admin/file/landscape.jpg?v=3f2a…. It's served through your Alinea handler, and the v parameter changes when the file is replaced, so caches never serve an outdated copy.

  • url: the same as src.

  • width and height: the dimensions in pixels.

  • alt: the alt text editors enter in the media library, in the locale of the entry you queried when the media root is translated.

  • focus: the focal point as {x, y}, each between 0 and 1. Use it as object-position when you crop the image.

  • thumbHash and averageColor: a compact placeholder and the average color ('#aabbcc') to show while the image loads.

  • title, extension, size (bytes) and hash.

  • fields: the values of your extra fields.

A single image field is null while it's empty, a multiple one is an array.

Rendering with next/image

withAlinea allows the image urls in your Next.js image config, so you can pass them to next/image directly. imageBlurUrl from alinea/ui turns the thumbhash into a data url for the blur placeholder:

components/CmsImage.tsx
import type {ImageLink} from 'alinea'
import {imageBlurUrl} from 'alinea/ui'
import Image from 'next/image'

interface CmsImageProps {
  image: ImageLink | null
  sizes?: string
}

export function CmsImage({image, sizes}: CmsImageProps) {
  if (!image?.src) return null
  const blurDataURL = imageBlurUrl(image) || undefined
  return (
    <Image
      src={image.src}
      width={image.width}
      height={image.height}
      alt={image.alt ?? ''}
      sizes={sizes}
      placeholder={blurDataURL ? 'blur' : 'empty'}
      blurDataURL={blurDataURL}
      style={{
        objectFit: 'cover',
        objectPosition: image.focus
          ? `${image.focus.x * 100}% ${image.focus.y * 100}%`
          : undefined
      }}
    />
  )
}

Alt text and captions

image.alt is the alt text editors enter once, in the media library. When an image needs a description that depends on where it's used, or a caption or credit, add extra fields with the fields option. Editors fill them in next to the image they picked, and the values are stored with the link in your entry, not on the media file:

import {Config, Field} from 'alinea'

export const Article = Config.document('Article', {
  fields: {
    image: Field.image('Image', {
      fields: {
        alt: Field.text('Alt text', {required: true}),
        caption: Field.text('Caption'),
        credit: Field.text('Credit', {width: 0.5})
      }
    })
  }
})

Options like required, help and width work on these fields as usual: an entry with an image but without its alt text can't be published.

Query results keep the extra values on image.fields, next to the properties of the image. An extra field named alt does not replace the alt text of the media library: image.alt still holds that one, and image.fields.alt the one entered on this entry. ImageLink takes the type of the extra fields as its type parameter:

import type {ImageLink} from 'alinea'

interface FigureProps {
  image: ImageLink<{alt: string; caption: string; credit: string}> | null
}

export function Figure({image}: FigureProps) {
  if (!image?.src) return null
  const {alt, caption, credit} = image.fields
  return (
    <figure>
      <img
        src={image.src}
        width={image.width}
        height={image.height}
        alt={alt || image.alt || ''}
      />
      {caption && (
        <figcaption>
          {caption} {credit && <small>{credit}</small>}
        </figcaption>
      )}
    </figure>
  )
}

In a multiple image field every image gets its own values. The Link, Entry, File and Url fields take the same fields option and return the values on link.fields too.