npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

prosemirror-inspector

v0.0.1

Published

Compact, multi-instance ProseMirror developer tools with transaction and plugin performance profiling.

Readme

Work in progress: this package is still under active development. APIs, integrations, and behavior may change between releases, including breaking changes. Feedback and bug reports are welcome in GitHub Issues.

A development panel for inspecting and profiling ProseMirror editors.

Connect an EditorView once, then inspect its state, transaction history, plugins, schema, snapshots, and synchronous performance. Render the panel directly in your application or add it to TanStack Devtools.

This package is development-only tooling. Recording editor activity and wrapping plugin hooks adds runtime overhead.

Install

npm install --save-dev prosemirror-inspector

The application must already provide prosemirror-model, prosemirror-state, prosemirror-transform, and prosemirror-view. React, Preact, Vue, Svelte, and Angular are optional peers and are needed only when you use their matching adapters. Solid powers the native panel and is installed with the package.

Getting started

There are two parts to the integration: connect the editor, then choose where to render the panel. Both the standalone and TanStack versions read from the same devtools store.

1. Connect a ProseMirror editor

Create a session before EditorState so it can optionally instrument the editor's plugins. Attach the finished EditorView, then dispose the session during teardown.

import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { createProseMirrorDevtoolsSession } from "prosemirror-inspector/core";

const devtools = createProseMirrorDevtoolsSession({
  id: "main-editor",
  label: "Main editor",
});

const plugins = devtools.instrumentPlugins(rawPlugins);
const state = EditorState.create({ schema, plugins });
const view = new EditorView(document.querySelector("#editor")!, { state });

devtools.attach(view);

// When the editor is removed:
devtools.dispose();
view.destroy();

schema and rawPlugins in this example are the application's existing editor configuration.

instrumentPlugins is optional. Skip it when you need only editor state, history, dispatch phases, plugins, schema, and snapshots. Use it when you also want timings for individual plugin hooks.

Plugin instrumentation must happen before EditorState.create. The returned plugins replace the original objects, so never include both versions in the same state.

Run session setup only in development. The framework adapters disable their UI and registration helpers automatically outside development, but direct core calls do not become no-ops.

2a. Render a standalone panel

Framework adapters export a panel renderer that works without TanStack Devtools. This React example uses the framework component in a fixed-height area of the application:

import { ProseMirrorDevtoolsPanel } from "prosemirror-inspector/react";

export function EditorDevtools() {
  return (
    <aside style={{ height: 480 }}>
      <ProseMirrorDevtoolsPanel defaultPanel="state" />
    </aside>
  );
}

The package root also exports the native Solid-rendered ProseMirrorDevtoolsPanel for hosts that already render Solid components.

2b. Add the panel to TanStack Devtools

Install the TanStack Devtools package for your framework:

npm install --save-dev @tanstack/react-devtools

Create the plugin once, outside the application component, and pass it to the TanStack shell:

import { TanStackDevtools } from "@tanstack/react-devtools";
import { createProseMirrorDevtoolsPlugin } from "prosemirror-inspector/react";

const prosemirrorDevtools = createProseMirrorDevtoolsPlugin({
  defaultOpen: false,
  defaultPanel: "state",
});

export function App() {
  return <TanStackDevtools plugins={[prosemirrorDevtools]} />;
}

Inside TanStack Devtools, the panel follows the shell's theme and uses its launcher and layout.

Optional event and rendering diagnostics

Enable bounded capture when creating the session, then instrument plugins before creating the editor state as in the quick start:

const devtools = createProseMirrorDevtoolsSession({
  id: "main-editor",
  diagnostics: { limit: 200 }, // `true` uses the same default.
});
const plugins = devtools.instrumentPlugins(rawPlugins);
// Create the state and view with these plugins, then devtools.attach(view).

function runBold() {
  const run = () => toggleMark(schema.marks.strong)(view.state, view.dispatch, view);
  return devtools.diagnostics ? devtools.diagnostics.traceCommand("Bold", run) : run();
}

