Revision History Plugin

This plugin is only available as a paid add-on to a TinyMCE subscription.
This feature is only available for TinyMCE 7.0 and later.

The Revision History plugin offers users the ability to view document changes over time and restore previous revisions effortlessly.

Interactive example

  • TinyMCE

  • HTML

  • JS

  • Edit on CodePen

<textarea id="revisionhistory">
  <p><img style="display: block; margin-left: auto; margin-right: auto;" title="Tiny Logo" src="https://www.tiny.cloud/docs/images/logos/android-chrome-256x256.png" alt="TinyMCE Logo" width="128" height="128"></p>
  <h2 style="text-align: center;">Welcome to the TinyMCE Revision History demo!</h2>

  <p style="text-align: center;">Try out the Revision History feature by typing in the editor and then clicking the Revision History icon in the toolbar.</p>

  <p style="text-align: center;">And visit the <a href="https://www.tiny.cloud/pricing">pricing page</a> to learn more about our Premium plugins.</p>

  <h2>A simple table to play with</h2>

  <table style="border-collapse: collapse; width: 100%;" border="1">
    <thead>
      <tr style="text-align: left;">
        <th>Product</th>
        <th>Cost</th>
        <th>Really?</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>TinyMCE Cloud</td>
        <td>Get started for free</td>
        <td><strong>Yes!</strong></td>
      </tr>
      <tr>
        <td>Plupload</td>
        <td>Free</td>
        <td><strong>Yes!</strong></td>
      </tr>
    </tbody>
  </table>

  <h2>Got questions or need help?</h2>

  <ul>
    <li>Our <a class="mceNonEditable" href="https://www.tiny.cloud/docs/tinymce/7/">documentation</a> is a great resource for learning how to configure TinyMCE.</li>
    <li>Have a specific question? Try the <a href="https://stackoverflow.com/questions/tagged/tinymce" target="_blank" rel="noopener"><code>tinymce</code> tag at Stack Overflow</a>.</li>
    <li>We also offer enterprise grade support as part of <a href="https://www.tiny.cloud/pricing">TinyMCE premium subscriptions</a>.</li>
  </ul>

  <h2>Found a bug?</h2>

  <p>If you think you have found a bug please create an issue on the <a href="https://github.com/tinymce/tinymce/issues">GitHub repo</a> to report it to the developers.</p>

  <h2>Finally…</h2>

  <p>Don’t forget to check out our other product <a href="http://plupload.com" target="_blank">Plupload</a>, your ultimate upload solution featuring HTML5 upload support.</p>

  <p>Thanks for supporting TinyMCE! We hope it helps you and your users create great content.</p>

  <p>All the best from the TinyMCE team.</p>
</textarea>
const getRandomDelay = () => {
  const minDelay = 500;
  const maxDelay = 2000;
  return Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
};

const lightRevisions = [
  {
    revisionId: '3',
    author: {
      id: 'tiny.husky',
      name: 'A Tiny Husky',
      avatar: '../_images/tiny-husky.jpg',
    },
    createdAt: '2023-11-25T08:30:21.578Z'
  },
  {
    revisionId: '2',
    author: {
      id: 'tiny.user',
      name: 'A Tiny User',
      avatar: '../_images/logos/android-chrome-192x192.png',
    },
    createdAt: '2023-11-24T22:26:21.578Z',
  },
  {
    revisionId: '1',
    author: {
      id: 'tiny.user',
      name: 'A Tiny User',
      avatar: '../_images/logos/android-chrome-192x192.png',
    },
    createdAt: '2023-11-23T20:26:21.578Z',
  },
];

const revisionhistory_fetch = () =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(
        lightRevisions
          .sort((a, b) =>
            new Date(a.createdAt) < new Date(b.createdAt) ? -1 : 1
          )
          .reverse()
      );
    }, getRandomDelay());
  });

