Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2486340
feat: de-couple yjs from blocknote/core
nperez0111 May 13, 2026
a437d1c
fix: proper position tracking for uninitialized doc #2759
nperez0111 May 20, 2026
fbb628e
fix: setup for yjs 14
nperez0111 May 14, 2026
053c9a5
feat: allow user colors on suggestion marks
nperez0111 May 13, 2026
221ad3c
feat: working `@y/prosemirror` demo
nperez0111 May 15, 2026
da657a4
fix: have `@blocknote/core/y` implement `RelativePositionMappingExten…
nperez0111 May 18, 2026
39551bc
fix: slightly better working fork doc plugin
nperez0111 May 18, 2026
571b4f3
feat: more progress into the version history demo
nperez0111 May 19, 2026
59489e9
test: get tests passing again
nperez0111 May 19, 2026
609763d
chore: attrs?
nperez0111 May 19, 2026
9673246
chore: pnpm-lock
nperez0111 May 20, 2026
ca9dd8f
chore: mark `@y/y` as optional
nperez0111 May 21, 2026
3b13bdb
test: copy over RelativePositionMapping tests to @y/y and fix positio…
nperez0111 May 21, 2026
6c3a177
feat: add versioning extension, Yjs thread store, and versioning exam…
nperez0111 May 22, 2026
9acc555
fix: widen forEachAttr callback key type to match @y/y's YType signature
nperez0111 May 22, 2026
89669d2
chore: update excludes & mapAttributionToMark
nperez0111 May 26, 2026
3d29abd
fix: make table content support attribution marks
nperez0111 May 26, 2026
800735c
chore: update `@y/prosemirror` patch
nperez0111 May 28, 2026
1dac678
fix: move modification styles from xl-ai to core
nperez0111 May 29, 2026
d1697e4
chore: update to latest @y/prosemirror patch
nperez0111 May 29, 2026
b3fad88
chore: incldue built deps
nperez0111 May 29, 2026
be86b22
special node approach
nperez0111 May 26, 2026
02b1f17
try suggestion-node prefix approach
nperez0111 May 27, 2026
826ae2f
feat: suggestion node transparency, keyboard/merge/move/split support…
nperez0111 May 27, 2026
8642bf4
chore: use correct node names
nperez0111 Jun 1, 2026
c8d19dc
build: suppress erors for build
nperez0111 Jun 1, 2026
4e96b0c
fix: patch in the latest version of yjs
nperez0111 Jun 1, 2026
f18f42a
build: patch in latest y-prosemirror and fix casing issue with paths
nperez0111 Jun 4, 2026
becfe34
fix: rename the required attribute
nperez0111 Jun 4, 2026
09475fa
fix: allow suggestionBlockContent to also be blockContent
nperez0111 Jun 4, 2026
011985b
chore: additional changes
nperez0111 Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions docs/content/docs/features/collaboration/comments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,28 @@ To enable comments in your editor, you need to:
- Optionally provide a schema for comments and comment editors to use. If left undefined, they will use the [default comment editor schema](https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/Comments/defaultCommentEditorSchema.ts). See [here](/docs/features/custom-schemas) to find out more about custom schemas.

```tsx
const editor = useCreateBlockNote({
extensions: [
CommentsExtension({
// See below.
threadStore: ...,
// Return user information for the given userIds (see below).
resolveUsers: async (userIds: string[]) => { ... },
// Optional, can be left undefined
schema: BlockNoteSchema.create(...)
}),
import { withCollaboration } from "@blocknote/core/yjs";

const editor = useCreateBlockNote(
withCollaboration({
extensions: [
CommentsExtension({
// See below.
threadStore: ...,
// Return user information for the given userIds (see below).
resolveUsers: async (userIds: string[]) => { ... },
// Optional, can be left undefined
schema: BlockNoteSchema.create(...)
}),
...
],
collaboration: {
// See real-time collaboration docs
...
},
...
],
collaboration: {
// See real-time collaboration docs
...
},
...
});
}),
);
```

**Demo**
Expand All @@ -50,7 +54,7 @@ BlockNote comes with several built-in ThreadStore implementations:
The `YjsThreadStore` provides direct Yjs-based storage for comments, storing thread data directly in the Yjs document. This implementation is ideal for simple collaborative setups where all users have write access to the document.

```tsx
import { YjsThreadStore } from "@blocknote/core/comments";
import { YjsThreadStore } from "@blocknote/core/yjs";