The Events & commands and Decorations & node views panels consume this capture. It is off by default. limit caps retained events, NodeViews, decoration sources, ranges per source, and dispatch references per event independently; it defaults to 200 and accepts 1–10,000. These limits are separate from transaction history. Panels render captured lists in pages of 100.

Only wrapped plugin hooks are observed: diagnostics do not rerun decoration callbacks or inspect uninstrumented editor props. Event records include handler order, exact boolean outcomes (or unknown for other returns), duration, nested parent calls, errors, and synchronous dispatch IDs. They exclude DOM events, targets, typed text, key characters, and clipboard payloads; safe metadata includes event type, key code, and modifier flags. Application error messages and shallow scalar decoration specs can still contain application data. traceCommand names one synchronous call, preserves its return value/errors, and does not capture deferred work or discover commands automatically.

Decoration ranges are the most recently observed result per plugin. Old-document ranges are marked stale, and overlays are removed on document changes or when leaving the panel. NodeView records retain lifecycle counts and durations without retaining their DOM.

Lower-level integrations can create a controller with createProseMirrorDevtoolsDiagnostics, then pass the same diagnostics to instrumentProseMirrorPlugins and attachProseMirrorDevtools (or a framework registration hook). Tiptap's setupTiptapDevtools also accepts this controller. Create one controller per editor lifecycle. The caller disposes an externally supplied controller; sessions dispose controllers they create from true or options. Controllers expose getSnapshot, subscribe, clearEvents, and clearNodeViews for custom hosts. clearNodeViews removes destroyed records and resets live counters while continuing to track existing live views.

Framework adapters

Every framework entrypoint exports a panel renderer and a TanStack Devtools plugin factory. Most also provide a lifecycle helper for attaching an EditorView.

| Framework | Entrypoint | Registration helper | | --------- | ------------------------------- | --------------------------- | | React | prosemirror-inspector/react | useProseMirrorDevtools | | Preact | prosemirror-inspector/preact | useProseMirrorDevtools | | Solid | prosemirror-inspector/solid | useProseMirrorDevtools | | Vue | prosemirror-inspector/vue | useProseMirrorDevtools | | Svelte | prosemirror-inspector/svelte | Use the core session | | Angular | prosemirror-inspector/angular | injectProseMirrorDevtools |

The normal framework entrypoints render no UI and skip registration outside development mode. Core APIs, the native root panel, and the Tiptap helpers are not automatically disabled, so guard those calls with your build tool's development flag. Import prosemirror-inspector/<framework>/production only when you intentionally want a framework adapter to stay active in a production build.

What the panel shows

| Panel | Contents | | ------------------------ | -------------------------------------------------------------------------------------------------- | | State | Document tree and positions, node JSON, selection, active marks, and document statistics | | History | Root/appended transactions, document restore, step diffs, changed ranges, and position mapping | | Selection | Anchor/head ancestry, offsets, boundaries, adjacent nodes, ranges, and marks | | Events & commands | Opt-in handler outcomes, nested calls, synchronous dispatch IDs, and named commands | | Decorations & node views | Opt-in ranges/specs, position overlays, and NodeView lifecycle counters | | Performance | Dispatch timeline, apply/view phases, plugin ranking, hook timings, markers, average, p95, and max | | Plugins | Searchable plugin order, state, runtime keys, and exposed editor props | | Schema | Node specs, mark specs, and the top node type | | Snapshots | Local save, restore, import, export, and delete controls |

When several editors are registered, the toolbar lets you switch the active instance without remounting the panel.

History’s Changes & mapping mode uses captured step maps, including mirrors and custom steps. Diffs are computed for the selected step, including marks and attributes. Filtered and unobserved custom dispatch outcomes do not claim applied mapping results.

JSON branches render 100 children at a time with Show more. Closed document disclosures defer serialization. Narrow layouts stack independently scrollable panes, embedded panels follow their host height, and tab navigation supports arrow keys, Home, and End.

Tiptap 3

The Tiptap entrypoint integrates with Tiptap's lifecycle and native transaction middleware without importing @tiptap/core into this package.

