Variables

Variables let you display values, such as a customer name or a calculated total, in your editor. When a value changes, every reference to it updates automatically.

The document stores a reference to the variable, while your application manages its value. You can also bind variables to slots to keep editable content in sync.

Client-side only

Variables run inside each editor instance. They do not provide a server API or synchronize runtime values between clients. Server-side support is on our roadmap. If you're interested, email us at humans@tiptap.dev.

Install

First, contact our team to join the Tiptap Composable Docs pilot program.

Then, configure your package manager by following the private registry guide.

Then, install the package:

npm install @tiptap/core @tiptap/starter-kit @tiptap-pro/extension-variable

Define and display a variable

First, import the editor and extensions:

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import { Variable } from '@tiptap-pro/extension-variable'

Define a variable called customer and add the extension to your editor:

const editor = new Editor({
  extensions: [
    StarterKit,
    Variable.configure({
      variables: { customer: 'Ada' },
    }),
  ],
  content: '<p></p>',
})

Insert a reference at the current selection. It displays Ada:

editor.commands.insertVariable({ name: 'customer' })

Update the value. Every reference to customer now displays Grace:

editor.commands.setVariable({ name: 'customer', value: 'Grace' })

Add and manage variables at runtime

You don't need to know every variable when you create the editor. Add a value or resolver later with addVariable(), then insert a reference:

editor.commands.addVariable({ name: 'company', value: 'Acme' })
editor.commands.insertVariable({ name: 'company' })

editor.commands.addVariable({
  name: 'customerLabel',
  value: ({ getText }) => `${getText('customer')} at ${getText('company')}`,
})
editor.commands.insertVariable({ name: 'customerLabel' })

addVariable() returns false if the name is empty or already registered. Use setVariable() to update an existing value, or setVariables() to replace a value or resolver:

editor.commands.setVariable({ name: 'company', value: 'Tiptap' })
editor.commands.setVariables({
  variables: { customerLabel: ({ getText }) => getText('customer').toUpperCase() },
})

Remove a variable with removeVariable(). Remove its dependent resolvers first, or remove them together with removeVariables():

editor.commands.removeVariable({ name: 'customerLabel' })
editor.commands.removeVariable({ name: 'company' })

Existing references stay in the document and display an empty value after removal. Adding the same name again restores their display. Runtime definitions belong to this editor instance and are not saved in the document or reverted by document undo. For bound slots, removal returns the variable to slot content.

See the command reference for bulk registration, removal, and failure behavior.

Values and resolvers

Define values directly or calculate them with a function, called a resolver. Use get() to read another variable's raw value, or getText() to read it as a string. Replace Variable.configure(...) in your editor with:

Variable.configure({
  variables: {
    customer: 'Ada',
    quantity: 3,
    greeting: ({ getText }) => `Hello, ${getText('customer')}!`,
  },
})

With this configuration, changing customer also updates greeting:

editor.commands.setVariable({ name: 'customer', value: 'Grace' })
// greeting becomes "Hello, Grace!"

Values can also be objects or arrays. Objects that are not Tiptap content display as JSON:

editor.commands.setVariable({
  name: 'customer',
  value: { name: 'Ada', company: 'Acme' },
})

To display the name and company separately, define a resolver for each property using get('customer'), then insert references to those variables. Names such as customer.name do not look up object properties automatically.

Use Tiptap JSON to display formatted content:

editor.commands.setVariable({
  name: 'customer',
  value: { type: 'text', text: 'Ada', marks: [{ type: 'bold' }] },
})

Connect variable values to external sources

A resolver can read data from your application. Create a customer object, a resolver that reads its name, and an editor that displays it:

let customer = { name: 'Ada' }

const refreshEditor = new Editor({
  extensions: [
    StarterKit,
    Variable.configure({
      variables: { customer: () => customer.name },
    }),
  ],
  content: '<p></p>',
})
refreshEditor.commands.insertVariable({ name: 'customer' })

Listen for an application event and refresh the resolver:

const events = new EventTarget()
const refresh = () => refreshEditor.commands.refreshVariables({ names: ['customer'] })
events.addEventListener('customer-change', refresh)

After changing the data, send the event. The editor now displays Grace:

customer = { name: 'Grace' }
events.dispatchEvent(new Event('customer-change'))

Remove the listener when the editor is destroyed:

refreshEditor.on('destroy', () => events.removeEventListener('customer-change', refresh))

What is saved

The document saves the variable's name, not its value. Each editor supplies its own values.

Text inside a bound slot is saved as normal document content.