Rolling Back Published Content Without a Redeploy

Within Draft State Management, rollback is the path back from a bad publish: an incorrect price, a legal statement published too early, a broken embed that crashes a page. This guide restores the previous content version in the CMS and pushes it through every cache tier, so the fix is live in seconds without rebuilding or redeploying the frontend.

The instinct in an incident is to redeploy, because a deploy is the one lever most developers trust to “clear everything”. For content problems it is the wrong lever. It takes minutes, it clears every cache on the site instead of the affected pages, it causes a regeneration storm against the CMS at the worst moment, and it does nothing about the bad content, which is still published in the CMS and will render again on the next request.

Redeploy versus targeted rollbackA redeploy rebuilds the site while the bad content remains published; a targeted rollback restores the previous version in the CMS, whose publish webhook invalidates only the affected tags and purges the CDN.Bad publishprice wrongRedeployminutes, all pagesBad contentstill publishedRestore versionin CMSPublish webhookaffected tagsFixed in secondsother pages cached
The content is the problem, so the fix belongs in the CMS; the caches only need to hear about it.

The Problem

An online retailer publishes a price change for a popular product with a missing digit: 29 instead of 299. Within a minute, the price appears on the product page, three category listings, the homepage carousel and a comparison table. The on-call developer sees the alert, triggers a redeploy and waits six minutes for the build. The redeploy finishes, the site shows the correct price for a moment, and then shows 29 again, because the entry in the CMS was never fixed: the first request after the deploy regenerated the product page from the still-published bad version. Meanwhile, the redeploy emptied every cached page, and the burst of regenerations pushed the CMS API into rate limiting, so unrelated pages showed errors for several minutes.

The correct sequence takes under a minute: restore the previous version of the product entry in the CMS, publish it, and let the normal publish webhook invalidate the product’s tags. The same machinery that made the bad price appear everywhere within a minute makes the good price appear everywhere just as quickly.

How Version Restores Work in Each CMS

Every major headless CMS keeps version history, but restores differ in whether they publish immediately:

Restoring a previous version by CMSHow Contentful, Sanity, Storyblok, Strapi and Hygraph expose version history and whether restoring a version publishes it or creates a draft first.CMSVersion historyRestore createsThenContentfulentry versions, snapshotsdraft from snapshotpublish to fire webhookSanitydocument history, revisionsdraft from revisionpublishStoryblokversions per storydraftpublishStrapi v5content history (paid plans)draftpublishHygraphversions per entrydraftpublish to PUBLISHED stage
In every case the restore must end in a publish, because only a publish fires the webhook that clears caches.

The common shape is “restore to draft, then publish”. That extra step is a safety feature, because it lets someone preview the restored version before it goes live, but in an incident it is also where rollbacks stall: someone restores the version and forgets to publish, and nothing changes on the site. A runbook that names the publish step explicitly removes that failure.

Implementation

For incidents that happen often enough to deserve tooling, a small rollback script restores and publishes in one command through the management API and then verifies the public page. The Contentful version below restores the entry to a given version, publishes it, and waits until the public URL reflects the change.

TypeScript
// scripts/rollback-entry.ts  (run: npx tsx scripts/rollback-entry.ts <entryId> <version> <publicUrl> <expectedText>)
import contentful from "contentful-management";

async function main(): Promise<void> {
  const [entryId, versionArg, publicUrl, expected] = process.argv.slice(2);
  if (!entryId || !versionArg || !publicUrl || !expected) throw new Error("usage: <entryId> <version> <publicUrl> <expectedText>");

  const client = contentful.createClient({ accessToken: process.env.CMA_TOKEN ?? "" });
  const env = await (await client.getSpace(process.env.CONTENTFUL_SPACE ?? "")).getEnvironment("master");

  // Snapshots hold every published version's fields.
  const entry = await env.getEntry(entryId);
  const snapshots = await entry.getSnapshots();
  const target = snapshots.items.find((s) => s.snapshot.sys.version === Number(versionArg));
  if (!target) throw new Error(`No snapshot with version ${versionArg}`);

  entry.fields = target.snapshot.fields;
  const updated = await entry.update();
  const published = await updated.publish(); // fires the publish webhook
  console.log(`Published entry ${entryId} at version ${published.sys.version}`);

  // Verify the public page, bypassing browser caches only (CDN purges come from the webhook).
  for (let attempt = 1; attempt <= 12; attempt++) {
    const res = await fetch(publicUrl, { headers: { "Cache-Control": "no-cache" } });
    const html = await res.text();
    if (html.includes(expected)) {
      console.log(`Public URL shows expected content after ${attempt * 5}s`);
      return;
    }
    await new Promise((r) => setTimeout(r, 5000));
  }
  throw new Error("Public URL did not show the restored content within 60s; check webhook and purge logs");
}