import { Extension } from "@tiptap/core";
import {
  attachTiptapDevtools,
  detachTiptapDevtools,
  dispatchTiptapTransaction,
  setupTiptapDevtools,
} from "prosemirror-inspector/tiptap";

export const TiptapDevtools = Extension.create({
  name: "prosemirrorDevtools",
  priority: 10_000,

  onBeforeCreate() {
    setupTiptapDevtools(this.editor, {
      id: "main-editor",
      label: "Main editor",
    });
  },

  onCreate() {
    attachTiptapDevtools(this.editor);
  },

  dispatchTransaction(props) {
    dispatchTiptapTransaction(this.editor, props);
  },

  onDestroy() {
    detachTiptapDevtools(this.editor);
  },
});

Add TiptapDevtools to the editor's extensions array. Keep a high priority so the measurement surrounds ordinary lower-priority middleware. Do not call props.next yourself; dispatchTiptapTransaction forwards it exactly once.

Keep Tiptap's enableExtensionDispatchTransaction option enabled (the default), and do not also attach the same view through the generic core API. Either change would bypass or duplicate recording.

Plugin instrumentation is enabled by default for this integration. Pass instrumentPlugins: false when you need only transaction history and dispatch phases. Dynamically registered plugins are not instrumented automatically.

This adapter uses the dispatchTransaction extension middleware introduced in Tiptap 3. For Tiptap 2, use the generic core integration.

Plugin performance

instrumentPlugins creates replacement Plugin objects and measures synchronous calls to:

  • state.init and state.apply;
  • filterTransaction and appendTransaction;
  • function-valued editor props and DOM event handlers;
  • node-view creation, updates, events, selection, mutation handling, and cleanup;
  • plugin-view creation, updates, and cleanup.

Return values and thrown errors are preserved, and runtime plugin keys remain stable. Strict object identity does not: code that needs a stable lookup should use a PluginKey.

One call to view.dispatch creates one history record. With the default dispatcher, the panel separates time spent applying state from the synchronous view update. An existing custom dispatchTransaction is preserved and called with its original this value; public applyTransaction and updateState calls made synchronously inside it are observed where possible.

The timings cover synchronous ProseMirror work. Browser paint, compositing, work before dispatch, deferred updates, and asynchronous framework commits are outside the measurement.

Multiple editors and isolated stores

The default prosemirrorDevtoolsStore is shared. Give every live editor a unique public id and the panel will offer an instance selector.

const articleDevtools = createProseMirrorDevtoolsSession({
  id: "article-editor",
  label: "Article",
});

const commentsDevtools = createProseMirrorDevtoolsSession({
  id: "comments-editor",
  label: "Comments",
});

Create a store when a test, application root, or panel must be isolated:

import {
  createProseMirrorDevtoolsSession,
  createProseMirrorDevtoolsStore,
} from "prosemirror-inspector/core";
import { ProseMirrorDevtoolsPanel } from "prosemirror-inspector/react";

const store = createProseMirrorDevtoolsStore();

const devtools = createProseMirrorDevtoolsSession({
  id: "main-editor",
  store,
});

export function Devtools() {
  return <ProseMirrorDevtoolsPanel store={store} />;
}

Pass the same store to the session, panel, and TanStack plugin. Sessions also own a private lifecycle id, so a later editor can safely reuse the same public id after the earlier session is disposed.

History is unbounded by default. Use transactionLimit when a long-running session needs a fixed retention cap, or clear history from the panel. Persistent before/after and intermediate step documents are retained until their JSON or diffs are inspected; full EditorState objects are not retained by history.

Custom performance markers

Add application-specific events or synchronous spans to the dispatch that is currently being recorded:

import { markProseMirrorDevtools, measureProseMirrorDevtools } from "prosemirror-inspector/core";

markProseMirrorDevtools("main-editor", "Remote steps received", {
  category: "collaboration",
  data: { count: steps.length },
});

const result = measureProseMirrorDevtools(
  "main-editor",
  "Rebase remote steps",
  () => rebaseSteps(steps),
  { category: "collaboration", tone: "warning" },
);

A point marker returns false when no dispatch is active. A measured callback always runs; it is simply not recorded when called outside a dispatch.

