Heavy on video usage and want to set a specific frame as the thumbnail from within the CMS itself? Now you can.
TL;DR: You can now choose the poster frame for any video right from the asset editor. Choose the perfect moment, pin it, and that frame becomes the video's thumbnail everywhere. No re-encoding, no external tools, no guessing which frame the player will grab.
How it works
Open a video asset and you'll find a poster picker built right into the player:
Head to the frame you want. A pin on the seek bar marks the current poster frame, aligned precisely to the player's timeline so what you see is what you get.
Hover the pin and a large, high-resolution thumbnail shows exactly how that frame will look as the poster, sized to fit whether you're in the media area, a modal, or the sidebar.
Select Use current frame as poster and your choice saves instantly.
Under the hood
The chosen frame is stored as poster_time (in seconds) inside the upload's default_field_metadata, the direct video analogue of focal_point. Like the focal point, it's a non-localized, per-asset default, so it travels with the asset and is available through the Content Management API for your front end to consume when rendering posters.
Thumbnail selection is precise to the hundredth of a second. The time readout shows m:ss.cs, so you can land on an exact frame rather than a rough area — 1:04.27, not "somewhere around 1:04".
Getting correctly-sized responsive images used to mean adding a sizes prop that mirrored your CSS layout, and keeping it in sync as the design changed. No more. Update any DatoCMS framework SDK and you can stop dealing with that.
Responsive images were 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 on you: hand-write a sizes value mirroring your CSS layout, and keep it in sync every time the design changed.
Hello, sizes=auto!
Browsers now have a proper fix: sizes="auto". On a lazily-loaded image, it tells the browser to 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.
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":
Upgrade the package and your existing <Image> components start requesting right-sized files on their own.
Under the hood
If you pass an explicit sizes prop, or your responsiveImage GraphQL query already returns a sizes value, we never override it.
Images marked with the priority prop load eagerly, and sizes="auto" requires loading="lazy", so they keep their current behavior.
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 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, so there's no regression anywhere.
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.
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
1
{
2
"data":{
3
"id":"12345678",
4
"type":"upload",
5
"attributes":{
6
"default_field_metadata":{// Locales first, then fields
7
"en":{// Primary locale
8
"alt":"English alt text",
9
"title":"English title",
10
"custom_data":{
11
"english_custom_data":"hi"
12
},
13
"focal_point":{
14
"x":0.12,
15
"y":0.34
16
}
17
},
18
"it":{
19
"alt":"Italian alt text",
20
"title":"Italian title",
21
"custom_data":{
22
"italian_custom_data":"ciao"
23
},
24
"focal_point":{// This is silently ignored now; overridden by primary
25
"x":0.56,
26
"y":0.78
27
}
28
}
29
}
30
}
31
}
32
}
After opt-in migration: New field-keyed shape
1
{
2
"data":{
3
"id":"12345678",
4
"type":"upload",
5
"attributes":{
6
"default_field_metadata":{// Fields first, then locales
7
"alt":{
8
"en":"English alt text",
9
"it":"Italian alt text"
10
},
11
"title":{
12
"en":"English title",
13
"it":"Italian title"
14
},
15
"custom_data":{
16
"en":{
17
"english_custom_data":"hi"
18
},
19
"it":{
20
"italian_custom_data":"ciao"
21
}
22
},
23
"focal_point":{// No longer locale-specific
24
"x":0.12,
25
"y":0.34
26
}
27
}
28
}
29
}
30
}
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:
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.
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.
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.
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.
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.
Plugindevelopment — create a brand-new plugin from scratch with the Vite/React structure, picking the initial surfaces (field extensions, config screens, sidebars, pages, asset sources).
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!
// value position: the model's id and ref, generated from your project
5
await client.items.create({
6
item_type: Schema.Article.REF,
7
// …
8
});
9
10
if (item.relationships.item_type.data.id === Schema.Article.ID) {
11
// …
12
}
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:
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)) {
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.
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.
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
$npxdatocmsprojects:list
npmerrorMissingscript:"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.