prosemirror-state

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

Library
npm
v1.4.4
232stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
40/100Fair
Development Activity8
Maintenance0
Community72
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
71/100Good
Architecture82
Code Quality72
Innovation75
Learning Curve55

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 16 apps in this directory

TypeScript
51%
MIT

Anarlog

Note Taking · AI Assistants · Productivity

9,087

Anarlog is an open-source, local-first AI meeting notetaker that records, transcribes, and summarizes meetings entirely on your device — no cloud lock-in, no mandatory account, and every note saved as a plain markdown file you own forever.

View details
86
Repo Health
77
Technical
69
Dependency
Built with
TypeScript51%
Rust38%
Updated today
Swift
64%
GPL 3.0

cmux

Developer Tools · AI Development

26,221

A native, Ghostty-based macOS terminal with vertical tabs, agent-aware notifications, and a scriptable browser built for running many parallel AI coding agent sessions instead of juggling tmux panes.

View details
84
Repo Health
81
Technical
72
Dependency
Built with
Swift64%
Rust14%
Updated today
Ruby
62%
GPL 2.0

Discourse

Community

47,672

Open-source community platform with dynamic trust levels, real-time chat, and a powerful plugin architecture for self-hosted forums.

View details
89
Repo Health
89
Technical
67
Dependency
Built with
Ruby62%
JavaScript31%
Updated today
TypeScript
92%
MIT

Dittofeed

Marketing · Automation

2,903

Open-source omni-channel customer engagement platform for automating transactional and marketing messages via email, SMS, WhatsApp, Slack, and mobile push.

View details
54
Repo Health
75
Technical
63
Dependency
Built with
TypeScript92%
Updated 4 months ago
TypeScript
72%
Other

Epicenter

Knowledge Management · Note Taking · Developer Tools

4,761

A local-first monorepo led by Whispering, an open-source speech-to-text app, built on an MIT toolkit that turns your data into plain Markdown and SQLite files you own instead of a database you rent.

View details
88
Repo Health
90
Technical
68
Dependency
Built with
TypeScript72%
Svelte15%
Updated today
TypeScript
76%
Other

Joplin

Note Taking

56,003

The privacy-first, open-source note-taking app with end-to-end encrypted sync, AI assistance, and a powerful plugin ecosystem across every platform.

View details
93
Repo Health
87
Technical
62
Dependency
Built with
TypeScript76%
JavaScript15%
Updated yesterday
Elixir
71%
AGPL 3.0

Keila

Marketing

2,188

Self-hosted newsletter platform with a visual block editor, Liquid personalization, and flexible email delivery — a privacy-respecting Mailchimp alternative you run on your own infrastructure.

View details
88
Repo Health
76
Technical
71
Dependency
Built with
Elixir71%
HTML18%
Updated yesterday
Python
51%
AGPL 3.0

LearnHouse

Learning Management · CMS

2,178

Open-source LMS with AI tutoring, real-time collaboration boards, live code execution, and built-in course monetization — self-hosted in minutes.

View details
89
Repo Health
77
Technical
67
Dependency
Built with
Python51%
TypeScript48%
Updated 3 days ago
Scala
63%
AGPL 3.0

lila (Lichess)

Community

18,642

The forever-free, ad-free, open-source chess server powering millions of real-time games, AI analysis, puzzles, and tournaments worldwide.

View details
89
Repo Health
80
Technical
78
Dependency
Built with
Scala63%
TypeScript26%
Updated today

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

Search