npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

datocms-plugin-media-layouts

v0.1.2

Published

A DatoCMS plugin that adds a field that allows you to select an asset together with a layout configuration

Downloads

327

Readme

Media Layouts

A DatoCMS plugin that extends JSON fields to allow editors to select assets with precise layout configuration including aspect ratios and output widths. Ideal for content that requires specific rendering dimensions across responsive breakpoints.

DatoCMS Plugin

Features

  • Self-contained data: Stores URL, filename, and all metadata directly in JSON—no additional API calls needed
  • Three operational modes: Single asset, multiple assets (gallery), or predefined layout slots
  • Aspect ratio control: 9 presets plus custom ratios for precise cropping
  • Width presets: Original (max) plus 320px mobile to 4K resolution, with named custom presets and custom widths
  • Focal point support: Preserves focal point data from DatoCMS media library
  • Metadata editing: Edit alt text, title, and focal point directly from the field
  • Per-asset options: Optional CSS class and lazy loading toggle per image
  • Layout builder: Grid or masonry layouts with drag-and-drop placement, slot spans, and auto-sizing from aspect ratio
  • Layout metadata: Optional layout aspect ratio + width for overall composition sizing
  • Automatic height calculation: Heights computed from width and aspect ratio for easy imgix integration

Installation

  1. Go to Settings > Plugins in your DatoCMS project
  2. Click Add new plugin
  3. Search for "Media Layouts" or install from URL:
    https://github.com/datocms/plugins/media-layouts

Configuration

Global Settings

Configure default values that apply to all fields using this plugin:

| Setting | Description | Default | |---------|-------------|---------| | Default Aspect Ratio | Applied to new assets when no field override exists | 16:9 | | Default Width | Applied to new assets when no field override exists | 1920px | | Width Presets | Named width options shown in all width selectors | (none) |

Field Configuration

When adding the plugin to a JSON field, you must select a mode:

Single Asset Mode

Select one image with layout controls. Stores a single MediaLayoutItem object.

Multiple Assets Mode (Gallery)

Select multiple images, each with independent layout settings. Stores an array of MediaLayoutItem objects.

Layout Mode (Predefined Slots)

Define a layout with named slots that editors fill with assets. Choose a style:

  • Grid: Explicit rows/columns (1–4 columns, 1–6 rows), slots placed in cells
  • Masonry: Column-based layout with variable heights and ordered slots

Each slot has:

  • Label: Descriptive name (e.g., "Hero Image", "Sidebar Thumbnail")
  • Aspect Ratio: Fixed ratio for this slot (including Custom)
  • Width: Fixed output width for this slot (including Custom)
  • Required: Whether the slot must be filled

Layout builder enhancements:

  • Drag-and-drop placement: Move or swap slots directly in the preview
  • Slot spans (grid): Slots can span multiple rows/columns
  • Auto size from aspect ratio (grid): Let spans be calculated based on the slot ratio
  • Column span (masonry): Control the width of a masonry tile

Optional layout metadata:

  • Layout aspect ratio: Auto, preset, or custom ratio for the overall composition
  • Layout width: Auto, preset, or custom width for the overall composition (used to compute layout dimensions)

Field Overrides

For single/multiple modes, you can optionally override global defaults:

  • Toggle "Override default aspect ratio" to set a field-specific ratio
  • Toggle "Override default width" to set a field-specific width

Per-Asset Options

You can optionally enable extra per-image fields:

  • CSS class: Text input to pass a custom class name
  • Lazy loading: Boolean toggle for lazy loading preference

Aspect Ratio Options

| Value | Label | Use Case | |-------|-------|----------| | original | Original (no crop) | Preserve native dimensions | | 16:9 | Widescreen | Video players, hero banners | | 4:3 | Standard | Traditional TV format | | 1:1 | Square | Social media, avatars | | 3:2 | Photo | Standard photography | | 2:3 | Portrait Photo | Vertical photography | | 21:9 | Ultrawide | Cinematic banners | | 9:16 | Portrait/Mobile | Stories, vertical video | | 3:4 | Portrait Standard | Vertical content | | custom | Custom... | Enter any ratio (e.g., 2.35:1) |

Width Presets

| Width | Label | Typical Use | |-------|-------|-------------| | original | Original (max) | Use native resolution | | 320px | Mobile small | Feature phones | | 640px | Mobile | Standard mobile | | 768px | Tablet | Portrait tablets | | 1024px | Tablet landscape | Landscape tablets | | 1280px | Desktop small | Small laptops | | 1920px | Full HD | Standard desktop | | 2560px | 2K | High-resolution displays | | 3840px | 4K | Ultra HD displays |

Custom widths are supported: select "Custom..." and enter any pixel value between 1 and 10000. Heights are still calculated automatically.

You can also add named custom presets in the plugin configuration (label + width), which appear alongside the built-in options to guide editors with context-specific sizes.

Custom presets are available in all width selectors (global defaults, field overrides, asset/slot width pickers, and layout width). Layout width excludes the "Original" option and supports Auto + Custom values.

Data Model

The plugin stores all asset data directly in the JSON field, including the URL. No additional API calls are needed to render images.

Single Mode

{
  "uploadId": "abc123",
  "url": "https://www.datocms-assets.com/12345/image.jpg",
  "filename": "image.jpg",
  "format": "jpg",
  "size": 245000,
  "alt": "A beautiful sunset",
  "title": "Sunset at the beach",
  "cssClass": "hero-image",
  "lazyLoading": true,
  "focalPoint": { "x": 0.5, "y": 0.3 },
  "aspectRatio": "16:9",
  "width": 1920,
  "height": 1080,
  "originalWidth": 4000,
  "originalHeight": 3000
}

