Skip to content

API Reference

Complete reference for all public exports from the WriteTrack SDK.

WriteTrack provides multiple entry points via package.json exports:

Import PathFormatDescription
writetrackESM, CJS, BrowserCore SDK — WriteTrack class and types
writetrack/pipesESM, CJS, BrowserOutput sink factories (also re-exported from writetrack)
writetrack/browserESMBrowser bundle (same as core, explicit browser entry)
Import PathFormatDescription
writetrack/reactESMReact hook — useWriteTrack
writetrack/vueESMVue composable — useWriteTrack
Import PathFormatDescription
writetrack/tiptapESMTipTap extension — WriteTrackExtension
writetrack/ckeditorESMCKEditor 5 plugin — WriteTrackPlugin
writetrack/prosemirrorESMProseMirror plugin — WriteTrackPlugin
writetrack/quillESMQuill module — WriteTrackModule
writetrack/lexicalESMLexical integration — createWriteTrackLexical
writetrack/slateESMSlate integration — createWriteTrackSlate
writetrack/tinymceESMTinyMCE integration — createWriteTrackTinyMCE
Import PathFormatDescription
writetrack/verifyESMServer-side signature verification
writetrack/vizESMSession visualization components
import {
WriteTrack,
webhook,
datadog,
segment,
opentelemetry,
analyzeEvents,
formatIndicator,
} from 'writetrack';
import { useWriteTrack } from 'writetrack/react';
import { useWriteTrack } from 'writetrack/vue';
import { WriteTrackExtension } from 'writetrack/tiptap';
import { WriteTrackPlugin } from 'writetrack/ckeditor';
import { WriteTrackPlugin } from 'writetrack/prosemirror';
import { WriteTrackModule } from 'writetrack/quill';
import { createWriteTrackLexical } from 'writetrack/lexical';
import { createWriteTrackSlate } from 'writetrack/slate';
import { createWriteTrackTinyMCE } from 'writetrack/tinymce';
import { verifyAnalysisSignatureAsync } from 'writetrack/verify';
import { WtScorecard } from 'writetrack/viz';
const { WriteTrack, webhook } = require('writetrack');
<script type="module">
import { WriteTrack } from 'https://unpkg.com/writetrack/dist/browser/index.js';
</script>

All types are exported from their respective entry points:

import type {
WriteTrackOptions,
WriteTrackDataSchema,
SessionAnalysis,
SessionReport,
IndicatorOutput,
KeystrokeEvent,
ClipboardEvent,
SelectionEvent,
UndoRedoEvent,
ProgrammaticInsertionEvent,
CompositionEvent,
InputSource,
WriteTrackSink,
WebhookOptions,
DatadogOptions,
SegmentOptions,
OpenTelemetryOptions,
} from 'writetrack';

The main class for keystroke tracking and behavioral analysis.

new WriteTrack(optionsOrTarget: WriteTrackOptions | HTMLElement)

Creates a new WriteTrack instance. Accepts either a full options object or an HTMLElement directly for localhost evaluation.

ParameterTypeDescription
optionsOrTargetWriteTrackOptions | HTMLElementConfiguration options or target element
// Full options with context
const tracker = new WriteTrack({
target: document.querySelector('#response-field'),
license: 'wt_live_...',
userId: 'u_abc123',
contentId: 'post_draft_42',
metadata: { formName: 'signup' },
});
// Shorthand (localhost evaluation)
const tracker = new WriteTrack(document.querySelector('#response-field'));
start(): void

Begins recording keystroke events. Clears any previously captured data.

  • Resets all event arrays (keystrokes, clipboard, selection, undo/redo, programmatic insertion, composition)
  • Records session start time
  • Enables event capture
const tracker = new WriteTrack({ target: textarea });
tracker.start(); // Now recording
// User types...
const events = tracker.getRawEvents();
stop(): void

Stops recording keystrokes.

  • Disables event capture
  • Cleans up selection polling interval and mutation observer
  • Data remains available after stopping
tracker.stop();
// Data still accessible
const events = tracker.getRawEvents();
getData(): WriteTrackDataSchema

Returns the complete session as a structured WriteTrackDataSchema object, including metadata, all captured events, and computed quality metrics.