main().catch((err: unknown) => {
  console.error(err);
  process.exit(1);
});

The script deliberately relies on the normal publish webhook for cache invalidation rather than calling revalidateTag itself. If the page does not update, the webhook path is broken, and that is worth discovering: the same bug would also delay every ordinary publish.

Rolling Back Several Entries Together

Many bad publishes touch more than one entry. A campaign release might update a landing page, three promo blocks and the navigation at once, and a partial rollback can leave the site in a state that never existed before: the old landing page with the new navigation pointing at a promo that the landing page no longer mentions. Treat multi-entry rollbacks as releases. Collect the entries published in the incident window from the audit log, restore each to its version from just before the window, and publish them together, using the CMS’s release or bulk publish feature where available. Because each publish fires its own webhook, the frontend invalidates every affected tag within the same few seconds, and readers never see a mixed state for longer than the webhook burst takes.

Record the version numbers you restored in the incident ticket. If the rollback itself turns out to be wrong, for example because one of the entries had a legitimate change in the same window, those numbers let you roll forward precisely instead of guessing.

Configuration Reference

Item Recommendation Why
Management token for rollback separate, scoped to the environment, stored in the incident vault Available when needed, not in everyday CI.
Webhook topics include publish and unpublish Restores end in a publish; emergency unpublishes need to clear caches too.
Tagging entry id tags on every fetch A restore of one entry reaches every page that shows it.
Verification poll the public URL for the expected text Confirms the fix end to end, not just in the CMS.
Audit record who rolled back, from which version, and why Governance and post-incident review.

Gotchas & Edge Cases

  • References changed too. If the bad publish also changed referenced entries, such as a new promo block, restoring the parent alone leaves the child published. Check which entries were published in the same window and restore them together, ideally as a release.
  • Localized fields. Snapshots restore every locale. If only one locale was wrong, restoring the whole entry also reverts legitimate changes in other locales. Restore the affected fields only.
  • Emergency unpublish. When content must disappear immediately, such as a legal takedown, unpublish rather than restore. Make sure unpublish webhooks invalidate caches and that pages handle the missing entry, as described in handling references to unpublished entries.
  • Client caches in open tabs. Readers who already have the page open keep the old content until their client cache revalidates. For high-stakes content, push the invalidation to open tabs as well.
  • Search engines and social cards. Crawlers and social networks may have captured the bad content. Request recrawling of the affected URLs and refresh social card caches through the platforms’ debugging tools.

Worked Example

After the retailer’s price incident, the team wrote a two-line runbook. First: restore the previous version of the entry in the CMS and publish it. Second: run the verification script against the product URL. They also added dependency tags so listing pages were invalidated together with the product. The next incident, a wrong shipping promise on a campaign page, was fixed in 70 seconds by a content editor without developer involvement, and the monitoring showed no regeneration spike, because only four pages were invalidated.

Time to fix a bad publish, two incidents comparedMinutes from alert to correct content everywhere for the price incident handled by redeploy and the shipping-promise incident handled by targeted rollback.Redeploy, then CMS fix19 minRestore + publish in CMS1.2 min
The redeploy took longer and still failed until the CMS entry was fixed; the targeted rollback fixed four pages and nothing else.

Rollout Checklist

  • Confirm version history is enabled for every content type that carries prices, legal text or claims.
  • Write a runbook that ends in an explicit publish step and a public-URL check.
  • Tag every fetch with entry ids so restores reach listings and teasers.
  • Store a scoped management token for incident use.
  • Rehearse a rollback in staging once per quarter.

Frequently Asked Questions

When is a redeploy the right response?

When the problem is in code or configuration, not in content: a broken component, a bad environment variable, a wrong cache header. For those, roll back the deployment through your hosting platform, which is usually instant, rather than building a new one.

Can editors roll back without developers?

Yes, if the runbook is written for them and webhooks are reliable. Editors already know version history; the only missing piece is usually knowing that the restore must be published and how to confirm the fix on the live site.

How do I find which entries a bad publish touched?

The CMS audit log or the webhook delivery log lists every publish with its timestamp. Filter to the incident window and restore those entries together.