prosemirror-state

Immutable, plugin-extensible editor state for ProseMirror, with transactional document, selection, and stored-mark updates.

Library
npm
v1.4.4
231 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
39 /100 Needs Attention
Development Activity 4
Maintenance 0
Community 72
Maturity 60
Momentum 20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
71 /100 Good
Architecture 82
Code Quality 72
Innovation 75
Learning Curve 55

prosemirror-state is the core state module of ProseMirror, the toolkit for building rich, well-behaved WYSIWYG editors on top of contentEditable. It defines the EditorState object — an immutable snapshot of the current document, selection, and stored marks — and the Transaction type used to move from one state to the next. Rather than mutating an editor in place, every change (typing, formatting, drag-and-drop, collaborative merges) is expressed as a transaction applied to produce a brand-new state, which makes undo history, time travel, and collaborative editing tractable to implement correctly.

On top of that base, the module provides ProseMirror’s plugin system: a Plugin can declare its own StateField that rides along inside EditorState, hook into transaction filtering and appending, and contribute view-level props. This is the layer that libraries like prosemirror-example-setup, prosemirror-collab, and countless editor products (Notion-style block editors, comment/annotation systems, collaborative docs) build their behavior on, without needing to touch prosemirror-view or prosemirror-model directly.

What You Get

  • EditorState — an immutable state object holding doc, selection, and storedMarks, created via EditorState.create and advanced via state.apply(transaction)
  • Transaction — a Transform subclass adding selection tracking, stored-mark management, scroll-into-view flags, and arbitrary setMeta/getMeta metadata for coordinating with plugins
  • Selection, TextSelection, NodeSelection, and AllSelection — a small class hierarchy covering cursor/range text selections, whole-node selections, and select-all, with SelectionRange and SelectionBookmark for multi-range and serializable selection state
  • Plugin and PluginKey — a plugin abstraction that can add a StateField (custom persisted state), filterTransaction and appendTransaction hooks, and EditorProps, keyed so state can be looked up without holding the plugin instance
  • JSON serialization — EditorState.toJSON/fromJSON round-trips document, selection, and opted-in plugin state for persistence or transmission

Common Use Cases

  • Building a custom rich-text or block-based editor (comment threads, wiki pages, CMS content fields) where you need full control over document state and undo behavior
  • Implementing collaborative editing by combining Transaction/appendTransaction hooks with prosemirror-collab to merge concurrent changes deterministically
  • Writing editor plugins that need their own persisted, transaction-driven state (e.g. tracked changes, live cursors, inline suggestions) via a custom StateField
  • Serializing and restoring editor sessions (drafts, versioned documents) with EditorState.toJSON/fromJSON

Under The Hood

Architecture — The module centers on three cooperating pieces: EditorState (src/state.ts), Transaction (src/transaction.ts), and Plugin/PluginKey (src/plugin.ts). EditorState doesn’t store its fields as a fixed set of properties; instead, a Configuration object built from the active plugin list assembles a fields array — the four built-in FieldDescs (doc, selection, storedMarks, scrollToSelection) plus one FieldDesc per plugin that declares a state spec — and applyInner walks that array calling each field’s apply(tr, oldValue, oldState, newState) to produce the next state. EditorState.applyTransaction layers a second loop on top of apply: after the root transaction is applied, every plugin with an appendTransaction hook gets a chance to inspect the new transactions and append its own (used for auto-corrections, collaborative rebasing, etc.), tracked per-plugin via a seen array so a plugin never re-sees transactions it already processed. Selection is a small class hierarchy (Selection abstract base in src/selection.ts, with TextSelection, NodeSelection, AllSelection subclasses) that all implement map() so selections survive document changes.

Tech Stack — Pure TypeScript (100% of the codebase per GitHub’s language breakdown), targeting ES modules with a CJS build (dist/index.js / dist/index.cjs) generated via the @prosemirror/buildhelper dev dependency and pm-buildhelper. Runtime dependencies are exactly the other ProseMirror core modules — prosemirror-model (document/schema), prosemirror-transform (the Transform/Step base that Transaction extends), and prosemirror-view (only for the EditorView/EditorProps types used in plugin specs) — with no third-party runtime dependencies at all.