tracker.stop();
const data = tracker.getData();
// {
// version: "2.1.0",
// metadata: { tool, targetElement, timestamp, duration },
// session: { events, clipboardEvents, selectionEvents, compositionEvents, ... },
// quality: { overallScore, sequenceValid, qualityLevel, ... },
// }
// Send to your server
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data),
});
getText(): string

Returns the current text content of the target element. Reads value for input/textarea elements, or textContent for other elements.

getRawEvents(): KeystrokeEvent[]

Returns a copy of all captured keystroke events.

const events = tracker.getRawEvents();
console.log(`Captured ${events.length} keystrokes`);
events
.filter((e) => e.type === 'keydown')
.forEach((e) => {
console.log(`${e.key} at ${e.timestamp}ms, flight: ${e.flightTime}ms`);
});
getClipboardEvents(): ClipboardEvent[]

Returns a copy of all clipboard events (copy, paste, cut).

const pastes = tracker.getClipboardEvents().filter((e) => e.type === 'paste');
console.log(`User pasted ${pastes.length} times`);
pastes.forEach((e) => {
console.log(`Pasted ${e.length} chars at position ${e.position}`);
});
getSelectionEvents(): SelectionEvent[]

Returns text selection events.

const selections = tracker.getSelectionEvents();
selections.forEach((e) => {
console.log(`Selected ${e.selectedLength} chars via ${e.method}`);
});
getUndoRedoEvents(): UndoRedoEvent[]

Returns undo/redo operation events.

getProgrammaticInsertionEvents(): ProgrammaticInsertionEvent[]

Returns detected programmatic insertion events. These are emitted when text appears in the input without corresponding keystrokes, indicating browser autocomplete, autofill, predictive text, AI extension injection, voice dictation, or programmatic value assignment.

const insertions = tracker.getProgrammaticInsertionEvents();
insertions.forEach((e) => {
console.log(
`Programmatic insertion: "${e.insertedText}" (${e.insertedLength} chars)`
);
});
getCompositionEvents(): CompositionEvent[]

Returns IME composition events captured during the session. A composition event is emitted when a CJK (Chinese, Japanese, Korean) input method completes a composition sequence. Intermediate keystrokes during composition are suppressed.

const compositions = tracker.getCompositionEvents();
compositions.forEach((e) => {
console.log(`Composition: ${e.insertedLength} chars in ${e.duration}ms`);
});
getSessionDuration(): number

Returns the total session duration in milliseconds since start() was called, including time when the tab was hidden.

const duration = tracker.getSessionDuration();
console.log(`Session: ${(duration / 1000).toFixed(1)} seconds`);
getActiveTime(): number

Returns the active (visible) session time in milliseconds. This subtracts time spent with the tab hidden from the total session duration. Returns 0 before start() is called.

const active = tracker.getActiveTime();
const total = tracker.getSessionDuration();
console.log(
`Active: ${(active / 1000).toFixed(1)}s of ${(total / 1000).toFixed(1)}s total`
);
getKeystrokeCount(): number

Returns the total number of keydown events captured.

pipe(sink: WriteTrackSink): this

Registers an output sink to receive session data when getData() is called. Returns this for chaining.

import { WriteTrack, webhook } from 'writetrack';
tracker
.pipe(webhook({ url: 'https://api.example.com/typing' }))
.pipe({ send: async (data) => console.log(data) });

See Output Sinks for full documentation of the pipe system and available sinks.

on(event: string, handler: (...args: unknown[]) => void): this

Registers an event listener.

Supported events:

EventHandler signatureDescription
pipe:error(err: Error, sink: WriteTrackSink) => voidFires when a sink fails
tick(data: { activeTime: number; totalTime: number }) => voidFires every ~1 second while the session is active and the tab is visible
tracker.on('pipe:error', (err, sink) => {
console.error('Sink failed:', err);
});
// Display a live session timer
tracker.on('tick', ({ activeTime, totalTime }) => {
const seconds = Math.floor(activeTime / 1000);
console.log(`Active: ${seconds}s`);
});
isLicenseValid(): boolean

Returns whether a valid, non-expired license key has been verified for the current domain. Returns false when no license key is provided, during the grace period, or when validation fails. Recording (event capture) works regardless of license status — only getAnalysis() requires a valid license, grace period, or localhost.

