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)

This post blew up on DEV in 2020:

js visualized

🚀⚙️ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! 🥳

Happy coding!