
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeYou're absolutely right that _createdAt doesn't necessarily reflect when a document was first published—it just shows when the document was created in the system, which could have been while it was still a draft.
For tracking first published and last published dates, the recommended approach is to create custom fields and use a custom publish action to set them. Here's how:
Add two fields to your schema:
{
name: 'firstPublished',
type: 'datetime',
title: 'First Published',
readOnly: true
},
{
name: 'lastPublished',
type: 'datetime',
title: 'Last Published',
readOnly: true
}Then create a custom publish action that sets firstPublished only on the first publish, and updates lastPublished every time:
import {useDocumentOperation} from 'sanity'
export function createPublishAction(originalPublishAction) {
return (props) => {
const {patch, publish} = useDocumentOperation(props.id, props.type)
const {draft, published} = props
return {
...originalPublishAction(props),
onHandle: () => {
// Set firstPublished only if document hasn't been published before
if (!published) {
patch.execute([{set: {firstPublished: new Date().toISOString()}}])
}
// Always update lastPublished
patch.execute([{set: {lastPublished: new Date().toISOString()}}])
// Perform the publish
publish.execute()
props.onComplete()
}
}
}
}This is actually documented as an example in Sanity's schema recipes!
While you could parse the History API's NDJSON response to find the first transaction where a document was published (looking for transactions that created the document without the drafts. prefix), it's unnecessarily complex and has limitations based on your history retention period (3 days for Free, 90 days for Growth, 365 days for Enterprise).
The custom field approach is more reliable, performant, and gives you instant access to these timestamps in your queries without needing to process transaction history.
For last published, you can actually use _updatedAt on the published document (not the draft), which will reflect when it was last published. Just make sure you're querying the published version specifically by filtering out drafts or using the published perspective in your queries.
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store