isLicenseValidated(): boolean

Returns whether license validation has been performed.

if (tracker.isLicenseValidated() && tracker.isLicenseValid()) {
console.log('License valid, ready to use');
}
isTargetDetached(): boolean

Returns whether the monitored target element has been removed from the DOM. Useful for detecting SPA navigation that destroys the tracked element.

if (tracker.isTargetDetached()) {
console.warn('Target element removed — call stop() and create a new tracker');
}
ready: Promise<void>;

A promise that resolves when the tracker is ready for use. When persist: true, this resolves after IndexedDB loads any prior session data. When persist: false, it resolves immediately. After stop() with persistence enabled, ready resolves when the save completes.

const tracker = new WriteTrack({
target: textarea,
contentId: 'doc-1',
persist: true,
});
await tracker.ready; // IndexedDB loaded
tracker.start(); // Will auto-resume prior session if available
async clearPersistedData(): Promise<void>

Removes any persisted session data from IndexedDB for this field. No-op when persist is not enabled. Use this to reset a field’s session history.

await tracker.clearPersistedData();
async getAnalysis(): Promise<SessionAnalysis | null>

Runs WASM-powered analysis on the captured session data. The WASM binary is lazy-loaded on the first call and cached for subsequent calls. Returns null if the WASM module fails to load.

const tracker = new WriteTrack({
target: textarea,
wasmUrl: '/writetrack.wasm',
});
tracker.start();
// ... user types ...
tracker.stop();
const analysis = await tracker.getAnalysis();
// {
// version: "0.9.1",
// sufficientData: true,
// contentOrigin: { ... },
// timingAuthenticity: { ... },
// ...
// }
async getSessionReport(): Promise<SessionReport>

Returns both the raw capture data and the WASM analysis in a single call.

const report = await tracker.getSessionReport();
// {
// data: WriteTrackDataSchema, // Same as getData()
// analysis: SessionAnalysis, // Same as getAnalysis()
// }
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(report),
});
get wasmReady(): boolean

Whether the WASM module has been loaded and is ready for analysis. Returns false until getAnalysis() is called for the first time and the WASM binary loads successfully.

const tracker = new WriteTrack({ target: textarea });
console.log(tracker.wasmReady); // false
await tracker.getAnalysis();
console.log(tracker.wasmReady); // true (if WASM loaded)
static async listPersistedSessions(): Promise<PersistedSessionInfo[]>

Returns metadata for all persisted sessions stored in IndexedDB. Useful for multi-document apps that need to display or manage saved writing sessions.

const sessions = await WriteTrack.listPersistedSessions();
// [{ contentId: 'doc_1', fieldId: 'default', keystrokeCount: 42, lastSavedAt: 1709..., sessionId: '...' }]
static async deletePersistedSession(contentId: string, fieldId?: string): Promise<void>

Deletes persisted session data from IndexedDB. If fieldId is omitted, deletes all sessions for the given contentId.

// Delete all sessions for a document
await WriteTrack.deletePersistedSession('doc_1');
// Delete a specific field's session
await WriteTrack.deletePersistedSession('doc_1', 'response');
import { webhook, datadog, segment, opentelemetry } from 'writetrack';
ExportReturnsDescription
webhook(options)WriteTrackSinkPOST session data to a URL
datadog(options)WriteTrackSinkSend as Datadog RUM custom action
segment(options)WriteTrackSinkSend as Segment track event
opentelemetry(options)WriteTrackSinkSend as OpenTelemetry span

See Output Sinks for configuration options and usage guides.

import { analyzeEvents } from 'writetrack';
async function analyzeEvents(
rawEventData: unknown,
options?: { wasmUrl?: string; licenseKey?: string }
): Promise<SessionAnalysis | null>;

Analyze a WriteTrackDataSchema object without needing a live DOM element. Useful for server-side analysis of previously captured session data.

// Server-side: analyze data received from the client
const data = await request.json(); // WriteTrackDataSchema from getData()
const analysis = await analyzeEvents(data, {
licenseKey: process.env.WRITETRACK_LICENSE_KEY,
});
import { formatIndicator } from 'writetrack';
function formatIndicator(indicator: IndicatorOutput): string;

Converts a machine-readable WT-NNN indicator code (from SessionAnalysis) into a human-readable English string.

