Character count extension for Tiptap rich text editor with configurable limits and automatic content trimming
npx @tessl/cli install tessl/npm-tiptap--extension-character-count@3.4.0Character count extension for Tiptap rich text editor with configurable limits and automatic content trimming. This extension provides real-time character and word counting functionality with optional character limits, automatic content enforcement, and support for different counting modes.
npm install @tiptap/extension-character-countimport { CharacterCount } from "@tiptap/extension-character-count";
import type { CharacterCountOptions } from "@tiptap/extension-character-count";For default import:
import CharacterCount from "@tiptap/extension-character-count";For CommonJS:
const { CharacterCount } = require("@tiptap/extension-character-count");import { CharacterCount } from "@tiptap/extension-character-count";
import { Editor } from "@tiptap/core";
// Basic usage with default options
const editor = new Editor({
extensions: [CharacterCount],
content: "<p>Hello world!</p>",
});
// With character limit
const editor = new Editor({
extensions: [
CharacterCount.configure({
limit: 280,
})
],
content: "<p>Hello world!</p>",
});
// Access character count
const characterCount = editor.storage.characterCount.characters();
const wordCount = editor.storage.characterCount.words();
console.log(`Characters: ${characterCount}, Words: ${wordCount}`);The extension is built around several key components:
textSize) and node-based (nodeSize) counting modesThe main extension providing character counting functionality with optional limits and automatic enforcement.
const CharacterCount: Extension<CharacterCountOptions, CharacterCountStorage>;Configuration interface for customizing the character count behavior.
interface CharacterCountOptions {
/**
* The maximum number of characters that should be allowed. Defaults to null.
* @default null
* @example 180
*/
limit: number | null | undefined;
/**
* The mode by which the size is calculated. If set to 'textSize', the textContent of the document is used.
* If set to 'nodeSize', the nodeSize of the document is used.
* @default 'textSize'
* @example 'textSize'
*/
mode: 'textSize' | 'nodeSize';
/**
* The text counter function to use. Defaults to a simple character count.
* @default (text) => text.length
* @example (text) => [...new Intl.Segmenter().segment(text)].length
*/
textCounter: (text: string) => number;
/**
* The word counter function to use. Defaults to a simple word count.
* @default (text) => text.split(' ').filter(word => word !== '').length
* @example (text) => text.split(/\s+/).filter(word => word !== '').length
*/
wordCounter: (text: string) => number;
}Runtime interface for accessing character and word counts.
interface CharacterCountStorage {
/**
* Get the number of characters for the current document.
* @param options The options for the character count. (optional)
* @param options.node The node to get the characters from. Defaults to the current document.
* @param options.mode The mode by which the size is calculated. If set to 'textSize', the textContent of the document is used.
*/
characters: (options?: {
node?: ProseMirrorNode;
mode?: 'textSize' | 'nodeSize'
}) => number;
/**
* Get the number of words for the current document.
* @param options The options for the character count. (optional)
* @param options.node The node to get the words from. Defaults to the current document.
*/
words: (options?: { node?: ProseMirrorNode }) => number;
}Type definition for ProseMirror nodes used in the storage interface.
// Imported from @tiptap/pm/model
import type { Node as ProseMirrorNode } from '@tiptap/pm/model';import { CharacterCount } from "@tiptap/extension-character-count";
import { Editor } from "@tiptap/core";
// Using Unicode-aware character counting
const editor = new Editor({
extensions: [
CharacterCount.configure({
limit: 280,
textCounter: (text) => {
// Count grapheme clusters instead of code units
return [...new Intl.Segmenter().segment(text)].length;
},
})
],
});import { CharacterCount } from "@tiptap/extension-character-count";
import { Editor } from "@tiptap/core";
// Using custom word counting logic
const editor = new Editor({
extensions: [
CharacterCount.configure({
wordCounter: (text) => {
// Split on any whitespace and filter empty strings
return text.split(/\s+/).filter(word => word !== '').length;
},
})
],
});import { CharacterCount } from "@tiptap/extension-character-count";
import { Editor } from "@tiptap/core";
// Use nodeSize instead of textContent for counting
const editor = new Editor({
extensions: [
CharacterCount.configure({
mode: 'nodeSize',
limit: 1000,
})
],
});import { CharacterCount } from "@tiptap/extension-character-count";
import { Editor } from "@tiptap/core";
const editor = new Editor({
extensions: [CharacterCount],
});
// Get counts for current document
const chars = editor.storage.characterCount.characters();
const words = editor.storage.characterCount.words();
// Get counts for specific node with custom mode
const nodeChars = editor.storage.characterCount.characters({
node: someSpecificNode,
mode: 'nodeSize'
});
// Get word count for specific node
const nodeWords = editor.storage.characterCount.words({
node: someSpecificNode
});The extension automatically enforces character limits through ProseMirror's transaction filtering:
import { CharacterCount } from "@tiptap/extension-character-count";
import { Editor } from "@tiptap/core";
const editor = new Editor({
extensions: [
CharacterCount.configure({
limit: 100, // Strict 100 character limit
})
],
content: "<p>This content will be automatically trimmed if it exceeds the 100 character limit during initialization</p>",
});
// The editor will automatically prevent further typing at the limit
// and trim any pasted content that would exceed the limitThe extension includes built-in error handling and warnings:
No exceptions are thrown during normal operation, making the extension safe to use in production environments.