const revisions = [
  {
    revisionId: '3',
    createdAt: '2023-11-24T22:26:21.578Z',
    author: {
      id: 'tiny.husky',
      name: 'A Tiny Husky',
      avatar: '../_images/tiny-husky.jpg',
    },
    content: `
      <p><img style="display: block; margin-left: auto; margin-right: auto;" title="Tiny Logo" src="https://www.tiny.cloud/docs/images/logos/android-chrome-256x256.png" alt="TinyMCE Logo" width="128" height="128"></p>
      <h2 style="text-align: center;">Welcome to the TinyMCE editor demo!</h2>
      <h2>A simple table to play with</h2>
      <table style="border-collapse: collapse; width: 100%;" border="1">
      <thead>
      <tr>
      <th>Product</th>
      <th>Cost</th>
      <th>Really?</th>
      </tr>
      </thead>
      <tbody>
      <tr>
      <td>TinyMCE</td>
      <td>Free</td>
      <td>YES!</td>
      </tr>
      <tr>
      <td>Plupload</td>
      <td>Free</td>
      <td>YES!</td>
      </tr>
      </tbody>
      </table>
      <h2>Found a bug?</h2>
      <p>If you think you have found a bug please create an issue on the <a href="https://github.com/tinymce/tinymce/issues">GitHub repo</a> to report it to the developers.</p>
      <h2>Finally ...</h2>
      <p><s>Don't forget to check out our other product <a href="http://www.plupload.com" target="_blank" rel="noopener">Plupload</a>, your ultimate upload solution featuring HTML5 upload support.</s></p>
      <p>Thanks for supporting TinyMCE! We hope it helps you and your users create great content.<br>All the best from the TinyMCE team.</p>
    `,
  },
  {
    revisionId: '2',
    createdAt: '2023-11-25T08:30:21.578Z',
    author: {
      id: 'tiny.user',
      name: 'A Tiny User',
      avatar: '../_images/logos/android-chrome-192x192.png',
    },
    content: `
      <p><img style="display: block; margin-left: auto; margin-right: auto;" title="Tiny Logo" src="https://www.tiny.cloud/docs/images/logos/android-chrome-256x256.png" alt="TinyMCE Logo" width="128" height="128"></p>
      <h2 style="text-align: center;">Welcome to the TinyMCE editor demo!</h2>
      <h2>Got questions or need help?</span></h2>
      <ol>
      <li>Our <a href="../">documentation</a> is a great resource for learning how to configure TinyMCE.</li>
      <li>Have a specific question? Try the <a href="https://stackoverflow.com/questions/tagged/tinymce" target="_blank" rel="noopener"><code>tinymce</code> tag at Stack Overflow</a>.</li>
      <li>We also offer enterprise grade support as part of <a href="../../../../pricing">TinyMCE premium plans</a>.</li>
      </ol>
      <h2>A simple table to play with</h2>
      <table style="border-collapse: collapse; width: 100%;" border="1">
      <thead>
      <tr>
      <th>Product</th>
      <th>Cost</th>
      <th>Really?</th>
      </tr>
      </thead>
      <tbody>
      <tr>
      <td>TinyMCE</td>
      <td>Free</td>
      <td>YES!</td>
      </tr>
      <tr>
      <td>Plupload</td>
      <td>Free</td>
      <td>YES!</td>
      </tr>
      </tbody>
      </table>
      <h2>Found a bug?</h2>
      <p>If you think you have found a bug please create an issue on the <a href="https://github.com/tinymce/tinymce/issues">GitHub repo</a> to report it to the developers.</p>
      <h2>Finally ...</h2>
      <p>Don't forget to check out our other product <a href="http://www.plupload.com" target="_blank" rel="noopener">Plupload</a>, your ultimate upload solution featuring HTML5 upload support.</p>
      <p>Thanks for supporting TinyMCE! We hope it helps you and your users create great content.<br>All the best from the TinyMCE team.</p>
    `,
  },
  {
    revisionId: '1',
    createdAt: '2023-11-29T10:11:21.578Z',
    author: {
      id: 'tiny.user',
      name: 'A Tiny User',
      avatar: '../_images/logos/android-chrome-192x192.png',
    },
    content: `
      <p><img style="display: block; margin-left: auto; margin-right: auto;" title="Tiny Logo" src="https://www.tiny.cloud/docs/images/logos/android-chrome-256x256.png" alt="TinyMCE Logo" width="128" height="128"></p>
      <h2 style="text-align: center;">Welcome to the TinyMCE editor demo!</h2>
      <h2>Got questions or need help?</h2>
      <ul>
      <li>Our <a href="../">documentation</a> is a great resource for learning how to configure TinyMCE.</li>
      <li>Have a specific question? Try the <a href="https://stackoverflow.com/questions/tagged/tinymce" target="_blank" rel="noopener"><code>tinymce</code> tag at Stack Overflow</a>.</li>
      <li>We also offer enterprise grade support as part of <a href="../../../../pricing">TinyMCE premium plans</a>.</li>
      </ul>
      <h2>A simple table to play with</h2>
      <table style="border-collapse: collapse; width: 100%;" border="1">
      <thead>
      <tr>
      <th>Product</th>
      <th>Cost</th>
      <th>Really?</th>
      </tr>
      </thead>
      <tbody>
      <tr>
      <td>TinyMCE</td>
      <td>Free</td>
      <td>YES!</td>
      </tr>
      <tr>
      <td>Plupload</td>
      <td>Free</td>
      <td>YES!</td>
      </tr>
      </tbody>
      </table>
      <h2>Found a bug?</h2>
      <p>If you think you have found a bug please create an issue on the <a href="https://github.com/tinymce/tinymce/issues">GitHub repo</a> to report it to the developers.</p>
      <h2>Finally ...</h2>
      <p>Don't forget to check out our other product <a href="http://www.plupload.com" target="_blank" rel="noopener">Plupload</a>, your ultimate upload solution featuring HTML5 upload support.</p>
      <p>Thanks for supporting TinyMCE! We hope it helps you and your users create great content.<br>All the best from the TinyMCE team.</p>
    `,
  }
];

const revisionhistory_fetch_revision = (_editor, revision) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      const newRevision = revisions.find((r) => r.revisionId === revision.revisionId);
      if (newRevision === undefined) {
        reject(`Revision ${revision.revisionId} is not found`);
      } else {
        resolve(newRevision);
      }
    }, getRandomDelay());
  });

tinymce.init({
  selector: 'textarea#revisionhistory',
  height: 800,
  plugins: 'revisionhistory',
  toolbar: 'revisionhistory',
  content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }',
  revisionhistory_fetch,
  revisionhistory_fetch_revision,
  revisionhistory_author: {
    id: 'john.doe',
    name: 'John Doe'
  },
  revisionhistory_display_author: true
});
This feature is only supported when TinyMCE is run in classic mode. It is not supported in inline mode. For more information on the differences between the editing modes, see Classic editing mode.

How it works

The Revision History view is accessible via either the revisionhistory toolbar button or menu button within the View menu.

The key components are:

  1. In the Revision History view header, there are two buttons.

    • Restore this version: Sets the selected revision’s content to the editor and closes the view. Note: the button is disabled for the initial and draft revisions.

    • Close: Closes the Revision History view.

  2. The readonly diff view presents the changes between the selected revision and its immediate predecessor, clearly highlighting for easy recognition. The changes are also color-coded for clarity:

    • Red: Removed content.

    • Green: New content.

    • Yellow: Content being modified. Modifications to HTML content implies attributes or formatting (e.g. bold, italic, etc.).

  3. The revisions sidebar displays all available document revisions. When a revision is selected, the diff view is updated accordingly.

The default highlighting colors can be customized using the revisionhistory_css_url option.
The Revision History plugin processes commented HTML as valid content but disregards it during the revision comparison process. Revisions containing only commented content appear as empty in the view.

Basic setup

To setup the Revision History plugin in the editor:

  • add revisionhistory to the plugins option in the editor configuration;

  • add revisionhistory to the toolbar option in the editor configuration;

  • add revisionhistory_fetch option to the editor configuration;

For example:

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  plugins: 'revisionhistory',
  toolbar: 'revisionhistory',
  revisionhistory_fetch: () => Promise.resolve([]), // Replace this with an API request to get saved revisions
});

Understanding revision types

The Revision History plugin offers three revision types:

  1. Initial: This revision is generated during the TinyMCE Loaded event, capturing the editor’s initial content.

  2. Draft: Generated upon opening the Revision History, this revision reflects the editor’s current content. When included, it becomes the latest revision in the revisions list.

  3. Saved: These revisions are fetched from the client’s storage when opening the Revision History view, using the revisionhistory_fetch and revisionhistory_fetch_revision options.

When working with a document that has no saved revisions, the Revision History plugin typically maintains two revisions: initial and draft. If no changes have been made to the content since initialization, the initial revision is not displayed, as it is identical to the draft revision.

For documents with saved revisions, the initial revision is disregarded, assuming it already exists among the saved revisions. To include the initial content, add it as a revision using the revisionhistory_fetch option.