const analysis = await tracker.getAnalysis();
if (analysis) {
const msg = formatIndicator(analysis.contentOrigin.indicator);
// e.g., "85% of text was pasted from external sources"
}
import { createSessionReport } from 'writetrack';
async function createSessionReport(
rawData: unknown,
options?: { wasmUrl?: string; licenseKey?: string }
): Promise<SessionReport>;

Analyze raw session data and return a complete SessionReport (data + analysis). Wraps analyzeEvents() — accepts WriteTrackDataSchema (from getData()) or raw typing-sample format (with rawEvents instead of session). The returned report is ready for scorecard.setData().

const data = await request.json();
const report = await createSessionReport(data, {
licenseKey: process.env.WRITETRACK_LICENSE_KEY,
});
// report.data — normalized input data
// report.analysis — SessionAnalysis result

Configuration options for the WriteTrack constructor.

interface WriteTrackOptions {
target: HTMLElement; // Required: Input element to monitor
license?: string; // Optional: License key for production
userId?: string; // Optional: User identifier (included in metadata)
contentId?: string; // Optional: Content/document identifier (included in metadata)
metadata?: Record<string, unknown>; // Optional: Arbitrary tags (appears as metadata.custom)
wasmUrl?: string; // Optional: URL to WASM binary for analysis
persist?: boolean; // Optional: Enable IndexedDB session persistence (requires contentId)
cursorPositionProvider?: () => number; // Optional: Custom cursor position for rich-text editors
inputSourceProvider?: () => InputSource | undefined; // Optional: Callback returning current input source classification. Set automatically by editor integrations.
}

Metadata about a persisted writing session, returned by listPersistedSessions().

interface PersistedSessionInfo {
contentId: string; // Document/content identifier
fieldId: string; // Field identifier within the document
keystrokeCount: number; // Number of keystrokes in the persisted session
lastSavedAt: number; // Unix timestamp of last save
sessionId: string; // Unique session ID
}

The complete session output returned by getData().

interface WriteTrackDataSchema {
version: '2.1.0';
metadata: SessionMetadata;
session: RawEventData;
quality: DataQualityMetrics;
unlicensed?: boolean;
}

Session metadata included in the export.

interface SessionMetadata {
tool: { name: string; version: string };
targetElement: string; // e.g., 'textarea', 'input'
fieldId: string; // Derived from data-writetrack-field, id, or name attribute
sessionId: string; // Unique session ID (stable across resume)
segment?: number; // Resume segment counter (present when session was resumed)
timestamp: string; // ISO 8601
duration: number; // ms
userId?: string; // From WriteTrackOptions.userId
contentId?: string; // From WriteTrackOptions.contentId
custom?: Record<string, unknown>; // From WriteTrackOptions.metadata
}

Quality assessment of the captured session.

interface DataQualityMetrics {
overallScore: number; // 0-1
completeness: number; // 0-1
sequenceValid: boolean; // Timestamps monotonically increasing
timingValid: boolean; // Session has valid timing data
sessionDuration: number; // ms
eventCount: number;
qualityLevel: 'POOR' | 'FAIR' | 'GOOD' | 'EXCELLENT';
issues: string[]; // Human-readable quality issues
validatedAt: string; // ISO 8601
}

Individual keystroke event data.

interface KeystrokeEvent {
key: string; // Character or key name
code: string; // Physical key code
type: 'keydown' | 'keyup';
timestamp: number; // High-precision timestamp
dwellTime?: number; // Key hold duration (keyup only)
flightTime?: number; // Time since last key (keydown only)
isCorrection?: boolean; // Backspace/Delete/ArrowLeft/ArrowRight
windowFocused: boolean;
timeSinceLastMouse: number;
isTrusted?: boolean; // true if user-generated, false if scripted
inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback
isBurst?: boolean; // Schema field, populated by analysis pipelines
cursorPosition?: number; // Schema field, populated by analysis pipelines
modifiers?: ModifierState; // Schema field, populated by analysis pipelines
}

Clipboard operation event data.

