Unpublish a record
In a model where the draft/published system is enabled, Published records can subsequently be Unpublished in order to return them to Draft status.
When unpublishing a record, you can choose to either unpublish the whole record, or just some of its locales, similar to how the "Unpublish" dropdown button in the UI sidebar works.
This is the default behavior when you don't provide a request body.
This will unpublish the entire record, including all its localizations.
Do not include a request body at all — not even an empty object {}.
import { buildClient, inspectItem } from "@datocms/cma-client-node";import type * as Schema from "./schema.js";
/* * BlogPost * ├─ title: string * └─ slug: slug */
async function run() { const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "fyq6ADkeTL6Ryk7s98xmHw";
// The record starts as Published. The attributes are the same before and // after unpublishing; what changes is the record's publication state, // which lives in `meta` (status / published_at). const beforeUnpublish = await client.items.find<Schema.BlogPost>(itemId); console.log("-- BEFORE UNPUBLISH --"); console.log(`meta.status: "${beforeUnpublish.meta.status}"`); console.log(`meta.published_at: ${beforeUnpublish.meta.published_at}`); console.log(inspectItem(beforeUnpublish));
const unpublishedItem = await client.items.unpublish<Schema.BlogPost>(itemId);
console.log("-- AFTER UNPUBLISH --"); console.log(`meta.status: "${unpublishedItem.meta.status}"`); console.log(`meta.published_at: ${unpublishedItem.meta.published_at}`); console.log(inspectItem(unpublishedItem));}
run();-- BEFORE UNPUBLISH --meta.status: "published"meta.published_at: 2026-04-23T10:02:26.737+01:00└ Item "fyq6ADkeTL6Ryk7s98xmHw" (item_type: "UZyfjdBES8y2W2ruMEHSoA") ├ title: "My first blog post!" └ slug: "my-first-blog-post"
-- AFTER UNPUBLISH --meta.status: "draft"meta.published_at: null└ Item "fyq6ADkeTL6Ryk7s98xmHw" (item_type: "UZyfjdBES8y2W2ruMEHSoA") ├ title: "My first blog post!" └ slug: "my-first-blog-post"Selective unpublishing is used when you only want to unpublish certain localizations instead of the whole record.
Please note: You can only unpublish locales that are currently published within a specific record. If you try to unpublish a record's locale that is already unpublished (i.e. in draft state) or doesn't exist in the record at all (even if the project has that locale), you will get a VALIDATION_INVALID error on the content_in_locales field.
import { buildClient, inspectItem } from "@datocms/cma-client-node";import type * as Schema from "./schema.js";
/* * BlogPost * ├─ title: string (localized) * └─ slug: slug */
async function run() { const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "fyq6ADkeTL6Ryk7s98xmHw";
// Read the current published version so we can compare it to the new // published version after the selective unpublish. const publishedBefore = await client.items.find<Schema.BlogPost>(itemId, { version: "published", }); console.log("-- Published version BEFORE --"); console.log(inspectItem(publishedBefore));
// Unpublish only the `it` locale — the `en` locale and the non-localized // fields stay published. await client.items.unpublish<Schema.BlogPost>(itemId, { content_in_locales: ["it"], });
// Re-read the published version: the `it` translation is gone; `en` and // `slug` are unchanged. const publishedAfter = await client.items.find<Schema.BlogPost>(itemId, { version: "published", }); console.log("-- Published version AFTER --"); console.log(inspectItem(publishedAfter));}
run();-- Published version BEFORE --└ Item "fyq6ADkeTL6Ryk7s98xmHw" (item_type: "UZyfjdBES8y2W2ruMEHSoA") ├ title │ ├ en: "My first blog post!" │ └ it: "Il mio primo post!" └ slug: "my-first-blog-post"
-- Published version AFTER --└ Item "fyq6ADkeTL6Ryk7s98xmHw" (item_type: "UZyfjdBES8y2W2ruMEHSoA") ├ title │ └ en: "My first blog post!" └ slug: "my-first-blog-post"TypeScript typing
Unpublishing a record without typed schemas gives you back a record whose attributes are all unknown — any downstream code that reads from it is fighting TypeScript. The single biggest lever you have is passing a generated Schema.X marker as the generic on items.unpublish. TypeScript then knows the exact shape of the returned record — its field names, types, and block structures — so reads are typed end-to-end:
import * as Schema from "./schema";
const record = await client.items.unpublish<Schema.Article>("record-id");record.title; // typed, not unknownFor the exact type of a specific field on the returned record (to annotate a helper or intermediate variable), index ApiTypes.Item<Schema.Article>["field_api_key"]. See the full TypeScript guide for how to generate schema.ts and the complete pattern.
Query parameters
When recursive is true, if the record belongs to a tree-like collection, and any of the children records are published, those children records will unpublished as well. When recursive is false or not specified, a PUBLISHED_CHILDREN error will occur in such cases.
Body parameters
Array of locales to publish. They must be currently published in this record. To unpublish all locales, do NOT use this parameter, but instead unpublish the entire record by leaving the body blank (see example above).
["en"]
, ["en", "it"]
Returns
Returns a resource object of type item