Multiple Mode

[
  {
    "uploadId": "abc123",
    "url": "https://www.datocms-assets.com/12345/photo1.jpg",
    "filename": "photo1.jpg",
    "format": "jpg",
    "size": 180000,
    "alt": "First image",
    "title": null,
    "cssClass": "",
    "lazyLoading": false,
    "focalPoint": null,
    "aspectRatio": "1:1",
    "width": 640,
    "height": 640,
    "originalWidth": 2000,
    "originalHeight": 2000
  },
  {
    "uploadId": "def456",
    "url": "https://www.datocms-assets.com/12345/photo2.png",
    "filename": "photo2.png",
    "format": "png",
    "size": 320000,
    "alt": "Second image",
    "title": null,
    "cssClass": "gallery-item",
    "lazyLoading": true,
    "focalPoint": { "x": 0.3, "y": 0.5 },
    "aspectRatio": "4:3",
    "width": 1024,
    "height": 768,
    "originalWidth": 3000,
    "originalHeight": 2000
  }
]

Layout Mode

{
  "layout": {
    "columns": 2,
    "rows": 2,
    "layoutStyle": "grid",
    "layoutAspectRatio": "16:9",
    "layoutWidth": 1920,
    "slots": [
      {
        "id": "hero",
        "label": "Hero",
        "aspectRatio": "21:9",
        "width": 1920,
        "row": 0,
        "col": 0,
        "rowSpan": 1,
        "colSpan": 2,
        "autoSpan": false,
        "required": true
      },
      {
        "id": "sidebar",
        "label": "Sidebar",
        "aspectRatio": "1:1",
        "width": 320,
        "row": 1,
        "col": 1,
        "rowSpan": 1,
        "colSpan": 1,
        "autoSpan": false,
        "required": false
      }
    ]
  },
  "assignments": [
    {
      "slotId": "hero",
      "uploadId": "abc123",
      "url": "https://www.datocms-assets.com/12345/hero.jpg",
      "filename": "hero.jpg",
      "format": "jpg",
      "size": 450000,
      "alt": "Hero banner",
      "title": null,
      "cssClass": "hero-image",
      "lazyLoading": true,
      "focalPoint": { "x": 0.5, "y": 0.5 },
      "aspectRatio": "21:9",
      "width": 1920,
      "height": 823,
      "originalWidth": 4000,
      "originalHeight": 2000
    },
    {
      "slotId": "sidebar",
      "uploadId": "def456",
      "url": "https://www.datocms-assets.com/12345/sidebar.jpg",
      "filename": "sidebar.jpg",
      "format": "jpg",
      "size": 85000,
      "alt": "Sidebar image",
      "title": null,
      "cssClass": "",
      "lazyLoading": false,
      "focalPoint": null,
      "aspectRatio": "1:1",
      "width": 320,
      "height": 320,
      "originalWidth": 800,
      "originalHeight": 800
    }
  ]
}

TypeScript Types

type MediaLayoutItem = {
  uploadId: string;
  url: string;
  filename: string;
  format: string | null;
  size: number;
  alt: string | null;
  title: string | null;
  cssClass?: string;
  lazyLoading?: boolean;
  focalPoint: { x: number; y: number } | null;
  aspectRatio: string;
  customAspectRatio?: string; // When aspectRatio is "custom"
  width: number | 'original';
  height: number;
  originalWidth: number | null;
  originalHeight: number | null;
};

type LayoutSlot = {
  id: string;
  label: string;
  aspectRatio: string;
  customAspectRatio?: string; // When aspectRatio is "custom"
  width: number | 'original';
  row: number;
  col: number;
  rowSpan?: number;
  colSpan?: number;
  autoSpan?: boolean;
  required: boolean;
};

type LayoutConfig = {
  slots: LayoutSlot[];
  columns: number;
  rows: number;
  layoutStyle?: 'grid' | 'masonry';
  layoutAspectRatio?: string;
  layoutCustomAspectRatio?: string;
  layoutWidth?: number;
};

// Single mode value
type SingleFieldValue = MediaLayoutItem | null;

// Multiple mode value
type MultipleFieldValue = MediaLayoutItem[];

// Layout mode value
type SlotAssignment = MediaLayoutItem & { slotId: string };
type LayoutFieldValue = {
  layout: LayoutConfig;
  assignments: SlotAssignment[];
};

Usage with imgix

The stored URL, width, height, and focal point data integrates directly with DatoCMS imgix:

const { url, width, height, focalPoint, aspectRatio, originalWidth } = mediaLayoutItem;

// Build imgix parameters
const resolvedWidth = width === 'original' ? originalWidth : width;
const params = new URLSearchParams({
  fit: aspectRatio === 'original' ? 'max' : 'crop',
});

if (resolvedWidth) {
  params.set('w', resolvedWidth.toString());
}
if (height && resolvedWidth) {
  params.set('h', height.toString());
}

// Add focal point if available
if (focalPoint && aspectRatio !== 'original') {
  params.set('crop', 'focalpoint');
  params.set('fp-x', focalPoint.x.toString());
  params.set('fp-y', focalPoint.y.toString());
}

const finalUrl = `${url}?${params.toString()}`;

Fetching Data

JSON fields return the complete JSON blob in GraphQL queries. Since the URL is stored directly, no additional API calls are needed:

query {
  blogPost {
    heroImage # Returns the full MediaLayoutItem or array with URL included
  }
}

The response contains everything needed to render images, including the base URL which you can combine with imgix parameters.

Development

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Type check
npx tsc -b

License

MIT