Panel extensions

Add application-specific tabs without coupling them to a UI framework. An extension receives scoped data, safe actions, host details, and a cleanup signal.

import type { DevtoolsExtension } from "prosemirror-inspector/core";

export const collaborationExtension: DevtoolsExtension = {
  id: "collaboration",
  panels: [
    {
      id: "collaboration-status",
      title: "Collaboration",
      requiresInstance: false,
      mount(container, { data }) {
        const render = () => {
          const editor = data.activeInstance();
          container.textContent = editor ? `Active editor: ${editor.label}` : "No editor connected";
        };

        render();
        return data.activeInstance.subscribe(render);
      },
    },
  ],
};

Pass the extension to either host:

<ProseMirrorDevtoolsPanel extensions={[collaborationExtension]} />;

const plugin = createProseMirrorDevtoolsPlugin({
  extensions: [collaborationExtension],
});

React hosts can use createReactDevtoolsExtension for React-rendered extension panels. All other frameworks can implement the same DOM mount(container, context) => cleanup contract.

Themes and styling

The native panel accepts theme="system", "light", or "dark". A panel hosted by TanStack Devtools inherits the shell theme and avoids duplicate appearance controls.

The panel loads scoped styles automatically. Stable --prosemirror-devtools-* CSS custom properties let applications and extension panels adjust colors, typography, shadows, and radius without depending on generated class names. Typed variable names are exported from prosemirror-inspector/styles; the compiled stylesheet is available as prosemirror-inspector/styles.css for custom hosts.

Performance options

performanceFrameBudgetMs controls the reference line and minimum chart scale. It defaults to a 60 FPS budget:

<ProseMirrorDevtoolsPanel performanceFrameBudgetMs={1000 / 120} />

Each retained dispatch creates a browser User Timing measure by default. Set performanceMarks: false on the session or attachment to disable those measures.

Chrome's Performance panel can also show dispatches, phases, plugin hooks, and application markers as custom tracks:

const devtools = createProseMirrorDevtoolsSession({
  id: "main-editor",
  performanceExtension: true,
});

While recording in Chrome, enable Capture settings → Show custom tracks. Pass an options object instead of true to choose the track grouping or filter very short plugin hooks. This integration uses Chrome's Performance extensibility API and is ignored in unsupported browsers.

Snapshots

The built-in Snapshots panel stores serialized document and selection JSON in localStorage, scoped by editor id. Nothing is uploaded.

Restoring a snapshot dispatches a normal transaction through the current editor. The saved document must therefore be compatible with the current schema. An invalid saved selection is ignored when the document itself can still be restored.

Programmatic helpers are available from the core entrypoint:

import {
  createSnapshot,
  loadSnapshots,
  persistSnapshots,
  restoreSnapshot,
} from "prosemirror-inspector/core";

Package exports

| Export | Purpose | | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ | | prosemirror-inspector | Core APIs plus the native Solid-rendered panel | | prosemirror-inspector/core | Sessions, stores, instrumentation, inspection, serialization, markers, extensions, and snapshots | | prosemirror-inspector/performance | Framework-neutral timing, grouping, ranking, and summary helpers | | prosemirror-inspector/styles | Typed names for the panel's public CSS custom properties | | prosemirror-inspector/styles.css | Compiled panel stylesheet for explicit CSS-only use | | prosemirror-inspector/react | React panel, registration hook, TanStack plugin, and extension adapter | | prosemirror-inspector/preact | Preact panel, registration hook, and TanStack plugin | | prosemirror-inspector/solid | Solid panel, registration helper, and TanStack plugin | | prosemirror-inspector/vue | Vue panel, registration composable, and TanStack plugin | | prosemirror-inspector/svelte | Svelte panel and TanStack plugin | | prosemirror-inspector/angular | Angular panel, injection helper, and TanStack plugin | | prosemirror-inspector/<framework>/production | Explicit production-enabled version of a framework adapter | | prosemirror-inspector/tiptap | Tiptap 3 lifecycle and transaction-middleware helpers |

License

MIT © 2026 Riccardo Perra.