Data structure

Revision

The revision is an Object that contains the following fields:

Field Type Required? Description

revisionId

string

required

The unique string ID of the revision.

createdAt

string

required

A UTC datetime string in ISO-8061 format.

content

string

optional

HTML string of the revision content. Empty string is considered as valid content.

author

Author Object

optional

The author of the revision.

Author

The author is an Object that represents the author or creator of a revision. It contains the following fields:

Field Type Required? Description

id

string

required

The unique string ID of the author.

name

string

optional

The name of the revision author. If not provided, the default value is Anonymous.

avatar

string

optional

The URL of the author’s avatar image. If not provided or invalid, the Revision History will use a generated avatar using the author’s initials.

Options

The following configuration options affect the behavior of the Revision History plugin.

revisionhistory_fetch

The revisionhistory_fetch function retrieves saved revisions and is called when the user opens the Revision History view.

The revisionhistory_fetch function is required for the Revision History plugin to function.

It expects an asynchronous function that returns a Promise which resolves to an array of revisions.

The Revision History plugin does not sort results before displaying them, allowing integrators to sort the results according to their preference. Tiny recommends to sort the results in reverse chronological order, as this makes it easier for users to identify revisions by date.

Type: Function (Returns a Promise)

Input parameters: None

Return data: Array of Revision Objects

Example of revisionhistory_fetch response

const revisions = [
  {
    revisionId: '3',
    createdAt: '2023-11-29T10:11:21.578Z',
    content: `
      <h2>Welcome to TinyMCE Docs!</h2>
      <p>Here is some content that is <strong>bold</strong> and <em>italic</em>.</p>
    `,
  },
  {
    revisionId: '2',
    createdAt: '2023-11-25T08:30:21.578Z',
    content: `
      <p>Welcome to TinyMCE Docs!</p>
      <p>Here is some content that is bold and italic.</p>
    `,
  },
  {
    revisionId: '1',
    createdAt: '2023-11-24T22:26:21.578Z',
    content: `
      <p>Welcome to Tinymce!</p>
    `,
  }
];

Example: Using revisionhistory_fetch

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  plugins: 'revisionhistory',
  toolbar: 'revisionhistory',
  revisionhistory_fetch: () =>
    fetch('<API URL>')  // Update the URL and response handling code according to your API
    .then((response) => response.json())
    .then((data) => data)
    .catch((error) => console.log('Failed to get revisions\n' + error))
});

revisionhistory_fetch_revision

When a revision is selected, the plugin uses this option to update the selected or closest revision that has no content. The function is expected to return a Promise that resolves with an updated revision.

Type: Function (Returns a Promise)

Input parameters:

Field Type Required? Description

editor

Editor

required

The current editor instance (useful if there are multiple editors on the page).

revision

Revision Object

required

The targeted revision.

Return data: Revision Object

Example: Using revisionhistory_fetch_revision

const lightRevisions = [
  {
    revisionId: '3',
    createdAt: '2023-11-29T10:11:21.578Z',
  },
  {
    revisionId: '2',
    createdAt: '2023-11-25T08:30:21.578Z',
  },
  {
    revisionId: '1',
    createdAt: '2023-11-24T22:26:21.578Z',
  }
];

const revisions = [
  {
    revisionId: '3',
    createdAt: '2023-11-29T10:11:21.578Z',
    content: `
      <h2>Welcome to TinyMCE Docs!</h2>
      <p>Here is some content that is <strong>bold</strong> and <em>italic</em>.</p>
    `,
  },
  {
    revisionId: '2',
    createdAt: '2023-11-25T08:30:21.578Z',
    content: `
      <p>Welcome to TinyMCE Docs!</p>
      <p>Here is some content that is bold and italic.</p>
    `,
  },
  {
    revisionId: '1',
    createdAt: '2023-11-24T22:26:21.578Z',
    content: `
      <p>Welcome to Tinymce!</p>
    `,
  }
];

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  plugins: 'revisionhistory',
  toolbar: 'revisionhistory',
  revisionhistory_fetch: () => Promise.resolve(lightRevisions),
  revisionhistory_fetch_revision: (_editor, revision) => new Promise((resolve) => {
  let newRevision = null;
  for (let i = 0; i < revisions.length; i++) {
    const temp = revisions[i];
    if (temp.revisionId === revision.revisionId) {
      newRevision = temp;
      break;
    }
  }
  resolve(newRevision);
  })
});

