Product Updates

DatoCMS changelog for new features and general improvements
Performance optimization New

Responsive images that size themselves, across every SDK

TL;DR — Getting correctly-sized responsive images used to mean hand-writing a sizes prop that mirrored your CSS layout, and keeping it in sync as the design changed. Update any DatoCMS framework SDK and you can stop. Never set sizes? It's a free win with no code changes. Did set it? You can delete it.

Responsive images have always been a burden

The whole point of a responsive srcset is to let the browser pick the smallest image that still looks sharp. But to choose, it has to know how wide the image will actually render, and it needs that number up front, while parsing the HTML, before any CSS or layout exists.

It can't measure the element itself, so the job fell to you: hand-write a sizes value mirroring your CSS layout, and keep it in sync every time the design changed. Skip it, and you got the safe-but-pessimistic "assume the image is as wide as the viewport", so a 400px sidebar image on a 1600px screen downloaded the full-viewport candidate anyway: roughly 16× more pixels than it needed.

Welcome, sizes=auto!

Browsers now have a proper fix: sizes="auto". On a lazily-loaded image, it tells the browser to drop the guess and use the element's real, laid-out width when choosing a srcset candidate. Because a lazy image is fetched after layout, the box is already measured by the time the request goes out — so the choice is exact, not estimated. The manual bookkeeping is gone: the browser does the measuring you used to do by hand.

What changed

When you don't pass an explicit sizes prop, all four framework SDKs — react-datocms, vue-datocms, @datocms/svelte, and @datocms/astro — now emit sizes="auto" (with 100vw kept as a fallback) together with loading="lazy":

<!-- before -->
<img srcset="… 200w, 400w, 800w, 1600w" sizes="100vw" loading="lazy" />
<!-- now -->
<img srcset="… 200w, 400w, 800w, 1600w" sizes="auto, 100vw" loading="lazy" />

The amount of work on your side: none. Upgrade the package and your existing <Image> components start requesting right-sized files on their own.

When it kicks in, and when it doesn't

  • You stay in control. If you pass an explicit sizes prop, or your responsiveImage GraphQL query already returns a sizes value, that wins — we never override it.

  • Priority images are untouched. Images marked with the priority prop load eagerly, and sizes="auto" requires loading="lazy", so they keep their current behavior.

  • It needs a real layout size. Every SDK component already sets aspect-ratio + width: 100% + a max-width, which is exactly what auto needs to resolve to the correct box — so the default styling already does the right thing.

Browser support and graceful fallback

sizes="auto" is supported in Chrome and Edge 126+, Opera, Samsung Internet, and Firefox 150+. Safari doesn't support it yet.

That's exactly why we emit sizes="auto, 100vw" rather than a bare auto: browsers that don't understand auto skip it and fall back to 100vw — the safe default that's been the de-facto standard across image libraries, and the exact behavior you have today. So there's no regression anywhere: just a free optimization on the browsers that can do it, with more joining the list over time.

Available now

Upgrade to pick it up:

Terminal window
npm i react-datocms@latest # React
npm i vue-datocms@latest # Vue
npm i @datocms/svelte@latest # Svelte
npm i @datocms/astro@latest # Astro

Further reading

UI Improvement

One focal point per asset

The focal point of an image marks where its subject is: the face, the product, the thing that should stay in frame. It matters when you ask GraphQL for a cropped version. Pass imgixParams like w: 200, h: 200, fit: crop, and if a focal point is set, DatoCMS automatically adds fp-x and fp-y so the crop is built around the subject rather than the dead center of the image. You set it by clicking on the image in the upload's preview.

When we first shipped this, we let you set a different focal point for each locale, behind an interface that did almost everything in its power to hide that fact. The control sat on the left, over the image. Which locale you were actually editing was decided somewhere else entirely: a language selector tucked into the Default metadata panel on the right, with nothing visibly connecting the two. The only hint was a small Focal point (English) label that changed quietly when you switched languages on the far side of the screen.

