Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/alineacms/alinea/llms.txt

Use this file to discover all available pages before exploring further.

Alinea includes a built-in media library that integrates directly with your content. Uploaded files are stored in your repository (or an external storage provider) and referenced from content entries using typed link fields. The media library supports both images and arbitrary files, with automatic preview generation and blur-hash thumbnails for images.

Setting up a media root

Every workspace that needs file uploads must include a media root. Create one with Config.media() and add it to the workspace’s roots map.
cms.config.ts
import {Config} from 'alinea'
import {createCMS} from 'alinea/next'

export const cms = createCMS({
  schema,
  workspaces: {
    primary: Config.workspace('Primary workspace', {
      source: 'content/primary',
      mediaDir: 'public',
      roots: {
        // ... other roots
        media: Config.media()
      }
    })
  }
})
Config.media() returns a pre-configured root that uses the built-in MediaLibrary and MediaFile types. Only one media root per workspace is needed.

mediaDir

mediaDir
string
default:"public"
The workspace option that controls where uploaded files are physically stored on disk. Defaults to public, which makes files directly accessible by Next.js’s static file serving. Paths inside entries are stored relative to this directory.
Customising the media directory
Config.workspace('Primary workspace', {
  source: 'content/primary',
  mediaDir: 'public/uploads',  // files land in public/uploads/
  roots: {
    media: Config.media()
  }
})

Built-in media types

Alinea registers two reserved types automatically — you cannot define your own types with these names.

MediaLibrary

Represents a folder in the media library. The dashboard uses this type to render the folder tree in the media explorer. Each MediaLibrary entry can contain other MediaLibrary entries (nested folders).

MediaFile

Represents a single uploaded file. Alinea creates one MediaFile entry per uploaded file and populates it with metadata fields automatically:
FieldTypeDescription
titlestringFile name
pathstringPath relative to the workspace root
locationstringPhysical path on disk
previewUrlstringPublic URL used for in-dashboard preview rendering
extensionstringFile extension, e.g. '.png'
sizenumberFile size in bytes
hashstringContent hash for deduplication
widthnumberImage width in pixels (images only)
heightnumberImage height in pixels (images only)
previewstringBase64 WebP preview data URI (images only)
averageColorstringHex dominant colour (images only)
focus{x: number; y: number}Editor-set focal point (images only)
thumbHashstringBlur-hash for placeholder rendering

Referencing media in your types

Use Field.image() and Field.file() to create media link fields inside your content types.

Field.image()

Renders an image picker that filters the media library to image file types (.jpg, .jpeg, .png, .gif, .webp, etc.).
Using Field.image()
import {Config, Field} from 'alinea'

export const Article = Config.document('Article', {
  fields: {
    title: Field.text('Title'),
    hero: Field.image('Hero image'),
    gallery: Field.image.multiple('Gallery images')
  }
})
The resolved value is an ImageLink object:
ImageLink shape
interface ImageLink {
  title: string
  src: string        // public URL of the image
  url: string
  extension: string
  size: number
  hash: string
  width: number
  height: number
  averageColor: string
  thumbHash: string
  focus: {x: number; y: number}
}

Field.file()

Renders a file picker for non-image files (documents, PDFs, archives, etc.).
Using Field.file()
import {Config, Field} from 'alinea'

export const Download = Config.document('Download', {
  fields: {
    label: Field.text('Label'),
    attachment: Field.file('Attachment'),
    assets: Field.file.multiple('Additional files')
  }
})
The resolved value is a FileLink object:
FileLink shape
interface FileLink {
  title: string
  href: string       // public URL of the file
  extension: string
  size: number
}

Querying media entries

Query MediaFile and MediaLibrary entries just like any other type.
Listing all images in the media library
import {Query} from 'alinea'
import {MediaFile} from 'alinea'

const images = await cms.find({
  type: MediaFile,
  select: {
    title: Query.title,
    src: MediaFile.location,
    width: MediaFile.width,
    height: MediaFile.height,
    thumbHash: MediaFile.thumbHash
  }
})
Listing media folders
import {MediaLibrary} from 'alinea'

const folders = await cms.find({
  type: MediaLibrary,
  select: {
    title: Query.title,
    path: Query.path
  }
})

Image preview generation

When an image is uploaded through the dashboard, Alinea automatically:
  1. Resizes the image to at most 100×100 pixels and encodes it as a ThumbHash — a compact blur placeholder.
  2. Computes the dominant average colour from the ThumbHash data.
  3. Generates a 160×160 WebP preview and stores it as a base64 data URI in the preview field.
  4. Records the original width and height.
This is handled by the createPreview utility (alinea/core/media/CreatePreview), which uses the sharp package server-side. No additional configuration is needed — it runs automatically during upload.
sharp is an optional peer dependency. If it is not installed, image previews will not be generated and width, height, preview, thumbHash, and averageColor will remain empty. Install it with your package manager to enable previews.

Using blur-hash placeholders

The thumbHash and averageColor fields are designed for use in <img> loading states. A thumbHash can be decoded client-side using the thumbhash library to render a coloured blur that matches the image content before it loads.
Rendering a blur placeholder
import {thumbHashToDataURL} from 'thumbhash'
import {base64} from 'alinea/core/util/Encoding'

function BlurPlaceholder({thumbHash}: {thumbHash: string}) {
  const dataUrl = thumbHashToDataURL(base64.parse(thumbHash))
  return <img src={dataUrl} style={{filter: 'blur(8px)'}} />
}
Alinea stores uploaded files directly in your git repository under mediaDir. This works well for small to medium projects. For larger media collections or team workflows where large binaries in git become problematic, consider using Git LFS or an external storage provider configured through the handler options.

Build docs developers (and LLMs) love