Skip to content

TipTap Integration

WriteTrack includes a TipTap extension, compatible with @tiptap/core v2.0+ and tested against v3.19. See React and Vue examples below.

Install WriteTrack alongside your TipTap packages:

Terminal window
npm i writetrack @tiptap/core @tiptap/starter-kit

Add a container element for the editor, then initialize with the extension:

<div id="editor"></div>
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import { WriteTrackExtension } from 'writetrack/tiptap';
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [
StarterKit,
WriteTrackExtension.configure({
license: 'your-license-key',
}),
],
});
// Access WriteTrack data when needed
const data = editor.storage.writetrack.tracker.getData();

Pass options via .configure():

WriteTrackExtension.configure({
license: 'your-license-key',
userId: 'user-123',
contentId: 'document-456',
metadata: { formName: 'signup' },
autoStart: true, // default
});
OptionTypeDefaultDescription
licensestringundefinedWriteTrack license key
userIdstringundefinedUser identifier included in metadata
contentIdstringundefinedContent identifier included in metadata
metadataRecord<string, unknown>undefinedAdditional metadata
autoStartbooleantrueStart tracking when editor is created
wasmUrlstringundefinedURL to WASM binary for analysis
persistbooleanfalseEnable IndexedDB session persistence (requires contentId)
onTick(data: { activeTime: number; totalTime: number; tracker: WriteTrack }) => voidundefinedCallback fired every ~1s with active session time and tracker instance
onReady(tracker: WriteTrack) => voidundefinedCallback fired when tracker is ready (persistence loaded if applicable)

The extension stores the WriteTrack instance in TipTap’s storage system:

// Get the tracker instance
const tracker = editor.storage.writetrack.tracker;
// Get typing data
const data = tracker.getData();
// Check readiness and tracking status
const isReady = editor.storage.writetrack.isReady;
const isTracking = editor.storage.writetrack.isTracking;
PropertyTypeDescription
trackerWriteTrack | nullThe underlying WriteTrack instance
isReadybooleanWhether the tracker is ready (persistence loaded if applicable)
isTrackingbooleanWhether tracking is currently active

The extension emits a writetrack:ready event on the TipTap editor when the tracker becomes ready. This is useful for sibling components that don’t configure the extension directly:

editor.on('writetrack:ready', ({ tracker }) => {
// Tracker is ready — subscribe to events, update UI, etc.
tracker.on('analysis', (analysis) => {
console.log('Analysis:', analysis);
});
});
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { WriteTrackExtension } from 'writetrack/tiptap';
const writetrack = WriteTrackExtension.configure({
license: 'your-license-key',
});
function FormEditor() {
const editor = useEditor({
extensions: [StarterKit, writetrack],
});
const handleSubmit = () => {
const data = editor?.storage.writetrack.tracker?.getData();
console.log('Typing data:', data);
};
return (
<div>
<EditorContent editor={editor} />
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3';
import StarterKit from '@tiptap/starter-kit';
import { WriteTrackExtension } from 'writetrack/tiptap';
const writetrack = WriteTrackExtension.configure({
license: 'your-license-key',
});
const editor = useEditor({
extensions: [StarterKit, writetrack],
});
function handleSubmit() {
const data = editor.value?.storage.writetrack.tracker?.getData();
console.log('Typing data:', data);
}
</script>
<template>
<div>
<EditorContent :editor="editor" />
<button @click="handleSubmit">Submit</button>
</div>
</template>

A complete, self-contained page using TipTap + WriteTrack with no framework:

<!DOCTYPE html>
<html>
<head>
<style>
.ProseMirror {
border: 1px solid #ccc;
padding: 12px;
min-height: 200px;
}
.ProseMirror:focus {
outline: 2px solid #3b82f6;
}
</style>
</head>
<body>
<div id="editor"></div>
<button id="submit">Get Data</button>
<pre id="output"></pre>
<script type="module">
import { Editor } from 'https://esm.sh/@tiptap/core';
import StarterKit from 'https://esm.sh/@tiptap/starter-kit';
import { WriteTrackExtension } from 'https://esm.sh/writetrack/tiptap';
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [
StarterKit,
WriteTrackExtension.configure({
onReady(tracker) {
document
.querySelector('#submit')
.addEventListener('click', () => {
const data = tracker.getData();
document.querySelector('#output').textContent =
JSON.stringify(data.quality, null, 2);
});
},
}),
],
});
</script>
</body>
</html>

If you prefer not to use the extension, you can attach WriteTrack directly to TipTap’s editable element:

import WriteTrack from 'writetrack';
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
const editor = new Editor({
element: document.querySelector('#editor'),
extensions: [StarterKit],
onCreate({ editor }) {
const tracker = new WriteTrack({
target: editor.view.dom,
license: 'your-license-key',
});
tracker.start();
},
});

The extension augments TipTap’s Storage interface so editor.storage.writetrack is fully typed automatically.

import type {
WriteTrackExtensionOptions,
WriteTrackExtensionStorage,
} from 'writetrack/tiptap';