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

TypeScript
54%
MIT

Anarlog

AI Assistants · Note Taking · Productivity

9,404

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
68
Dependency
Built with
TypeScript 54%
Rust 34%
Updated 5 days ago
Swift
62%
GPL 3.0

cmux

AI Development · Developer Tools

27,452

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
69
Dependency
Built with
Swift 62%
Rust 13%
Updated 5 days ago
Ruby
61%
GPL 2.0

Discourse

Community

47,915

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
Ruby 61%
JavaScript 32%
Updated 5 days ago
TypeScript
92%
MIT

Dittofeed

Automation · Marketing

2,968

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
62
Dependency
Built with
TypeScript 92%
Updated 6 months ago
TypeScript
64%
Other

Epicenter

Developer Tools · Knowledge Management · Note Taking

4,808

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
64
Dependency
Built with
TypeScript 64%
HTML 13%
Svelte 13%
Updated 6 days ago
TypeScript
77%
Other

Joplin

Note Taking

56,513

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
60
Dependency
Built with
TypeScript 77%
JavaScript 14%
Updated 6 days ago
Elixir
71%
AGPL 3.0

Keila

Marketing

2,224

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
84
Repo Health
76
Technical
72
Dependency
Built with
Elixir 71%
HTML 18%
Updated 1 weeks ago
Python
51%
AGPL 3.0

LearnHouse

CMS · Learning Management

2,301

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

View details
90
Repo Health
77
Technical
66
Dependency
Built with
Python 51%
TypeScript 48%
Updated 6 days ago
Scala
63%
AGPL 3.0

lila (Lichess)

Community

18,776

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
76
Dependency
Built with
Scala 63%
TypeScript 26%
Updated 5 days 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