const threadStore = new YjsThreadStore(
userId, // The active user's ID
Expand All @@ -68,10 +72,8 @@ The `RESTYjsThreadStore` combines Yjs storage with a REST API backend, providing
In this implementation, data is written to the Yjs document via a REST API which can handle access control. Data is still retrieved from the Yjs document directly (after it's been updated by the REST API), this way all comment information automatically syncs between clients using the existing collaboration provider.

```tsx
import {
RESTYjsThreadStore,
DefaultThreadStoreAuth,
} from "@blocknote/core/comments";
import { DefaultThreadStoreAuth } from "@blocknote/core/comments";
import { RESTYjsThreadStore } from "@blocknote/core/yjs";

const threadStore = new RESTYjsThreadStore(
"https://api.example.com/comments", // Base URL for the REST API
Expand Down
41 changes: 23 additions & 18 deletions docs/content/docs/features/collaboration/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,41 @@ Let's see how you can add Multiplayer capabilities to your BlockNote setup, and

_Try the live demo on the [homepage](https://www.blocknotejs.org)_

BlockNote uses [Yjs](https://github.com/yjs/yjs) for this, and you can set it up with the `collaboration` option:
BlockNote uses [Yjs](https://github.com/yjs/yjs) for this, and you can set it up with the `withCollaboration` helper:

```typescript
import * as Y from "yjs";
import { WebrtcProvider } from "y-webrtc";
import { withCollaboration } from "@blocknote/core/yjs";
// ...

const doc = new Y.Doc();

const provider = new WebrtcProvider("my-document-id", doc); // setup a yjs provider (explained below)
const editor = useCreateBlockNote({
// ...
collaboration: {
// The Yjs Provider responsible for transporting updates:
provider,
// Where to store BlockNote data in the Y.Doc:
fragment: doc.getXmlFragment("document-store"),
// Information (name and color) for this user:
user: {
name: "My Username",
color: "#ff0000",
const editor = useCreateBlockNote(
withCollaboration({
// ...
collaboration: {
// The Yjs Provider responsible for transporting updates:
provider,
// Where to store BlockNote data in the Y.Doc:
fragment: doc.getXmlFragment("document-store"),
// Information (name and color) for this user:
user: {
name: "My Username",
color: "#ff0000",
},
// When to show user labels on the collaboration cursor. Set by default to
// "activity" (show when the cursor moves), but can also be set to "always".
showCursorLabels: "activity",
},
// When to show user labels on the collaboration cursor. Set by default to
// "activity" (show when the cursor moves), but can also be set to "always".
showCursorLabels: "activity",
},
// ...
});
// ...
}),
);
```

The `withCollaboration` function accepts all the regular editor options along with a `collaboration` property, and configures your editor for real-time collaboration.

## Yjs Providers

When a user edits the document, an incremental change (or "update") is captured and can be shared between users of your app. You can share these updates by setting up a _Yjs Provider_. In the snipped above, we use [y-webrtc](https://github.com/yjs/y-webrtc) which shares updates over WebRTC (and BroadcastChannel), but you might be interested in different providers for production-ready use cases.
Expand Down
9 changes: 8 additions & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,14 @@
"tailwind-merge": "^3.4.0",
"y-partykit": "^0.0.25",
"yjs": "^13.6.27",
"zod": "^4.3.5"
"zod": "^4.3.5",
"@y/protocols": "^1.0.6-rc.1",
"@y/websocket": "^4.0.0-3",
"@y/y": "^14.0.0-rc.16",
"@y/prosemirror": "^2.0.0-2",
"@floating-ui/react": "^0.27.18",
"lib0": "1.0.0-rc.13",
"y-websocket": "^2.1.0"
},
"devDependencies": {
"@blocknote/code-block": "workspace:*",
Expand Down
92 changes: 91 additions & 1 deletion examples/01-basic/01-minimal/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,101 @@ import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
import { useEffect } from "react";

export default function App() {
// Creates a new editor instance.
// Creates a new editor instance with a default empty paragraph.
const editor = useCreateBlockNote();

// After the editor is created, replace its document with a ProseMirror
// structure that includes a paragraph--atributed before the blockContainer's paragraph.
useEffect(() => {
// Use editor.transact to dispatch a ProseMirror transaction that replaces
// the entire document content.
editor.transact((tr) => {
const { nodes } = editor.pmSchema;

// Build the paragraph--atributed (shadow node for suggestions)
const suggestionParagraph = nodes["paragraph--atributed"].create(
{
backgroundColor: "default",
textAlignment: "left",
textColor: "default",
__suggestionData: "true",
},
[editor.pmSchema.text("Hello from paragraph--atributed!")],
);

// Build the main blockContent paragraph
const mainParagraph = nodes.paragraph.create(
{
backgroundColor: "default",
textAlignment: "left",
textColor: "default",
},
[editor.pmSchema.text("Hello from blockContainer!")],
);

// Build the blockContainer with paragraph--atributed before blockContent
//
// Target structure:
// doc
// └─ blockGroup
// └─ blockContainer
// ├─ paragraph--atributed("Hello from paragraph--atributed!")
// └─ paragraph("Hello from blockContainer!")
const blockContainer1 = nodes.blockContainer.create({ id: "block-1" }, [
suggestionParagraph,
mainParagraph,
]);

// Second block: paragraph with trailing suggestion
const mainParagraph2 = nodes.paragraph.create(
{
backgroundColor: "default",
textAlignment: "left",
textColor: "default",
},
[editor.pmSchema.text("Second block main content")],
);
const trailingSuggestion = nodes["paragraph--atributed"].create(
{
backgroundColor: "default",
textAlignment: "left",
textColor: "default",
__suggestionData: "true",
},
[editor.pmSchema.text("Trailing suggestion text")],
);
const blockContainer2 = nodes.blockContainer.create({ id: "block-2" }, [
mainParagraph2,
trailingSuggestion,
]);

// Third block: plain paragraph (no suggestions)
const mainParagraph3 = nodes.paragraph.create(
{
backgroundColor: "default",
textAlignment: "left",
textColor: "default",
},
[editor.pmSchema.text("Third block, no suggestions")],
);
const blockContainer3 = nodes.blockContainer.create({ id: "block-3" }, [
mainParagraph3,
]);

const blockGroup = nodes.blockGroup.create(null, [
blockContainer1,
blockContainer2,
blockContainer3,
]);
const newDoc = nodes.doc.create(null, [blockGroup]);

tr.replaceWith(0, tr.doc.content.size, newDoc.content);
});
}, [editor]);

// Renders the editor instance using a React component.
return <BlockNoteView editor={editor} />;
}
27 changes: 15 additions & 12 deletions examples/07-collaboration/01-partykit/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import YPartyKitProvider from "y-partykit/provider";
import * as Y from "yjs";
import { withCollaboration } from "@blocknote/core/yjs";

// Sets up Yjs document and PartyKit Yjs provider.
const doc = new Y.Doc();
Expand All @@ -15,19 +16,21 @@ const provider = new YPartyKitProvider(
);

export default function App() {
const editor = useCreateBlockNote({
collaboration: {
// The Yjs Provider responsible for transporting updates:
provider,
// Where to store BlockNote data in the Y.Doc:
fragment: doc.getXmlFragment("document-store"),
// Information (name and color) for this user:
user: {
name: "My Username",
color: "#ff0000",
const editor = useCreateBlockNote(
withCollaboration({
collaboration: {
// The Yjs Provider responsible for transporting updates:
provider,
// Where to store BlockNote data in the Y.Doc:
fragment: doc.getXmlFragment("document-store"),
// Information (name and color) for this user:
user: {
name: "My Username",
color: "#ff0000",
},
},
},
});
}),
);

// Renders the editor instance.
return <BlockNoteView editor={editor} />;
Expand Down
17 changes: 10 additions & 7 deletions examples/07-collaboration/03-y-sweet/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useYDoc, useYjsProvider, YDocProvider } from "@y-sweet/react";
import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import { withCollaboration } from "@blocknote/core/yjs";

import "@blocknote/mantine/style.css";

Expand All @@ -23,13 +24,15 @@ function Document() {
const provider = useYjsProvider();
const doc = useYDoc();

const editor = useCreateBlockNote({
collaboration: {
provider,
fragment: doc.getXmlFragment("blocknote"),
user: { color: "#ff0000", name: "My Username" },
},
});
const editor = useCreateBlockNote(
withCollaboration({
collaboration: {
provider,
fragment: doc.getXmlFragment("blocknote"),
user: { color: "#ff0000", name: "My Username" },
},
}),
);

return <BlockNoteView editor={editor} />;
}
7 changes: 4 additions & 3 deletions examples/07-collaboration/05-comments/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import {
CommentsExtension,
DefaultThreadStoreAuth,
YjsThreadStore,
} from "@blocknote/core/comments";
import { withCollaboration, YjsThreadStore } from "@blocknote/core/yjs";

import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
Expand Down Expand Up @@ -74,14 +75,14 @@ function Document() {

// setup the editor with comments and collaboration
const editor = useCreateBlockNote(
{
withCollaboration({
collaboration: {
provider,
fragment: doc.getXmlFragment("blocknote"),
user: { color: getRandomColor(), name: activeUser.username },
},
extensions: [CommentsExtension({ threadStore, resolveUsers })],
},
}),
[activeUser, threadStore],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import {
DefaultThreadStoreAuth,
YjsThreadStore,
CommentsExtension,
} from "@blocknote/core/comments";
import { withCollaboration, YjsThreadStore } from "@blocknote/core/yjs";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
Expand Down Expand Up @@ -77,14 +77,14 @@ export default function App() {

// setup the editor with comments and collaboration
const editor = useCreateBlockNote(
{
withCollaboration({
collaboration: {
provider,
fragment: doc.getXmlFragment("blocknote"),
user: { color: getRandomColor(), name: activeUser.username },
},
extensions: [CommentsExtension({ threadStore, resolveUsers })],
},
}),
[activeUser, threadStore],
);

Expand Down
Loading
Loading