DEV Community

Andrei Kniazev
Andrei Kniazev

Posted on • Updated on

Migration from tiptap v.1 to tiptap v.2

If you are working with tiptap v.1 and want to upgrade to tiptap v.2 here is the story of how I did it for my project LoreHub.

LoreHub's stack
Back - .net 6, ef 6, c#
Front - Vue.js 2, Veutify, Pinia

Initial setup

I have a Vue.js component that does a put request that will update the description on the server. It will do two things:

  1. Updates in the description's snapshot table.
  2. Insert into the description's history table.

basic structure

Here are parts from the Vue component. As you can see I initialize the editor and on an update, it fires debounce function. Debounce function allows it to do requests only if the user will stop updating the content for 3 seconds. The debounce function is from lodash.

import debounce from "lodash-es/debounce";

function initEditor () {
    this.editor = new Editor({
        extensions: [
          // extensions
        onUpdate: ({ getJSON }) => {
          this.content = getJSON();
          this.isSaving = true;

function updateDocumentDescriptionOnServerDebounce: debounce(async function () {
    await this.updateDocumentDescriptionOnServer();
}, 3000),

async updateDocumentDescriptionOnServer() {
    try {
        this.serverError = null;
        // pinia store action - put to WebApi
        await useDescriptionStore().updateDescription(
        this.isSavingValue = false;
      } catch (e) {
        // some error handling
        this.serverError = e;
        this.isSavingValue = false;
Enter fullscreen mode Exit fullscreen mode

Time to upgrade

First of all, I do everything that is mentioned in the official upgrade guide -

So it took some time, but then I face a problem that requires me to migrate standard extensions names. Imagine I have gigabytes of data in my database that contains JSONs and I need to iterate through all of this to rename extensions types. It is not an option and there should be a better way to do it.

How do I solve it? My idea was to create a migration function that will do it on the front end. But I don't want it to run every time the description is loaded. The solution will be to save the state in the database that the migration was performed and I don't want it to do it again.

I decided to change my WebAPI and database. I introduce a new column 'EditorVersion'. Because I have too many raws to update I set this field as nullable, without a default value.

Example entity framework migration:

// ef 6 migration
    name: "EditorVersion",
    table: "Descriptions_History",
    type: "nvarchar(max)",
    nullable: true);

    name: "EditorVersion",
    table: "Descriptions_Description",
    type: "nvarchar(max)",
    nullable: true);
Enter fullscreen mode Exit fullscreen mode

After this I've created a v2 description.get action on the back that returns not just JSON, but JSON and editorVersion.

Description {
    value   string
    nullable: true
    editorVersion   string
    nullable: true
Enter fullscreen mode Exit fullscreen mode

If editorVersion is null it will run this migration on the front:

migrateExtensions(content) {
      for (const node of content) {
        // tiptap 2 migrate extensions type from v.1 to v2.
        if (node.type === "bullet_list") node.type = "bulletList";
        if (node.type === "code_block") node.type = "codeBlock";
        if (node.type === "hard_break") node.type = "hardBreak";
        if (node.type === "horizontal_rule") node.type = "horizontalRule";
        if (node.type === "list_item") node.type = "listItem";
        if (node.type === "ordered_list") node.type = "orderedList";
        if (node.type === "table_cell") node.type = "tableCell";
        if (node.type === "table_header") node.type = "tableHeader";
        if (node.type === "table_row") node.type = "tableRow";
        if (node.type === "todo_list") node.type = "taskList";
        if (node.type === "todo_item") node.type = "todo_item";

        // recursion
        if (node.content && node.content.length > 0)
Enter fullscreen mode Exit fullscreen mode

After the migration is done it will perform put request that will send updated JSON and set editor version to 'tiptap_v2'.


It took me about three days of work to migrate from tiptap v1 to tiptap v2. It includes migration for a custom extension that uses a Vue router for links. The tiptap's team did a good job with the migration guide, thank you. It was straightforward and easy to do.

Overall I like the new tiptap's API and this little hack allows to do a lazy migration 😊.

I hope this guide will help you do the migration and if you have any questions feel free to ask.

Top comments (0)