Character count extension for Tiptap rich text editor with configurable limits and automatic content trimming
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Character 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.