Show examples in:
Javascript HTTP
Content Management API > Record

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.1
Authorization: Bearer YOUR-API-TOKEN
Accept: application/json
X-Api-Version: 3
Content-Type: application/vnd.api+json

(No request body)

Terminal window
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 OK
Content-Type: application/json
Cache-Control: cache-control: max-age=0, private, must-revalidate
X-RateLimit-Limit: 30
X-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:

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.1
Authorization: Bearer YOUR-API-TOKEN
Accept: application/json
X-Api-Version: 3
Content-Type: application/vnd.api+json
{
"data": {
"type": "selective_publish_operation",
"attributes": {
"content_in_locales": [
"en"
],
"non_localized_content": false
}
}
}

Publishing only the "en" locale

Terminal window
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 OK
Content-Type: application/json
Cache-Control: cache-control: max-age=0, private, must-revalidate
X-RateLimit-Limit: 30
X-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 unknown

For 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

recursive boolean

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

For this endpoint, the body is not required and can be entirely omitted.
type string Required

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.

attributes.content_in_locales Required

Array of valid locale codes in this project to publish.

Type: Array<string>
Examples: ["en"] , ["en", "it"]
attributes.non_localized_content boolean Required

Whether non-localized content will be published

Returns

Returns a resource object of type item.