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 beforeconst article = await client.items.find<Schema.Article>(id);
// value position: the model's id and ref, generated from your projectawait 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 shapeFieldValueInNestedResponse<T, K>— what you get when reading withnested: trueFieldValueInRequest<T, K>— whatclient.items.create/updateexpects
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 guardif (isBlockWithItemOfType(Schema.CtaBlock.ID, node)) { // node.item.attributes is now typed as CtaBlock}
// curried predicateconst 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
nullorundefined: 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.