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.locationandlimitLocations: 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,hiddenandshared.
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 thevparameter changes when the file is replaced, so caches never serve an outdated copy.url: the same assrc.widthandheight: 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 asobject-positionwhen you crop the image.thumbHashandaverageColor: a compact placeholder and the average color ('#aabbcc') to show while the image loads.title,extension,size(bytes) andhash.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:
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.