NOTE: This plugin is deprecated. Use Sanity Scheduled Publishing instead.
Schedule and view your editorial calendar, right where you store your content. Prioritize and get organized on the fly with a visual calendar in your Studio.
- Graphical representation of your editorial calendar
- At-a-glance schedule review
- Quick prioritization of content to review
- More time to create content, less time spent scheduling
Run the following command in your studio folder using the Sanity CLI:
sanity install content-calendar
To use the calendar, the plugin needs to know which document types to display and what fields to use for scheduling documents.
Create or open the config file found in config/content-calendar.json
. The file is automatically created the first time the studio starts after adding the plugin. Add your document type and field combinations.
{
"types": [
{
"type": "post",
"field": "publishedAt",
"titleField": "title"
}
],
"calendar": {
"event": {
"dateFormat": "MMMM, dd yyyy",
"timeFormat": "hh:mm a",
"showAuthor": "false"
}
},
"filterWarnings": []
}
titleField
also supports nested properties, like title.en
.
In the configuration values, you can also modify how the dates and times are formatted on the calendar, as well as being able to show the document author.
Note: the type.field option signals when this post should be scheduled to release, this requires us to add a "date" or "datetime" field to the document you want to enable scheduling for.
If edits are made to a Document after it has been Scheduled, a Warning will show. However, if you want to hide this, use the filterWarnings
key. This will evaluate the Document for a matching condition, so for example:
"filterWarnings": [
{"_type": "article", "title.en": "Hello!"},
{"isLive": true},
]
If an Event matches all of the conditions in an any of the Objects in the Array, the Warning will be hidden.
In the example above:
- A document with the type
article
AND atitle.en
value ofHello!
will not show Warnings OR - Any document with an
isLive
field value oftrue
will not show Warnings
The plugin adds the Schedule, Unschedule, and Reschedule actions to your configured documents by implementing the part part:@sanity/base/document-actions/resolver
.
Because of a current limitation (as of version 2.0.9), these will not compose with your own implementation of this part for resolving document actions. For more on the parts system, see the part system documentation.
To implement the plugin, add the custom scheduling actions with your custom action.
// import the default document actions
import defaultResolve from 'part:@sanity/base/document-actions'
import {addActions} from 'sanity-plugin-content-calendar/build/register'
const CustomAction = () => ({
label: 'Hello world',
onHandle: () => {
window.alert('👋 Hello from custom action')
}
})
export default function resolveDocumentActions(props) {
const actions = [...defaultResolve(props), CustomAction]
return addActions(props, actions)
}
Much like custom document actions, if you have implemented custom document badges with part:@sanity/base/document-badges/resolver
you need to add in the Scheduled badge from the plugin.
import defaultResolve from 'part:@sanity/base/document-badges'
import {addBadge} from 'sanity-plugin-content-calendar/build/register'
const CustomBadge = () => {
return {
label: 'Custom',
title: 'Hello I am a custom document badge',
color: 'success'
}
}
export default function resolveDocumentBadges(props) {
const badges = [...defaultResolve(props), CustomBadge]
return addBadge(props, badges)
}
This plugin does not perform the publishing of documents on its own, as it is just a Studio plugin running in an editors' browser. In order to actually perform the scheduled publishing, a script needs to run either periodically, or at the given publishing times to perform the publish action.
We advise setting up a cronjob running for instance every minute that checks if any document should be published and then perform that action. A full script that does this is represented below.
When the publish event eventually occurs, any newer draft will be discarded. This is why the plugin warns you if you make further changes to a document after you schedule it. If you don't want to lose the newer changes an editor will need to Reschedule them. The plugin will prompt for this with a warning in the calendar view and an updated document action.
To publish documents, you can set up a serverless function to poll for pending scheduled events and perform the action. Typically, this can be run from a cronjob every minute or from another scheduled action.
Alternatively, you could schedule this script to run at specific times by using webhooks and listening for new schedule.metadata
documents.
const sanityClient = require('@sanity/client')
const client = sanityClient({
projectId: 'your-project-id',
dataset: 'your-dataset-name',
// Need a write token in order to read schedule metadata and publish documents
token: 'your-write-token',
useCdn: false
})
// Query for any scheduled publish events that should occur
const query = `* [_type == "schedule.metadata" && !(_id in path("drafts.**")) && datetime <= now()]`
const publish = async (metadata, client) => {
const dataset = client.config().dataset
const id = metadata.documentId
const rev = metadata.rev
// Fetch the draft revision we should publish from the History API
const uri = `/data/history/${dataset}/documents/drafts.${id}?revision=${rev}`
const revision = await client
.request({uri})
.then(response => response.documents.length && response.documents[0])
if (!revision) {
// Here we have a situation where the scheduled revision does not exist
// This can happen if the document was deleted via Studio or API without
// unscheduling it first.
console.error('Could not find document revision to publish', metadata)
return
}
// Publish it
return (
client
.transaction()
// Publishing a document is simply writing it to the dataset without a
// `drafts.` prefix. The `documentId` field on the metadata already does
// not include this prefix, but the revision we fetched probably does, so
// we overwrite it here.
.createOrReplace(Object.assign({}, revision, {_id: id}))
// Then we delete any current draft.
.delete(`drafts.${id}`)
// And finally we delete the schedule medadata, since we're done with it.
.delete(metadata._id)
.commit()
)
}
client
.fetch(query)
.then(response => Promise.all(response.map(metadata => publish(metadata, client))))