{"id":98110,"date":"2026-10-14T22:14:00","date_gmt":"2026-10-15T02:14:00","guid":{"rendered":"https:\/\/overcentral.com\/en\/?p=98110"},"modified":"2026-09-29T08:04:33","modified_gmt":"2026-09-29T12:04:33","slug":"emdas-revisions-api-custom-versioning-98110","status":"publish","type":"post","link":"https:\/\/overcentral.com\/en\/emdas-revisions-api-custom-versioning-98110\/","title":{"rendered":"Build Custom Versioning with EmDash&#8217;s Revisions API"},"content":{"rendered":"<p>Versioning in most CMSs is an afterthought \u2014 a history log you rarely use and even more rarely automate. EmDash flips that completely. Its Revisions API was built for programmatic control, not just audit trails. Most developers overlook this power, treating revisions as a safety net rather than a foundation for workflows. By the end of this guide, you&#8217;ll see revisions as a programmable layer that can power custom approval chains, content scheduling, and collaborative editing \u2014 not just a place to undo mistakes.<\/p>\n<h2>What Makes EmDash&#8217;s Revisions API Different?<\/h2>\n<p>WordPress stores revisions as serialized post meta, tightly coupled to the editor. EmDash treats revisions as first-class data objects. Every content type \u2014 posts, pages, custom types \u2014 automatically generates revision records on each save. But the API exposes far more than a simple list of past versions.<\/p>\n<p>The Revisions API gives you:<\/p>\n<ul>\n<li><strong>Raw access to revision data<\/strong> \u2014 timestamps, author, content diff, status changes<\/li>\n<li><strong>Programmatic creation<\/strong> \u2014 you can generate a revision without a human editor saving<\/li>\n<li><strong>Rollback endpoints<\/strong> \u2014 revert to any revision in a single call<\/li>\n<li><strong>Webhook triggers<\/strong> \u2014 every revision event fires a hook that plugins and workers can listen to<\/li>\n<\/ul>\n<p>These capabilities let you build versioning systems that WordPress could never support natively.<\/p>\n<h2>Why Build Custom Versioning?<\/h2>\n<p>Out-of-the-box versioning solves \u201coops, I need to undo that.\u201d Custom versioning solves real business problems:<\/p>\n<ul>\n<li><strong>Compliance<\/strong> \u2014 prove exactly who changed what and when, with immutable audit trails<\/li>\n<li><strong>Content scheduling<\/strong> \u2014 create revisions that become active on a future date<\/li>\n<li><strong>Multi-step approvals<\/strong> \u2014 a draft moves through editorial stages, each stage creating a revision<\/li>\n<li><strong>A\/B testing<\/strong> \u2014 compare two revision branches and deploy the winner<\/li>\n<\/ul>\n<p>EmDash&#8217;s API makes each of these possible in under 200 lines of code.<\/p>\n<h2>Prerequisites<\/h2>\n<p>Before you start, you need:<\/p>\n<ul>\n<li>An EmDash instance (local or deployed) \u2014 version 0.1.0 or later<\/li>\n<li>An API token with <code>content:write<\/code>codecodecode and <code>content:read<\/code>codecodecode permissions<\/li>\n<li>Node.js 18+ or a runtime that supports fetch<\/li>\n<\/ul>\n<p>Generate your token in EmDash admin under Settings \u2192 API Tokens. Scope it to only what your custom versioning system needs.<\/p>\n<h2>Step 1: Understanding the Revision Data Structure<\/h2>\n<p>Every content revision in EmDash is stored as a JSON object with this shape:<\/p>\n<p>&#8220;`json<\/p>\n<p>{<\/p>\n<p>  &#8220;id&#8221;: &#8220;rev_abc123&#8221;,<\/p>\n<p>  &#8220;documentId&#8221;: &#8220;doc_xyz789&#8221;,<\/p>\n<p>  &#8220;version&#8221;: 5,<\/p>\n<p>  &#8220;data&#8221;: { &#8230; },<\/p>\n<p>  &#8220;diff&#8221;: { &#8230; },<\/p>\n<p>  &#8220;author&#8221;: { &#8220;id&#8221;: &#8220;user_1&#8221;, &#8220;name&#8221;: &#8220;Jane&#8221; },<\/p>\n<p>  &#8220;createdAt&#8221;: &#8220;2026-04-10T14:23:00Z&#8221;,<\/p>\n<p>  &#8220;metadata&#8221;: { &#8220;reason&#8221;: &#8220;Updated pricing section&#8221; }<\/p>\n<p>}<\/p>\n<p>&#8220;`<\/p>\n<p>The <code>data<\/code>codecodecode field contains the full content snapshot at that revision. The <code>diff<\/code>codecodecode field holds a structured diff (added, removed, changed paths). <code>metadata<\/code>codecodecode is a free-form object you can populate when creating a revision programmatically.<\/p>\n<p><strong>Expected outcome:<\/strong> You understand exactly what the API returns and can map it to your own data structures.<\/p>\n<h2>Step 2: Fetching Revisions for a Document<\/h2>\n<p>To get all revisions for a specific piece of content, use the endpoint:<\/p>\n<p>&#8220;`<\/p>\n<p>GET \/api\/revisions?documentId=doc_xyz789&amp;limit=50<\/p>\n<p>&#8220;`<\/p>\n<p>The response is paginated. Include <code>cursor<\/code>codecodecode for the next page.<\/p>\n<p>&#8220;`javascript<\/p>\n<p>async function getRevisions(documentId, limit = 50) {<\/p>\n<p>  const res = await fetch(<code>https:\/\/your-site.com\/api\/revisions?documentId=${documentId}&amp;limit=${limit}<\/code>codecodecode, {<\/p>\n<p>    headers: { Authorization: <code>Bearer ${API_TOKEN}<\/code>codecodecode }<\/p>\n<p>  });<\/p>\n<p>  return res.json();<\/p>\n<p>}<\/p>\n<p>&#8220;`<\/p>\n<p>You can filter by author, date range, or status change. For example, to find all revisions where the status changed to &#8220;published&#8221;:<\/p>\n<p>&#8220;`<\/p>\n<p>GET \/api\/revisions?documentId=doc_xyz789&amp;statusChange=published<\/p>\n<p>&#8220;`<\/p>\n<p><strong>Expected outcome:<\/strong> You can retrieve any revision set and filter programmatically.<\/p>\n<h2>Step 3: Creating a Custom Diff Viewer<\/h2>\n<p>The built-in EmDash admin shows a basic diff. But you can build a richer comparison using the <code>diff<\/code>codecodecode field. The diff is stored as a JSON patch (RFC 6902). Here&#8217;s a minimal renderer:<\/p>\n<p>&#8220;`javascript<\/p>\n<p>function renderDiff(diff) {<\/p>\n<p>  return diff.map(op =&gt; {<\/p>\n<p>    if (op.op === &#8216;replace&#8217;) {<\/p>\n<p>      return <code><\/p>\n<div class=\"diff-line diff-change\">${op.path}: ${op.value}<\/div>\n<p><\/code>codecodecode;<\/p>\n<p>    }<\/p>\n<p>    if (op.op === &#8216;add&#8217;) {<\/p>\n<p>      return <code><\/p>\n<div class=\"diff-line diff-add\">+ ${op.path}: ${op.value}<\/div>\n<p><\/code>codecodecode;<\/p>\n<p>    }<\/p>\n<p>    if (op.op === &#8216;remove&#8217;) {<\/p>\n<p>      return <code><\/p>\n<div class=\"diff-line diff-remove\">- ${op.path}<\/div>\n<p><\/code>codecodecode;<\/p>\n<p>    }<\/p>\n<p>    return &#8221;;<\/p>\n<p>  }).join(&#8216;n&#8217;);<\/p>\n<p>}<\/p>\n<p>&#8220;`<\/p>\n<p>This gives you a side-by-side or inline diff that you can embed in a custom editorial dashboard, a Slack webhook, or an email notification.<\/p>\n<p><strong>Expected outcome:<\/strong> A reusable diff component that works outside the admin panel.<\/p>\n<h2>Step 4: Implementing Programmatic Rollback<\/h2>\n<p>Rolling back to a previous revision is a single API call:<\/p>\n<p>&#8220;`<\/p>\n<p>POST \/api\/documents\/doc_xyz789\/rollback<\/p>\n<p>Body: { &#8220;revisionId&#8221;: &#8220;rev_abc123&#8221; }<\/p>\n<p>&#8220;`<\/p>\n<p>But you should add safeguards. Here&#8217;s a safe rollback function that checks permissions and logs the action:<\/p>\n<p>&#8220;`javascript<\/p>\n<p>async function rollbackToRevision(documentId, revisionId, reason) {<\/p>\n<p>  \/\/ Validate the revision exists<\/p>\n<p>  const rev = await getRevision(revisionId);<\/p>\n<p>  if (!rev) throw new Error(&#8216;Revision not found&#8217;);<\/p>\n<p>  \/\/ Execute rollback<\/p>\n<p>  const res = await fetch(<code>https:\/\/your-site.com\/api\/documents\/${documentId}\/rollback<\/code>codecodecode, {<\/p>\n<p>    method: &#8216;POST&#8217;,<\/p>\n<p>    headers: {<\/p>\n<p>      Authorization: <code>Bearer ${API_TOKEN}<\/code>codecodecode,<\/p>\n<p>      &#8216;Content-Type&#8217;: &#8216;application\/json&#8217;<\/p>\n<p>    },<\/p>\n<p>    body: JSON.stringify({<\/p>\n<p>      revisionId,<\/p>\n<p>      metadata: { reason, rolledBackBy: currentUser }<\/p>\n<p>    })<\/p>\n<p>  });<\/p>\n<p>  if (!res.ok) throw new Error(&#8216;Rollback failed&#8217;);<\/p>\n<p>  \/\/ The rollback itself creates a new revision, so you can audit it<\/p>\n<p>  return res.json();<\/p>\n<p>}<\/p>\n<p>&#8220;`<\/p>\n<p><strong>Expected outcome:<\/strong> A production-ready rollback function that leaves an audit trail.<\/p>\n<h2>Step 5: Automating Revision Pruning<\/h2>\n<p>Revisions accumulate. EmDash doesn&#8217;t auto-delete old ones (yet). Build a cleanup worker that runs daily:<\/p>\n<p>&#8220;`javascript<\/p>\n<p>\/\/ Cloudflare Worker cron trigger: 0 0 <em> <\/em> *<\/p>\n<p>async function pruneRevisions(env) {<\/p>\n<p>  const thirtyDaysAgo = Date.now() &#8211; 30 <em> 24 <\/em> 60 <em> 60 <\/em> 1000;<\/p>\n<p>  \/\/ Fetch all documents, then for each, delete revisions older than <a href=\"https:\/\/overcentral.com\/en\/ai-influencers-fail-30-days-96592\/\" title=\"Why Most AI Influencers Fail to Earn in 30 Days\" data-iacss-internal=\"1\">30 days<\/a><\/p>\n<p>  const docs = await fetch(&#8216;https:\/\/your-site.com\/api\/documents&#8217;, {<\/p>\n<p>    headers: { Authorization: <code>Bearer ${env.API_TOKEN}<\/code>codecodecode }<\/p>\n<p>  }).then(r =&gt; r.json());<\/p>\n<p>  for (const doc of docs) {<\/p>\n<p>    const revisions = await getRevisions(doc.id);<\/p>\n<p>    const oldRevisions = revisions.filter(r =&gt; new Date(r.createdAt) &lt; thirtyDaysAgo);<\/p>\n<p>    for (const rev of oldRevisions) {<\/p>\n<p>      await fetch(<code>https:\/\/your-site.com\/api\/revisions\/${rev.id}<\/code>codecodecode, {<\/p>\n<p>        method: &#8216;DELETE&#8217;,<\/p>\n<p>        headers: { Authorization: <code>Bearer ${env.API_TOKEN}<\/code>codecodecode }<\/p>\n<p>      });<\/p>\n<p>    }<\/p>\n<p>  }<\/p>\n<p>}<\/p>\n<p>&#8220;`<\/p>\n<p><strong>Expected outcome:<\/strong> Automated cleanup that prevents database bloat <a href=\"https:\/\/overcentral.com\/en\/reinstall-star-wars-zero-company-without-losing-saves-78453\/\" title=\"How to Reinstall Star Wars Zero Company Without Losing Saves\" data-iacss-internal=\"1\">without losing<\/a> recent history.<\/p>\n<h2>Comparison: Versioning Strategies<\/h2>\n<table class=\"mw-table\">\n<thead>\n<tr>\n<th>Approach<\/th>\n<th>Storage<\/th>\n<th>Rollback Speed<\/th>\n<th>Audit Trail<\/th>\n<th>External Access<\/th>\n<th>AI-Friendly<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong>EmDash Revisions API<\/strong><\/td>\n<td>JSON in D1\/SQLite<\/td>\n<td>Instant (single API call)<\/td>\n<td>Full metadata, author, reason<\/td>\n<td>Yes, via API<\/td>\n<td>Yes (MCP server reads diff)<\/td>\n<\/tr>\n<tr>\n<td><strong>Git-based versioning<\/strong><\/td>\n<td>Git repository<\/td>\n<td>Minutes (clone + checkout)<\/td>\n<td>Commit messages only<\/td>\n<td>Requires git access<\/td>\n<td>Limited (no structured diff)<\/td>\n<\/tr>\n<tr>\n<td><strong>Manual database snapshots<\/strong><\/td>\n<td>SQL dump<\/td>\n<td>Hours (restore entire DB)<\/td>\n<td>None unless logged<\/td>\n<td>No<\/td>\n<td>No<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The EmDash approach wins for speed, granularity, and programmability.<\/p>\n<h2>Advanced: Connecting Revisions to AI Agents<\/h2>\n<p>EmDash ships with a built-in MCP server. You can expose your custom versioning system as a tool. For example, an agent can:<\/p>\n<ul>\n<li>&#8220;Roll back the homepage to the version from last Tuesday&#8221;<\/li>\n<li>&#8220;Show me all revisions where Jane made changes to the pricing table&#8221;<\/li>\n<li>&#8220;Create a new revision of the about page with these updated team photos&#8221;<\/li>\n<\/ul>\n<p>The agent reads the revision data through MCP and performs actions via the API. This turns your versioning system into an automated editorial assistant.<\/p>\n<h2>When the General Advice Fails<\/h2>\n<p>Custom versioning works well for structured content \u2014 blog posts, pages, product descriptions. It breaks down for highly dynamic content like real-time collaborative documents or user-generated content streams. In those cases, the revision API&#8217;s snapshot model creates too much overhead. You&#8217;d be better off with operational transforms (OT) or CRDTs. But for 90% of content management use cases, EmDash&#8217;s Revisions API is the right foundation.<\/p>\n<p><a href=\"https:\/\/overcentral.com\/en\/build-money-confidence-financial-plan-80228\/\" title=\"Create a Financial Plan to Build Your Money Confidence\" data-iacss-internal=\"1\">Build your<\/a> custom system. Test it in the playground. And when you deploy, remember: every revision is data you can use \u2014 not just data you can undo.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Versioning in most CMSs is an afterthought \u2014 a history log you rarely use and even more rarely automate. EmDash flips that completely. Its Revisions API was built for programmatic control, not just audit trails. Most developers overlook this power, treating revisions as a safety net rather than a foundation for workflows. By the end [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":100546,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98110.png","fifu_image_alt":"Build Custom Versioning with EmDash's Revisions API","footnotes":""},"categories":[31],"tags":[],"class_list":["post-98110","post","type-post","status-publish","format-standard","has-post-thumbnail","category-technology"],"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98110.png","fifu_image_alt":"Build Custom Versioning with EmDash's Revisions API","_links":{"self":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98110","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/comments?post=98110"}],"version-history":[{"count":1,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98110\/revisions"}],"predecessor-version":[{"id":100547,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98110\/revisions\/100547"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media\/100546"}],"wp:attachment":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media?parent=98110"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/categories?post=98110"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/tags?post=98110"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}