We were wrong, about both the idea and the interface. A focal point describes where the subject physically sits in the pixels, and a face doesn't move when you translate the page into German. And because the localizability was effectively invisible, almost nobody used it. Across more than 20 million multi-locale uploads, 92% had a focal point set in the primary locale only. That was less a deliberate choice than the natural result of an interface that never let on there was anything else to set. Of the few who did discover the per-locale behaviour, 95% simply repeated the same coordinates across all locales. Just 0.019% ever set genuinely different focal points across locales, and even then, the differences were almost always too small to see.

Worse, making it localizable didn't only add friction. It quietly broke cropping. Because nearly everyone set the point in a single language, focal-point cropping worked in that language and silently fell back to a plain centre crop everywhere else. The subject you carefully framed in English could end up off-centre, or cropped out entirely, in Italian, with nothing in the editor to warn you.

So we've fixed it.

What changes for everyone today

Every asset now has a single focal point: one value, shared across all locales. This isn't an opt-in. It applies to every project right away.

Cropping is now consistent across languages. Set the focal point once, and it applies in every locale. If you were only setting it in your primary locale (as most people were), your crops everywhere else just improved on their own, with nothing for you to do.

The editor is simpler, too. The focal point is no longer tied to the language selector or to the Default metadata form. It's now an always-visible control on the upload's preview. Click anywhere on the image to aim it, and the new position saves instantly in the background: no language to pick, no Save button to hunt for. (That also retires the old gotcha where the Save button hid inside a collapsible panel and was easy to lose after you'd moved the point.)

On the CDA, the response shape doesn't change. focalPoint is still a single value per query. It just now resolves to that one shared value in every locale, instead of vanishing in the locales where no one had set it.

In the rare case where you'd deliberately set different focal points per locale, the value from your primary locale is the one we keep. If you need the old per-locale values restored, contact Support.

If you read or write focal points through the Content Management API, there's a short follow-up for you: an optional, tidier CMA format. Read it here.

Content Management API

A tidier CMA format for focal points

Focal points are now non-localized: every asset has a single focal point, shared across all locales. That change is already live in every project and asks nothing of you. (If you missed it, here's the full story.)

This post is about the one part that is opt-in: the shape of default_field_metadata in the Content Management API.

By default, nothing changes for your integrations. The CMA keeps returning and accepting the existing locale-keyed shape, so your current code keeps working untouched. The only difference is that the focal point is now the same value in every locale entry: the one real value, replicated for backward compatibility.

When you want an API that reflects the new reality, opt in. With the flag on, default_field_metadata switches to a field-keyed shape: alt, title, and custom_data remain locale-keyed, but focal_point appears once at the top level. The legacy locale-keyed shape is then no longer accepted on write, so update any code that writes default_field_metadata to the new shape before you activate it.

Before opt-in migration: Current locale-keyed shape

{
"data": {
"id": "12345678",
"type": "upload",
"attributes": {
"default_field_metadata": { // Locales first, then fields
"en": { // Primary locale
"alt": "English alt text",
"title": "English title",
"custom_data": {
"english_custom_data": "hi"
},
"focal_point": {
"x": 0.12,
"y": 0.34
}
},
"it": {
"alt": "Italian alt text",
"title": "Italian title",
"custom_data": {
"italian_custom_data": "ciao"
},
"focal_point": { // This is silently ignored now; overridden by primary
"x": 0.56,
"y": 0.78
}
}
}
}
}
}

After opt-in migration: New field-keyed shape

{
"data": {
"id": "12345678",
"type": "upload",
"attributes": {
"default_field_metadata": { // Fields first, then locales
"alt": {
"en": "English alt text",
"it": "Italian alt text"
},
"title": {
"en": "English title",
"it": "Italian title"
},
"custom_data": {
"en": {
"english_custom_data": "hi"
},
"it": {
"italian_custom_data": "ciao"
}
},
"focal_point": { // No longer locale-specific
"x": 0.12,
"y": 0.34
}
}
}
}
}

Activating the opt-in

Existing Projects (created before 2026-06-11)

In an environment's Configuration screen (not Project settings), under Available updates, you should see a new opt-in update:

Screenshot showing tidier CMA format update opt-in

It's set per environment, and it's a one-way switch: once non_localized_focal_points is on, you can't turn it back off. If you have integrations that write default_field_metadata through the CMA, we strongly recommend enabling it first in a separate Sandbox environment, updating and verifying your integrations there, and only then activating it on your primary environment.

If you don't write this data programmatically, there's nothing to do here. The default shape continues to work, and the focal point is already consolidated across all assets.

New projects (created after 2026-06-11)

The opt-in is automatically enabled for new projects and will use the new field-keyed format.

JS clients' support for the new shape is available from version 5.5.0.

New UI Improvement

Hello Dark Mode 🌚

You and your editors can now use DatoCMS in dark mode. It follows your OS preference automatically: switch your system to dark and DatoCMS switches with you, no configuration needed.

Not a fan of that? Go manual. Each team member sets their own preference independently, synced to their account across all devices, with the option to override their system setting anytime.

Dark mode covers every single view: Schema Builder, Record Editor, Media Library, Settings pages, all of it. Contrast ratios were checked for WCAG compliance so it's built for long editing sessions, not just a cosmetic fresh coat of paint.

Upgrading Plugins

If you maintain a plugin, this part's for you — otherwise you can skip it.

In dark mode, plugins built on datocms-plugin-sdk 2.1.5 or earlier render inside a white frame. They keep working as normal; the frame is just a safe fallback so nothing breaks until you update.

To make a plugin fully dark-mode compatible, upgrade to the latest SDK version and follow this upgrade guide. The migration is mechanical, so the guide includes a ready-made prompt you can hand to an AI agent to do the whole thing for you.

Integrations

Announcing the new DatoCMS Remote MCP Server

The DatoCMS MCP server is now hosted remotely! Log in once via OAuth and work across every project you have access to — in a single session, without restarting the server or swapping environment variables.

No npm install, no API token management! The server is always up to date, always available, and always secure.

This also means the MCP server is no longer a developer-only tool. With the local version, setting it up required terminal skills, npm, and manual token configuration — effectively limiting it to technical users. The remote server removes all of that: content editors, marketers, and anyone with a DatoCMS account can now use AI assistants to interact with their projects directly.

Quick links

What's new

  • Scoped OAuth authentication: No more explicit API tokens! During the authorization step, you can limit access to only the projects you choose. Every action is tied to your personal identity, giving teams clear visibility over who made which changes, when using an AI assistant.

  • Multi-project support: The agent can discover which projects you have access to, and switch between them within the same conversation. You can also paste a DatoCMS editor URL into a prompt and the server resolves the right project automatically.

  • Sandboxed script execution: Scripts execute in an isolated remote sandbox (not on your machine), plus your DatoCMS credentials are kept safe and cannot be read by the agent.

  • Separate tools for safe/unsafe actions: You can configure your agent to ie. always execute safe (read-only) actions, and manually confirm writes/deletions.

Migration

Setup is simpler than ever! Follow the installation guide for your specific AI client — most of the times, this is the snippet that works:

{
"mcpServers": {
"datocms": {
"type": "http",
"url": "https://mcp.datocms.com"
}
}
}

Breaking changes

The old local MCP server has been deprecated in favor of the new, improved remote MCP server. The Github repository is archived and no longer maintained.

CLI Integrations New

Introducing DatoCMS Agent Skills

DatoCMS Agent Skills are a set of markdown-based playbooks for AI coding agents. They give your agent the context it needs help you develop your projects correctly with DatoCMS: the right patterns, the right conventions, loaded on demand based on what you're working on.

Quick Links:

Its' helpful knowing what these skills are capable of, so here's a TLDR of what they can currently do:

  • Content modeling — schema-design decisions: model vs block, references vs embedded blocks, taxonomies, field shapes, validators, editor appearances.

  • Reading content — GraphQL queries against the Content Delivery API: filters, pagination, localization, modular content, Structured Text, responsive images, SEO metadata, typed queries with gql.tada or codegen.

  • Writing content & automation — programmatic CMA scripts: record CRUD, bulk imports/exports, asset uploads, environment forks and promotions, webhooks, roles and tokens, scheduled publishing, audit logs.

  • Workflows best practices — migrations, schema-type generation, typed CMA scripts, environment operations, CI/CD pipelines.

  • Frontend integrations — draft mode, Web Previews, Visual Editing, real-time preview subscriptions, cache-tag invalidation, SEO/sitemap wiring across Next.js, Nuxt, SvelteKit, Astro.

  • Plugin development — create a brand-new plugin from scratch with the Vite/React structure, picking the initial surfaces (field extensions, config screens, sidebars, pages, asset sources).

Installation

For Claude Code and Codex:

Terminal window
/plugin marketplace add datocms/agent-skills
/plugin install datocms@datocms-skills

For Cursor, Windsurf, Copilot, and others:

Terminal window
npx skills add datocms/agent-skills

Skills are open source at github.com/datocms/agent-skills and listed on the skills.sh directory.

Read the Agent Skills docs for full installation options and usage.

CLI Content Management API API Clients New

CLI and CMA Improvements: cma:script, Stricter Types, and Dastdown

The last couple of weeks brought a set of converging improvements across the CLI, the JS CMA client, and the structured-text packages.

datocms schema:generate now exposes runtime ID and REF constants

The schema types generator now emits Schema.Article.ID and Schema.Article.REF alongside the existing TypeScript types. No more hardcoded item-type ID strings drifting out of sync across environments!

// type position: the model's TS shape, as before
const article = await client.items.find<Schema.Article>(id);
// value position: the model's id and ref, generated from your project
await client.items.create({
item_type: Schema.Article.REF,
// …
});
if (item.relationships.item_type.data.id === Schema.Article.ID) {
// …
}

New FieldValue<T, K> helpers

Three new helpers let you derive a field's type directly from an existing record or block for the full read/write cycle:

  • FieldValue<T, K> — standard response shape

  • FieldValueInNestedResponse<T, K> — what you get when reading with nested: true

  • FieldValueInRequest<T, K> — what client.items.create / update expects

T accepts anything item-shaped like a fetched record, a nested block, or an ItemTypeDefinition directly. The typical flow this unlocks: read a record with nested: true, iterate and mutate its nested blocks, and write the result back. Typing the accumulator takes one line:

const page = await client.items.find<LandingPage>(id, { nested: true });
const sections: FieldValueInRequest<typeof page, 'sections'> = [];

New isBlockWithItemOfType predicate

isBlockWithItemOfType (and its isInlineBlockWithItemOfType counterpart) from datocms-structured-text-utils narrows a block node to a specific model shape using its item type ID. Works in findFirstNode, mapNodes, or standard conditionals, giving you typed access to .item.attributes directly without extra casting.

// inline guard
if (isBlockWithItemOfType(Schema.CtaBlock.ID, node)) {
// node.item.attributes is now typed as CtaBlock
}
// curried predicate
const firstCta = findFirstNode(content, isBlockWithItemOfType(Schema.CtaBlock.ID));

Edit Structured Text as plain text with datocms-structured-text-dastdown

The new datocms-structured-text-dastdown package gives you a lossless, markdown-flavored serialization for DatoCMS Structured Text documents. Serialize to plain text, edit it however you like (string manipulation, regex, LLM rewrite), and parse back instead of walking the AST.

Best fit: text-heavy content like articles, docs, and chapters where edits are textual and may cross node boundaries. Not for landing pages made of opaque blocks — referenced blocks stay opaque in the serialized form, so you can move or remove them but not edit their internals at this layer.

A typical round-trip would look like:

import { parse, serialize } from 'datocms-structured-text-dastdown';
const cur = await client.items.find<Schema.Article>('article-id', { nested: true });
const text = serialize(cur.body);
const edited = text.replace(/Acme Corp/g, '**Acme Inc.**');
const body = parse(edited, cur.body);
await client.items.update<Schema.Article>('article-id', { body });

mapNodes now supports full tree rewrites

mapNodes and mapNodesAsync from datocms-structured-text-utils now support structural transforms, not just 1:1 node mapping. The return value controls what happens:

  • Return a single node: direct replacement

  • Return an array: splat into the parent's children

  • Return null or undefined: remove the node entirely

New agent-targeted commands

We've also shipped datocms cma:script, datocms schema:inspect, and enhanced datocms cma:docs - commands designed to be used by your agents directly. These are documented in the CLI repo and will evolve as agent tooling matures.


To update: npm i -g datocms for CLI changes, npm i @datocms/cma-client-node@latest for client changes, npm i datocms-structured-text-dastdown for the new package.

CLI New

CLI: `npx datocms` now Just Works

The DatoCMS CLI is now published on npm as datocms — an unscoped package with the same name as its binary. The scoped @datocms/cli package is still around as a thin alias, so existing setups keep working unchanged.

The main win: npx datocms now "Just Works" in every context — whether the package is installed locally, globally, or not installed at all.

Why this matters

Until now, running npx datocms projects:list inside a project that had @datocms/cli installed could result in a confusing error:

Terminal window
$ npx datocms projects:list
npm error Missing script: "datocms"

This is an npm/npx quirk: when the package name and binary name differ, it can't always find the CLI and bails out. pnpm, yarn, and bun all handled this case correctly — it's only npm users who got bitten. Now that the package and binary share the same name, the most natural command just works. As a bonus, we now own the unscoped datocms name on npm, so it can't get squatted.

What changes for you

  • New users: run npx datocms ... or npm i -g datocms. That's it.

  • Existing users on @datocms/cli: nothing to do. Your setup keeps working, and you'll transparently pick up the new datocms package as a dependency. Migrating is a one-line change in package.json whenever you feel like it.

  • Existing migration files importing from @datocms/cli/lib/cma-client-node: still work, no changes needed. New migration files use datocms/lib/cma-client-node.

Head over to the docs on configuring the CLI for installation details.

Content Management API

CMA limit raised for Developer Plan

We have raised the CMA limit of the developer plan from 10K to 25K monthly API calls, to make it easier to get started with projects on the free plan.

CLI New

CLI: Easier (and safer) project linking with OAuth

Setting up the DatoCMS CLI used to involve a clunky ritual of creating an API token in project settings, copying it, pasting it into an environment variable, and hoping you got it right. Repeat for every project. And since teams shared the same token, there was no way to tell who actually performed a given action in audit logs.

OAuth login is now the recommended way to authenticate. The new setup is fully guided: datocms login opens your browser, datocms link lets you search and select a project interactively with no tokens to copy, and no environment variables to configure. Every API call is tied to your personal identity, giving teams clear visibility over who made which changes.

During the authorization step, you can choose to grant the CLI access to all your projects or limit it to only the ones you select, so you stay in control of exactly which projects are exposed.

New commands

  • datocms login authenticates your DatoCMS account via OAuth. It opens your browser for a secure login flow. If the browser can't be opened, it falls back to a manual URL flow.

  • datocms link connects the current directory to a specific DatoCMS project. The interactive flow walks you through choosing a workspace, searching for a project, and configuring migration settings. Once linked, every CLI command in that directory automatically resolves an API token using your OAuth credentials. No environment variables needed.

  • datocms logout to remove credentials.

  • datocms whoami to check which account you're logged in as.

  • datocms unlink to disconnect a directory from a project.

What changes for existing users

Nothing breaks. The old profile:set and profile:remove commands still work, they just redirect to link and unlink under the hood. Existing scripts and CI/CD pipelines using DATOCMS_API_TOKEN or the --api-token flag are completely unaffected.

When a command needs an API token, the CLI now resolves it in this order: --api-token flag first, then environment variable, then linked project via OAuth. So your current setup always takes precedence.

Getting started

You can upgrade the CLI to the latest version with:

Terminal window
npm install -g @datocms/cli@latest

For teams that want user-level audit trails, the migration is straightforward: each team member runs datocms login once, then datocms link in each project directory. From that point on, all commands use personal credentials automatically.

You can still use DATOCMS_API_TOKEN in CI/CD or anywhere OAuth login isn't practical. If you prefer a custom environment variable name, you can configure it during datocms link.

OAuth authorizations can be reviewed and revoked at any time from your account settings, under "Authorized applications":

Explore the docs to get up to speed on Configuring the CLI.

Start using DatoCMS today
According to Gartner 89% of companies plan to compete primarily on the basis of customer experience this year. Don't get caught unprepared.
  • No credit card
  • Easy setup
Subscribe to our newsletter! 📥
One update per month. All the latest news and sneak peeks directly in your inbox.
support@datocms.com ©2026 Dato srl, all rights reserved P.IVA 06969620480