🎉 pluv.io is now stable! Read our announcement here
pluv.io 3.0 - Rich Text Editors, Free Tier, New Docs and more
pluv.io 3.0 - Rich Text Editors, Free Tier, New Docs and more

pluv.io 3.0 - Rich Text Editors, Free Tier, New Docs and more

May 27th
Engineering
Update

Overview: Unlock collaborative editing with rich text support, explore revamped documentation, and get started for free with pluv.io 3.0 — the easiest way to build real-time apps.

Introduction

pluv.io 3.0 is finally out, and with it brings major developer experience, stability and usability enhancements. Here’s what’s new:
 
  • 🚀 Yjs Provider: Plug into collaborative editors using our official Yjs connection provider.

Yjs Provider

Yjs defines a connection provider interface used by many collaborative libraries. While pluv.io has long been Yjs-compatible, it was still difficult to build more complex collaborative features, as pluv.io had not provided a compatible provider.
As of @pluv/crdt-yjs@2.2.0, pluv.io ships with its own official Yjs provider, allowing easy integration with popular rich text editors that support Yjs. The Yjs docs currently list six examples of editor bindings, and pluv.io adds docs for BlockNote, Lexical and Slate - bringing a total of nine supported editors.
 
Here’s a sample using BlockNote with pluv.io’s Yjs provider:
import { BlockNoteView } from "@blocknote/mantine"; import { useCreateBlockNote } from "@blocknote/react"; import { yjs } from "@pluv/crdt-yjs"; import { useMemo } from "react"; import { useRoom, useStorage } from "./pluv"; // ... const room = useRoom(); const [, fragment] = useStorage(); const provider = useMemo(() => { return yjs.provider({ room, // The presence field we choose to store // Yjs Awaneness data onto presenceField: "myAwareness", }); }, [room]); // ... Later on const editor = useCreateBlockNote({ collaboration: { provider, fragment, user: { name: "John Doe", color: "#00FF00" }, }, }); return <BlockNoteView editor={editor} />;

Streamlined Subscriptions (Breaking)

We’ve unified how you subscribe to room data with a new subscribe API. Previously, PluvRoom methods like .event, .storage, and .other were inconsistent. Now they’re organized under a single room.subscribe namespace for improved discoverability and DX.
Migration is simple:
const room = client.createRoom("example-room"); // Before room.subscribe("connection", (state) => {}); room.subscribe("my-presence", (myPresence) => {}); room.subscribe("myself", (myself) => {}); room.subscribe("others", (others) => {}); room.subscribe("storage-loaded", (storageLoaded) => {}); room.event.receiveMessage((event) => {}); room.storage.messages((messages) => {}); room.storageRoot((storage) => {}); room.other(("id_...", other) => {}); // After room.subscribe.connection((state) => {}); room.subscribe.myPresence((myPresence) => {}); room.subscribe.myself((myself) => {}); room.subscribe.others((others) => {}); room.subscribe.storageLoaded((storageLoaded) => {}); room.subscribe.event.receiveMessage((event) => {}); room.subscribe.storage.messages((messages) => {}); room.subscribe.storageRoot((storage) => {}); room.subscribe.other(("id_...", other) => {});
See the new v3 Breaking Changes migration guide for full details.

Improved CRDT Initialization (Breaking)

Our CRDT helpers in @pluv/crdt-yjs and @pluv/crdt-loro focused on improving the ergonomics of working with the underlying CRDT libraries (yjs and loro-crdt). While elegant, these abstractions added friction to working with these CRDTs.
In 3.0, we’ve simplified this:
  1. Declaring top-level storage fields now require using a document builder exposed in the .doc function, while nested types remain unchanged.
    1. import { yjs } from "@pluv/crdt-yjs"; import { Text as YText } from "yjs"; // Before yjs.doc(() => ({ groceries: yjs.array<string>([]), messages: yjs.array([ yjs.text("hello world"), ]), })); // After yjs.doc((t) => ({ // Top-level fields now use the `t` builder groceries: t.array<string>("groceries", []), messages: t.array("messages", [ // Nested fields continue using the yjs utility yjs.text("hello world"), ]), }));
  1. The yjs type-helpers return their native Yjs shared-types, and the loro type-helpers return their native Loro container-types.
    1. import { yjs } from "@pluv/crdt-yjs"; import { Text as YText } from "yjs"; import { useStorage } from "./pluv"; const [messages, yArray] = useStorage("messages"); // This means that these are ultimately the same yArray.push([yjs.text("hello world")]); yArray.push([new YText("hello world")]);
These changes not only improve the usability of these types, but were also necessary to enable assigning top-level shared-types to an IIFE for Slate.

Inferred CRDT Document Types

useDoc and PluvRoom.getDoc are now properly type-inferred and return their correctly-typed CRDT documents - no more manual typing or casting necessary.
import { createClient } from "@pluv/client"; import { yjs } from "@pluv/crdt-yjs"; import { Doc as YDoc } from "yjs"; const io = createClient({ // ... initialStorage: yjs.doc((t) => ({ /* ... */ }), }); const room = io.createRoom("example-room"); const doc = room.getDoc().value; // ^? const doc: YDoc;

Major Stability Fixes

We’ve resolved a number of long-standing issues that impacted connection reliability and storage behavior:
  1. 🧵 Fixed a race condition during the first initialStorage sync, sometimes causing documents to not be able to properly sync.
  1. 🗃️ Fixed race condition in @pluv/addon-indexeddb causing storage diffs to apply incorrectly between the browser’s IndexedDB and the server’s emitted sync state.
  1. 💾 Fixed persistence issues in Cloudflare Durable Object-backed SQLite storage.
  1. 🧱 Fixed shared object reference errors in @pluv/platform-cloudflare between multiple Durable Objects.

Revamped Documentation

We’ve rebuilt the pluv.io documentation to reflect the stability and maturity of the platform. Previously, the docs were written as we shipped features - often without the time to focus on making the docs easy to understand.
Now, with a much more stable API surface, we’ve focused on:
  • Better navigation
  • Clearer examples
  • More consistent terminology
There’s still room to grow, but this marks a big step forward in our commitment to great DX.
Want to help? Docs live on GitHub as .mdx files. Feel free to open a PR:

Free Tier

Starting today, pluv.io offered a fully managed free tier so taht you can start building multiplayer experiences immediately.
Just signup to start using @pluv/platform-pluv, and follow our quickstart guide to go live within minutes.
Want full control? Self-hosting pluv.io remains completely free with zero restrictions. Whether you deploy on Cloudflare Workers or Node.js, we’ll continue to support and recommend running it yourself if that’s your preference.
Let’s build some incredible multiplayer experiences together! 🚀
 
Follow me on Twitter@i3dly_dev or Bluesky@i3dly.dev for more updates!