interface ClipboardEvent {
type: 'copy' | 'paste' | 'cut';
timestamp: number;
position: number;
length: number;
shortcut: string; // e.g., 'Ctrl+V', 'Cmd+V'
content?: string; // Clipboard content (paste only)
beforeText?: string; // Text state before operation
afterText?: string; // Text state after operation
replacedText?: string; // Text that was replaced
replacedRange?: {
start: number;
end: number;
};
isTrusted?: boolean; // true if user-generated, false if scripted
inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback
}

Text selection event data.

interface SelectionEvent {
type: 'select';
timestamp: number;
startPosition: number;
endPosition: number;
selectedLength: number;
selectedText?: string; // The selected text content (omitted when privacy-sensitive)
method: 'mouse' | 'keyboard' | 'programmatic';
isTrusted?: boolean; // true if user-generated, false if scripted
}

Programmatic insertion event data.

interface ProgrammaticInsertionEvent {
type: 'programmatic-insertion';
timestamp: number;
insertedText: string; // Text that was inserted
insertedLength: number; // Character count
position: number; // Cursor position where text was inserted
isTrusted: boolean; // Whether the triggering input event was user-generated
inputType?: string; // InputEvent.inputType if available
inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback
}

Undo/redo operation event data.

interface UndoRedoEvent {
type: 'undo' | 'redo';
timestamp: number;
shortcut: string; // e.g., 'Ctrl+Z'
beforeText: string;
afterText: string;
}

IME composition event data. Emitted when a CJK input method or dead-key sequence completes.

interface CompositionEvent {
timestamp: number; // When the composition started
duration: number; // Duration of the composition sequence (ms)
insertedLength: number; // Characters inserted (0 if composition was abandoned)
isTrusted: boolean; // Whether the compositionend event was user-generated
}

The WASM analysis output returned by getAnalysis(). Contains indicator codes and rich metrics for each analysis dimension.

interface SessionAnalysis {
version: string;
locale: string;
analyzedAt: number;
sufficientData: boolean;
keydownCount: number;
integrity: IntegrityProof;
sessionTimeline: SessionSegment[];
initialTextLength: number;
initialTextHash: string;
finalTextLength: number;
finalTextHash: string;
contentOrigin: ContentOriginAnalysis;
timingAuthenticity: TimingAuthenticityAnalysis;
sessionContinuity: SessionContinuityAnalysis;
physicalPlausibility: PhysicalPlausibilityAnalysis;
revisionBehavior: RevisionBehaviorAnalysis;
temporalPatterns: TemporalPatternsAnalysis;
writingProcess: WritingProcessAnalysis;
outputSignature: string;
signedPayload?: string;
}

Each analysis dimension (e.g., contentOrigin, timingAuthenticity) contains an indicator with a machine-readable code (WT-100 through WT-606) and numeric params, plus a metrics object with detailed data and time series suitable for visualization. Content origin additionally has a pasteReworkIndicator (WT-105 through WT-107) for paste editing behavior.

The outputSignature field contains a cryptographic signature over the analysis output, verifiable via verifyAnalysisSignatureAsync() from writetrack/verify. The optional signedPayload field contains the exact JSON bytes that were signed, for cross-language verification.

Writing process stage classification returned in SessionAnalysis.writingProcess.

interface WritingProcessAnalysis {
indicator: IndicatorOutput;
metrics: {
segments: WritingProcessSegment[];
transitions: PhaseTransition[];
timeInPhase: {
planning: number; // Fraction of session time (0-1)
drafting: number;
revision: number;
};
phaseChangeCount: number;
windowDurationMs: number;
phaseTimeline: TimeSeries;
};
}
interface WritingProcessSegment {
startTime: number; // ms from session start
endTime: number;
phase: 'planning' | 'drafting' | 'revision';
confidence: number; // 0-1
features: WindowFeatures;
}
interface PhaseTransition {
from: 'planning' | 'drafting' | 'revision';
to: 'planning' | 'drafting' | 'revision';
count: number;
probability: number; // 0-1
}

Combined output of getSessionReport().

interface SessionReport {
data: WriteTrackDataSchema;
analysis: SessionAnalysis | null;
}

Machine-readable indicator returned by each analysis dimension.

interface IndicatorOutput {
code: string; // WT-NNN code (e.g., "WT-101")
params: Record<string, number>; // Numeric parameters (e.g., { pct: 85 })
}

Use formatIndicator() to convert to a human-readable English string.