Publish a record
When the draft/published system is enabled for a model, records will remain in a Draft status until they are Published.
When publishing a record, you can choose to either publish the whole record, or just some of its locales / non-localized content. This is similar to how the "Publish" dropdown button in the UI works.
This is the default behavior when you don't provide a request body.
This will publish the entire record, including all its localized and non-localized fields.
Do not include a request body at all — not even an empty object {}.
(No request body)
PUT https://site-api.datocms.com/items/:item_id/publish HTTP/1.1Authorization: Bearer YOUR-API-TOKENAccept: application/jsonX-Api-Version: 3Content-Type: application/vnd.api+json(No request body)
curl -g 'https://site-api.datocms.com/items/:item_id/publish' \ -X PUT \ -H "Authorization: Bearer YOUR-API-TOKEN" \ -H "Accept: application/json" \ -H "X-Api-Version: 3" \ -H "Content-Type: application/vnd.api+json"(No request body)
await fetch("https://site-api.datocms.com/items/:item_id/publish", { method: "PUT", headers: { Authorization: "Bearer YOUR-API-TOKEN", Accept: "application/json", "X-Api-Version": "3", "Content-Type": "application/vnd.api+json", },});Published item:
HTTP/1.1 200 OKContent-Type: application/jsonCache-Control: cache-control: max-age=0, private, must-revalidateX-RateLimit-Limit: 30X-RateLimit-Remaining: 28
-- BEFORE PUBLISH --meta.status: "draft"meta.published_at: null└ Item "T4m4tPymSACFzsqbZS65WA" (item_type: "UZyfjdBES8y2W2ruMEHSoA") ├ title: "My first blog post!" └ slug: "my-first-blog-post"
-- AFTER PUBLISH --meta.status: "published"meta.published_at: 2026-04-23T10:02:27.599+01:00└ Item "T4m4tPymSACFzsqbZS65WA" (item_type: "UZyfjdBES8y2W2ruMEHSoA") ├ title: "My first blog post!" └ slug: "my-first-blog-post"Selective publishing is used when you don't want to publish the entire record. Instead, you can publish a combination of:
- Zero or more specified locales
- And/or all of this record's non-localized fields
In this example, we will only publish the en locale. The it and es versions of localized_title will not be published, and will retain their previously published titles. non_localized_field will also keep its previously published value.
Publishing only the "en" locale
PUT https://site-api.datocms.com/items/:item_id/publish HTTP/1.1Authorization: Bearer YOUR-API-TOKENAccept: application/jsonX-Api-Version: 3Content-Type: application/vnd.api+json
{ "data": { "type": "selective_publish_operation", "attributes": { "content_in_locales": [ "en" ], "non_localized_content": false } }}Publishing only the "en" locale
curl -g 'https://site-api.datocms.com/items/:item_id/publish' \ -X PUT \ -H "Authorization: Bearer YOUR-API-TOKEN" \ -H "Accept: application/json" \ -H "X-Api-Version: 3" \ -H "Content-Type: application/vnd.api+json" \ --data-binary '{"data":{"type":"selective_publish_operation","attributes":{"content_in_locales":["en"],"non_localized_content":false}}}'Publishing only the "en" locale
await fetch("https://site-api.datocms.com/items/:item_id/publish", { method: "PUT", headers: { Authorization: "Bearer YOUR-API-TOKEN", Accept: "application/json", "X-Api-Version": "3", "Content-Type": "application/vnd.api+json", }, body: JSON.stringify({ data: { type: "selective_publish_operation", attributes: { content_in_locales: ["en"], non_localized_content: false }, }, }),});Published item with new English title. The Italian localized field and non-localized slug remain unchanged.
HTTP/1.1 200 OKContent-Type: application/jsonCache-Control: cache-control: max-age=0, private, must-revalidateX-RateLimit-Limit: 30X-RateLimit-Remaining: 28
-- Published version BEFORE --└ Item "T4m4tPymSACFzsqbZS65WA" (item_type: "UZyfjdBES8y2W2ruMEHSoA") ├ title │ ├ en: "My first blog post!" │ └ it: "Il mio primo post!" └ slug: "my-first-blog-post"
-- Published version AFTER --└ Item "T4m4tPymSACFzsqbZS65WA" (item_type: "UZyfjdBES8y2W2ruMEHSoA") ├ title │ ├ en: "My first blog post! (updated)" │ └ it: "Il mio primo post!" └ slug: "my-first-blog-post"TypeScript typing
Publishing 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.publish. 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.publish<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 parent records aren't published, those parent records will published as well. When recursive is false or not specified, an UNPUBLISHED_PARENT error will occur in such cases.
Body parameters
Must be exactly "selective_publish_operation".
Publish only the specified locales & non-localized content (see following attributes). To publish the entire record, simply avoid passing a request body to the endpoint.
Array of valid locale codes in this project to publish.
["en"]
, ["en", "it"]
Whether non-localized content will be published
Returns
Returns a resource object of type item.