revisionhistory_author

This option configures the author for the initial and draft revisions.

Type: Author Object

Example: using revisionhistory_author

tinymce.init({
  selector: 'textarea',  // Change this value according to your HTML
  plugins: 'revisionhistory',
  toolbar: 'revisionhistory',
  revisionhistory_fetch: () => Promise.resolve([]),
  revisionhistory_display_author: true,
  revisionhistory_author: {
    id: 'john.doe',
    name: 'John Doe',
    avatar: 'https://example.com/avatar.jpg'
  }
});

revisionhistory_display_author

This option configures the display of the revision’s author. When set to true, the author’s name appears in each revision within the Revision History sidebar. If the author’s name is not provided, it defaults to Anonymous.

Type: Boolean

Default value: false

Example: using revisionhistory_display_author

tinymce.init({
  selector: 'textarea',  // Change this value according to your HTML
  plugins: 'revisionhistory',
  toolbar: 'revisionhistory',
  revisionhistory_fetch: () => Promise.resolve([]),
  revisionhistory_display_author: true
});

revisionhistory_css_url

This option sets the location where a CSS file containing the styles for change annotations should be loaded from. It can be used along with revisionhistory_diff_classes to override the default CSS classes.

Ensure that the file includes all CSS classes expected by revisionhistory_diff_classes.

Type: String

Default value: '${pluginUrl}/css/revisionhistory.css'

Example: using revisionhistory_css_url

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  plugins: 'revisionhistory',
  toolbar: 'revisionhistory',
  revisionhistory_fetch: () => Promise.resolve([]), // Required option for the plugin - replace with actual API request
  revisionhistory_css_url: './revisionhistory.css'
});

revisionhistory_diff_classes

This option configures the CSS classes being applied to the change annotations. This option can be used in combination with the revisionhistory_css_url to provide custom styles to the change annotations.

The CSS class names must exist in the revisionhistory_css_url.

Type: Object

Default value: addition, removal, modification

{
  addition: 'tox-revisionhistory__annotation--added',
  removal: 'tox-revisionhistory__annotation--removed',
  modification: 'tox-revisionhistory__annotation--modified'
}

Example: using revisionhistory_diff_classes

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  plugins: 'revisionhistory',
  toolbar: 'revisionhistory',
  revisionhistory_fetch: () => Promise.resolve([]), // Required option for the plugin - replace with actual API request
  revisionhistory_css_url: './revisionhistory.css',
  revisionhistory_diff_classes: {
    addition: 'added',
    removal: 'removed',
    modification: 'modified'
  }
});

Toolbar buttons

The Revision History plugin provides the following toolbar buttons:

Toolbar button identifier Description

revisionhistory

Opens the Revision History view.

These toolbar buttons can be added to the editor using:

The Revision History plugin provides the following menu items:

Menu item identifier Default Menu Location Description

revisionhistory

View

Opens the Revision History view.

These menu items can be added to the editor using:

Commands

The Revision History plugin provides the following TinyMCE commands.

Command Description

revisionHistory

Toggle the Revision History view

Examples
tinymce.activeEditor.execCommand('revisionHistory');

Events

The Revision History plugin provides the following events.

The following events are provided by the Revision History plugin.

Name Data Description

VersionRestored

{ revisionId: string }

Fired when a version is restored.

APIs

The Revision History plugin provide the following APIs.

interface PluginAPI {
  diff: (originalHTML: string, changedHTML: string) => string
}

diff

Returns a HTML string with annotations indicating the changes in originalHTML relative to changedHTML. The styling of annotations can be customized via revisionhistory_diff_classes option.

This feature is only available for TinyMCE 7.1 and later.
Example
const input1 = `<p>The banner is <span style="color: rgb(224, 62, 45);">red</span></p>`;
const input2 = `<p>The flag is <span style="color: rgb(22, 145, 121);">red</span></p>`;
tinymce.activeEditor.plugins.revisionhistory.diff(input1, input2);
/*
Expected output:
<p>The
 <del class="tox-revisionhistory__annotation--removed">banner</del><ins class="tox-revisionhistory__annotation--added">flag</ins>
 is
<ins class="tox-revisionhistory__annotation--modified"><span style="color: rgb(22, 145, 121);">red</span></ins>
</p>
*/