Code Quality — Mocha-driven tests live in test/test-state.ts and test/test-selection.ts (352 lines combined) using the ist assertion library and prosemirror-test-builder for constructing test documents; coverage includes transaction application, plugin state fields (init/apply/toJSON/fromJSON), filterTransaction/appendTransaction interplay, and selection mapping across document changes. The source itself is compact (about 1,100 lines across five files) with extensive /// doc comments used to generate the published API reference, consistent use of TypeScript generics for typed plugin state (Plugin<PluginState>, PluginKey<PluginState>), and internal invariants enforced with explicit RangeError throws (e.g. rejecting a setSelection call with a selection pointing at a stale document).

API Design — The public surface is deliberately small and composes tightly with prosemirror-transform: state.tr returns a fresh Transaction, transaction methods return this for chaining (tr.setSelection(...).scrollIntoView()), and plugin authors interact with a single StateField<T> interface (init/apply/optional toJSON/fromJSON) rather than a sprawling plugin API. The tradeoff is a steep initial learning curve — understanding the transaction/appendTransaction/filterTransaction interplay and the immutable-state model takes real study — but once internalized, the API is highly predictable and the same few primitives (Transaction, StateField, PluginKey) cover nearly every plugin use case in the ecosystem.

Used by 18 apps in this directory

JavaScript
42%
Other

LimeSurvey

Forms Surveys

3,778

The world's most flexible open-source survey platform with 900+ templates, conditional logic, 80+ languages, and full GDPR compliance for any scale.

View details
86
Repo Health
62
Technical
62
Dependency
Built with
JavaScript 42%
PHP 36%
CSS 12%
Updated 6 days ago
JavaScript
55%
Other

Lokus

Knowledge Management · Note Taking

829

Local-first note-taking with graph view, canvas & AI plugins—your Markdown files, zero telemetry, blazing-fast Rust performance.

View details
75
Repo Health
75
Technical
64
Dependency
Built with
JavaScript 55%
HTML 21%
Rust 12%
Updated 2 months ago
TypeScript
44%
Other

Magic

AI Agents · Automation · Low Code Platforms

5,055

Magic is an enterprise-grade open-source AI agent platform combining a generalist AI agent, workflow engine, IM, and collaborative office system for running an AI-powered digital workforce.

View details
68
Repo Health
79
Technical
65
Dependency
Built with
TypeScript 44%
PHP 32%
Updated 2 months ago
Clojure
57%
Other

Metabase

Analytics

49,827

The open-source BI platform that lets anyone ask questions and build dashboards without writing SQL — with an embedded analytics SDK and AI-powered query assistant included.

View details
95
Repo Health
84
Technical
62
Dependency
Built with
Clojure 57%
TypeScript 38%
Updated 6 days ago
Python
38%
Other

Open WebUI

AI Agents · AI Assistants

156,607

The extensible, privacy-first AI platform that runs Ollama, OpenAI, and any LLM backend behind a polished, feature-packed web interface.

View details
92
Repo Health
75
Technical
66
Dependency
Built with
Python 38%
Svelte 35%
JavaScript 19%
Updated 6 days ago
TypeScript
93%
Apache 2.0

Openship

Devops · Hosting Control Panel

14,912

Openship is an open-source, self-hostable deployment platform that points at a repo and builds, ships, routes, and TLS-terminates the app — driven from a desktop app, web dashboard, or CLI.

View details
84
Repo Health
85
Technical
65
Dependency
Built with
TypeScript 93%
Updated 6 days ago
TypeScript
98%
Other

Outline

Collaboration · Knowledge Management

41,045

A fast, real-time collaborative knowledge base for growing teams built on React, Node.js, and ProseMirror.

View details
91
Repo Health
87
Technical
68
Dependency
Built with
TypeScript 98%
Updated 6 days ago
TypeScript
98%
Apache 2.0

rowboat

AI Assistants · AI Development

18,060

Build, test, and deploy multi-agent AI workflows with a visual editor, RAG data sources, MCP tool integration, and a production-ready REST API.

View details
88
Repo Health
72
Technical
65
Dependency
Built with
TypeScript 98%
Updated 1 weeks ago
Go
66%
AGPL 3.0

WriteFreely

Blogging · Social Media

5,275

A clean, Markdown-based publishing platform for writers, with built-in ActivityPub federation for connecting to the wider fediverse.

View details
86
Repo Health
63
Technical
69
Dependency
Built with
Go 66%
Go Template 23%
Updated 4 weeks ago

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers