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.
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
npminstall-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":
The Astro and Next.js starter kits now come with a private DatoCMS plugin already wired up, giving you a ready-made foundation to start building custom editor experiences from day one, with no separate repo, and no manual installation.
Because the Plugin SDK and datocms-react-ui component library are built on React, this feature is available in the React-based starters (Astro and Next.js) but not in the SvelteKit or Nuxt starters.
Build custom plugins right inside your project
The plugin lives alongside your site code and is served as a regular page (/private-datocms-plugin). On your first deploy it installs itself automatically via the post-deploy hook — zero manual setup in the dashboard. The source mirrors the structure of official DatoCMS plugins.
The included config screen links straight to the docs to help you get started:
The Astro starter has also been upgraded to Astro 6 along with all official integrations (@astrojs/node, @astrojs/react, @astrojs/check) and @datocms/cli v4. The minimum Node version is now 22.
Every file uploaded to the DatoCMS Media Area is now automatically scanned for viruses and malware. No configuration, no opt-in or workflow changes required from your side.
How it works
The moment a file is uploaded, a background scan is queued automatically. Editors can continue working since there's no blocking step or wait time. Within seconds, each file is assigned one of four statuses:
Clean: no threats detected and file is served normally
Infected: a threat was detected and the file is automatically quarantined
Skipped: the file exceeds the scanner's size or type limits and could not be assessed. Treat these files with appropriate caution
Failed: an error occurred during scanning, and will be retried automatically up to 6 times with exponential backoff. If it still cannot be scanned, then the file remains in a failed state
ℹ️ If an asset is replaced with a new version, the antivirus scan runs again automatically on the new file.
Quarantined files
When a threat is detected, infected files are automatically quarantined and DatoCMS will:
Remove the file from public storage
Purge it from the CDN cache, and
Keep the upload record visible in the Media Area so editors can see it was flagged, but the file URL will no longer serve any content
Editors should replace the asset to restore functionality.
For projects using a custom storage bucket, DatoCMS does not have permission to delete or move files from your storage. In this case, the file will still be accessible from your own bucket even after being flagged, and the upload record will be marked as infected, and editors will see the file path so they can remove it manually. The warning UI in the Media Area will reflect this.
Dashboard Changes
Infected files are surfaced throughout the Media Area:
A "Threat detected" badge appears on the upload card in grid, masonry, and table views. On smaller cards, this collapses to an icon with a tooltip
Opening an infected file replaces the normal preview with a warning screen that explains the situation, shows the specific threat name (useful for investigation), and prompts the editor to replace the asset
For custom storage projects, the warning is adjusted to show the file path and advise manual removal from the bucket
Editors can filter uploads by antivirus status (clean, infected, skipped, failed, pending) directly in the Media Area search. This filter is not available in the Content Delivery API.
Scan results are delivered in real time with the antivirus status in the dashboard updating live without requiring a page refresh, and Webhooks are fired on status changes, so you can build integrations that react to scan results, for example, getting a Slack alert when an infected file is detected in your project.
API Access
The antivirus status is also available on every upload object via the CMA, under a new meta.antivirus field:
"meta": {
"antivirus":{
"status":"infected",
"scanned_at":"2026-03-27T18:51:00Z",
"threat_name":"Trojan.GenericKD.12345"
}
}
The object includes the scan status, the timestamp of the last scan, and the threat name when applicable. It's worth knowing that antivirus scan results are preserved when forking environments, with no rescanning needed, and when duplicating a project, infected files are automatically excluded from the copy to prevent propagation.
When using the Visual Editing feature, the overlay color used to highlight editable areas can now be customized via a hue property (accepts values 0–359) in the configuration.
Previously, editable regions were always indicated with an orange highlight, which could create a usability problem for sites with orange-heavy designs, where the overlay could blend into the page.
All the SDKs now have a configurable hue property to allow the highlight to stand out regardless of your brand palette.