Tiptap Edit workflow

Build a workflow that allows the AI to edit your Tiptap documents with precise, efficient operations.

See the source code on GitHub.

Tech stack

Project overview

This demo uses the AI Toolkit's Tiptap Edit workflow to apply edit operations to the document in real-time. The workflow supports replacing, inserting before, and inserting after nodes.

Installation

Create a Next.js project:

npx create-next-app@latest tiptap-edit-workflow

Install the core Tiptap packages and the Vercel AI SDK for OpenAI:

npm install @tiptap/react @tiptap/starter-kit ai @ai-sdk/react @ai-sdk/openai zod uuid

Install the Tiptap AI Toolkit:

Pro package

The AI Toolkit is a pro package. Before installation, set up access to the private NPM registry by following the private registry guide.

npm install @tiptap-pro/ai-toolkit @tiptap-pro/ai-toolkit-tool-definitions

Server setup

Create an API endpoint that uses the Vercel AI SDK to call the OpenAI model.

If your backend is in another programming language than TypeScript, see this guide.

Inside the API endpoint, create and configure the Tiptap Edit workflow using the createTiptapEditWorkflow function. The workflow includes a ready-to-use system prompt that instructs the AI model on how to generate edit operations.

Additionally, you need to include these two properties in the user message:

  • content: The content of the document to be edited (obtained from tiptapRead)
  • task: The task to be performed by the AI. For example, Make the text more formal.
  • context: (Optional) Additional context or background information related to the task.

As the AI model generates its response, the API endpoint streams the operations to the client.

// app/api/tiptap-edit-workflow/route.ts
import { openai } from '@ai-sdk/openai'
import { createTiptapEditWorkflow } from '@tiptap-pro/ai-toolkit-tool-definitions'
import { Output, streamText } from 'ai'

export async function POST(req: Request) {
  const { content, task, context } = await req.json()

  // Create and configure the Tiptap Edit workflow (with the default settings).
  // It includes the ready-to-use system prompt and the output schema.
  const workflow = createTiptapEditWorkflow()

  const result = streamText({
    model: openai('gpt-5-mini'),
    // System prompt
    system: workflow.systemPrompt,
    // User message
    prompt: JSON.stringify({
      content,
      task,
      context,
    }),
    output: Output.object({ schema: workflow.zodOutputSchema }),
    // If you use gpt-5-mini, set the reasoning effort to minimal to improve the
    // response time.
    providerOptions: {
      openai: {
        reasoningEffort: 'minimal',
      },
    },
  })

  return result.toTextStreamResponse()
}

Client setup

Create a React component that renders the editor and applies the edits in real-time.

First, when the editing process starts, call the tiptapRead method of the AI Toolkit to read the document. The method returns the content in a format that is optimized for fast, precise edits.

Then, call the API endpoint to start the workflow. The component uses Vercel AI SDK's useObject hook to handle streaming, so that the response is received bit by bit and the edits are applied in real-time.

Every time the response changes, call the tiptapEditWorkflow method of the AI Toolkit to apply the edits to the document in real-time. The edits are applied immediately as they are received.

// app/tiptap-edit-workflow/page.tsx
'use client'

import { experimental_useObject as useObject } from '@ai-sdk/react'
import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import {
  AiToolkit,
  type TiptapReadResult,
  getAiToolkit,
  tiptapEditWorkflowOutputSchema,
} from '@tiptap-pro/ai-toolkit'
import { useEffect, useRef, useState } from 'react'
import { v4 as uuid } from 'uuid'

export default function Page() {
  const editor = useEditor({
    immediatelyRender: false,
    extensions: [StarterKit, AiToolkit],
    content: `<h1>Document Editor</h1><p>This is a sample document that can be edited by AI.</p>`,
  })

  const [workflowId, setWorkflowId] = useState('')
  const tiptapReadResultRef = useRef<TiptapReadResult | null>(null)
  const [task, setTask] = useState('Make the text more formal and professional')

  const { submit, isLoading, object } = useObject({
    api: '/api/tiptap-edit-workflow',
    schema: tiptapEditWorkflowOutputSchema,
  })

  const operations = object?.operations ?? []

  // Stream partial results as they arrive
  useEffect(() => {
    if (!editor || !operations) return

    const toolkit = getAiToolkit(editor)
    if (!tiptapReadResultRef.current) return

    toolkit.tiptapEditWorkflow({
      operations,
      workflowId,
      hasFinished: !isLoading,
      tiptapReadResult: tiptapReadResultRef.current,
    })
  }, [operations, workflowId, editor, isLoading])

  if (!editor) return null

  const editDocument = () => {
    const toolkit = getAiToolkit(editor)

    // Obtain the content of the document to be edited
    const readResult = toolkit.tiptapRead()
    tiptapReadResultRef.current = readResult

    // Each workflow must have a unique ID
    setWorkflowId(uuid())

    // Call the API endpoint to start the workflow
    submit({ content: readResult.content, task })
  }

  return (
    <div>
      <EditorContent editor={editor} />

      <input
        type="text"
        value={task}
        onChange={(e) => setTask(e.target.value)}
        placeholder="Enter editing task..."
      />

      <button onClick={editDocument} disabled={isLoading}>
        {isLoading ? 'Editing...' : 'Edit Document'}
      </button>
    </div>
  )
}

End result

With additional CSS styles, the result is a polished document editing application with real-time AI editing:

See the source code on GitHub.

Show the AI Caret

You can add the AiCaret extension to display a cursor that indicates where the AI is editing the document. This gives users real-time visual feedback during streaming.

import { AiCaret, AiToolkit, getAiToolkit } from '@tiptap-pro/ai-toolkit'

const editor = useEditor({
  extensions: [StarterKit, AiToolkit, AiCaret],
})

See the AI Caret guide for configuration options and CSS styles.

Edit part of the document

To edit only a sub-section of the document, set the range option. This argument takes a Range with the region to edit.

For example, to edit only the selected content:

const range = editor.state.selection

// Read only the selected part of the document
const { content } = toolkit.tiptapRead({ range })

const operations = callApi(content)

// Apply operations in the same range
toolkit.tiptapEditWorkflow({
  operations,
})

For large documents, you can use the tiptapReadChunks method to split the document into chunks and process them in parallel. Each chunk includes a Range with the region of that chunk.

Show a review UI

To let users review changes, configure the review options. Changes will show up as suggestions that users can accept or reject.

toolkit.tiptapEditWorkflow({
  operations,
  workflowId,
  // Apply operations in preview mode
  reviewOptions: { mode: 'preview' },
  hasFinished: true,
})

// Accept all suggestions
toolkit.acceptAllSuggestions()

If you need per-operation execution status after applying the workflow, read result.operationResults.

Review changes as tracked changes

If the Tracked Changes extension is installed, tiptapEditWorkflow can apply AI edits as tracked changes instead of preview suggestions.

toolkit.tiptapEditWorkflow({
  operations,
  workflowId,
  hasFinished: true,
  tiptapReadResult: readResult,
  reviewOptions: {
    mode: 'trackedChanges',
    trackedChangesOptions: {
      userId: 'ai-assistant',
    },
  },
})

See the Suggestions guide to learn more about suggestions.