---
meta:
  title: "Code editor"
  parentTitle: "Use cases"
  description:
    "Build collaborative code editors with CodeMirror or Monaco, synchronized
    with LiveText, with AI editing, version history, and comments."
---

Create a collaborative code editor with Liveblocks. Synchronize code between
users with character-level precision, show remote carets and selections, edit
files with AI, and restore previous versions.

<Figure
  caption={
    <>
      Code editing in the{" "}
      <a href="/examples/ai-slideshow/nextjs-ai-slideshow">AI Slideshow</a>{" "}
      example
    </>
  }
>
  <MuxVideo
    playbackId="S00hyNLmMWWa01xRShiRw2DOgraTuX7TxD9dsf5zwvQmI"
    alt="Code editor demo blog"
    static={true}
    height={520}
    width={768}
  />
</Figure>

## Features [#features]

- [**Realtime collaboration**](#realtime-collaboration): The code file is
  permanent and updates in realtime for connected users.
- [**Presence**](#presence): Show remote carets, selections, avatar stacks, and
  agent activity.
- [**Server-side editing**](#server-side-editing): Modify documents from your
  back end, and watch them update in realtime.
- [**Agentic editing**](#agentic-editing): Generate and apply code changes with
  AI agents.
- [**Version history**](#version-history): Save, preview, and restore file
  versions using manual or automatic snapshots.
- [**Multiplayer undo/redo**](#multiplayer-undo-redo): Each user can
  independently undo and redo their own changes.
- [**Comments**](#comments): Attach review discussions to the code.
- [**Permissions**](#permissions): Control which users can read and edit the
  file.

## Get started [#get-started]

Choose a starting point for your code editor.

<ListGrid columns={2} defaultVisibleItems={2}>
  <DocsCard
    type="technology"
    title="Get started with CodeMirror"
    href="/docs/get-started/nextjs-codemirror"
    description="Code editor built on LiveText with Next.js"
    visual={<DocsNextjsIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with Monaco and Yjs"
    href="/docs/get-started/yjs-monaco-react"
    description="Yjs-backed code editor with React"
    visual={<DocsReactIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with CodeMirror"
    href="/docs/get-started/react-codemirror"
    description="Code editor built on LiveText with React"
    visual={<DocsReactIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with CodeMirror & Yjs"
    href="/docs/get-started/yjs-codemirror-react"
    description="Yjs-backed code editor with React"
    visual={<DocsReactIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with CodeMirror & Yjs"
    href="/docs/get-started/yjs-codemirror-svelte"
    description="Yjs-backed code editor with Svelte"
    visual={<DocsSvelteIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with Monaco & Yjs"
    href="/docs/get-started/yjs-monaco-svelte"
    description="Yjs-backed code editor with Svelte"
    visual={<DocsSvelteIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with CodeMirror & Yjs"
    href="/docs/get-started/yjs-codemirror-vuejs"
    description="Yjs-backed code editor with Vue.js"
    visual={<DocsJavascriptIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with Monaco & Yjs"
    href="/docs/get-started/yjs-monaco-vuejs"
    description="Yjs-backed code editor with Vue.js"
    visual={<DocsJavascriptIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with CodeMirror & Yjs"
    href="/docs/get-started/yjs-codemirror-javascript"
    description="Yjs-backed code editor with JavaScript"
    visual={<DocsJavascriptIcon />}
  />
  <DocsCard
    type="technology"
    title="Get started with Monaco & Yjs"
    href="/docs/get-started/yjs-monaco-javascript"
    description="Yjs-backed code editor with JavaScript"
    visual={<DocsJavascriptIcon />}
  />
</ListGrid>

## Implementation [#implementation]

This is an overview of how each feature can be implemented using the
[`@liveblocks/codemirror`](/docs/api-reference/liveblocks-codemirror) package.
Code files are stored in [Sync](/docs/products/sync) and live selections are
broadcast using [Presence](/docs/products/sync/presence).

### Realtime collaboration [#realtime-collaboration]

Using
[`createLiveblocksSyncPlugin`](/docs/api-reference/liveblocks-codemirror#createLiveblocksSyncPlugin),
you can set up a CodeMirror editor that stores your code as a
[`LiveText`](/docs/api-reference/liveblocks-client#LiveText) using
[Sync](/docs/products/sync). Local edits are written to Sync, remote edits are
applied to the editor, and concurrent changes are merged character-by-character.

```tsx
"use client";

import { useEffect, useRef } from "react";
import { EditorView } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import type { LiveText } from "@liveblocks/client";
import {
  createLiveblocksSyncPlugin,
  createLiveblocksPresencePlugin,
} from "@liveblocks/codemirror";
import { useRoom } from "@liveblocks/react/suspense";

function CodeEditor({ text }: { text: LiveText }) {
  const room = useRoom();
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (containerRef.current === null) return;

    const view = new EditorView({
      parent: containerRef.current,
      state: EditorState.create({
        // +++
        doc: text.toString(),
        // +++
        extensions: [
          // +++
          createLiveblocksSyncPlugin(room, text),
          // +++
          createLiveblocksPresencePlugin(room, text),
        ],
      }),
    });

    return () => {
      view.destroy();
    };
  }, [room, text]);

  return <div ref={containerRef} />;
}
```

Multiple code files can be stored in a single room by passing different
`LiveText` values. Learn more under
[Sync integrations](/docs/products/sync/integrations) and
[Storage](/docs/products/sync/storage).

### Presence [#presence]

Broadcast each user’s caret and selection with
[`createLiveblocksPresencePlugin`](/docs/api-reference/liveblocks-codemirror#createLiveblocksPresencePlugin).
Remote carets are colored with each user’s
[`info.color`](/docs/api-reference/liveblocks-client#Room.getSelf), and
positions stay stable across concurrent edits.

```tsx
"use client";

import { useEffect, useRef } from "react";
import { EditorView } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import type { LiveText } from "@liveblocks/client";
import {
  createLiveblocksSyncPlugin,
  createLiveblocksPresencePlugin,
} from "@liveblocks/codemirror";
import { useRoom } from "@liveblocks/react/suspense";

function CodeEditor({ text }: { text: LiveText }) {
  const room = useRoom();
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (containerRef.current === null) return;

    const view = new EditorView({
      parent: containerRef.current,
      state: EditorState.create({
        doc: text.toString(),
        extensions: [
          createLiveblocksSyncPlugin(room, text),
          // +++
          createLiveblocksPresencePlugin(room, text),
          // +++
        ],
      }),
    });

    return () => {
      view.destroy();
    };
  }, [room, text]);

  return <div ref={containerRef} />;
}
```

To show who’s currently in the file, add the ready-made
[`AvatarStack`](/docs/api-reference/liveblocks-react-ui#AvatarStack) component,
or build custom presence UI with
[`useOthers`](/docs/api-reference/liveblocks-react#useOthers). Learn more under
[Presence](/docs/products/sync/presence).

### Server-side editing [#server-side-editing]

Trusted server processes can edit the code with
[`Liveblocks.mutateStorage`](/docs/api-reference/liveblocks-node#mutate-storage),
using the same [`LiveText`](/docs/api-reference/liveblocks-client#LiveText) API
as the client. The mutation targets the exact document the editor syncs with,
and connected users see the change in realtime.

```ts
import { Liveblocks } from "@liveblocks/node";

const liveblocks = new Liveblocks({
  secret: process.env.LIVEBLOCKS_SECRET_KEY!,
});

// +++
await liveblocks.mutateStorage("my-room", ({ root }) => {
  const document = root.get("document");
  document.insert(document.length, "\n// TODO: add tests");
});
// +++
```

Learn more under [Server-side editing](/docs/products/sync/server-side-editing).

### Agentic editing [#agentic-editing]

To allow AI agents to modify your code, generate your changes with AI then use
[`mutateStorage`](/docs/api-reference/liveblocks-node#mutate-storage) to apply
them. To show that AI is working in your app use
[`setPresence`](/docs/api-reference/liveblocks-node#post-rooms-roomId-presence)
to show it working—your agent will appear in [Presence](#presence) alongside
humans. Finally, remove the agent’s presence to indicate that the agent is no
longer working.

```ts
import { Liveblocks } from "@liveblocks/node";
import { generateText, Output } from "ai";
import { z } from "zod";

const liveblocks = new Liveblocks({
  secret: process.env.LIVEBLOCKS_SECRET_KEY!,
});

const roomId = "code-room";
const agent: Liveblocks["UserMeta"] = {
  id: "ai-agent",
  info: { name: "AI agent", color: "#7c3aed" },
};

// +++
await liveblocks.setPresence(roomId, {
  userId: agent.id,
  userInfo: agent.info,
  data: { status: "thinking" },
  ttl: 60,
});
// +++

// +++
await liveblocks.mutateStorage(roomId, async ({ root }) => {
  const text = root.get("document");

  const { output } = await generateText({
    model: "openai/gpt-5.6-sol",
    output: Output.object({
      schema: z.object({
        code: z.string(),
      }),
    }),
    prompt: `Write a unit test for the file. Here is the current file: ${text.toJSON()}`,
  });

  text.insert(text.length, `\n${output.code}`);
});
// +++

// +++
await liveblocks.setPresence(roomId, {
  userId: agent.id,
  userInfo: agent.info,
  data: { status: "idle" },
  ttl: 2,
});
// +++
```

Additionally, you can use [Feeds](/docs/products/sync/feeds) to store AI
workflow state, and to pass agent status updates to the UI. Learn more under
[Agentic editing](/docs/products/sync/agentic-editing).

### Version history [#version-history]

Create version snapshots before major changes, such as agentic edits, then
preview and restore them. Use
[`useHistoryVersions`](/docs/api-reference/liveblocks-react#useHistoryVersions)
to list versions,
[`useHistoryVersionStorageData`](/docs/api-reference/liveblocks-react#useHistoryVersionStorageData)
to build a read-only preview, and
[`useRestoreToStorageVersion`](/docs/api-reference/liveblocks-react#useRestoreToStorageVersion)
to restore the complete file as one synchronized change.

```tsx
import { useRestoreToStorageVersion } from "@liveblocks/react/suspense";

function RestoreFile({ versionId }: { versionId: string }) {
  // +++
  const restore = useRestoreToStorageVersion(versionId);
  // +++

  // +++
  return <button onClick={() => restore()}>Restore this version</button>;
  // +++
}
```

Automatic versions can be enabled in the dashboard, and meaningful versions can
be created from a backend with
[`Liveblocks.createVersionHistorySnapshot`](/docs/api-reference/liveblocks-node#create-version-history-snapshot).
Learn more under [Version history](/docs/products/sync/version-history).

### Multiplayer undo/redo [#multiplayer-undo-redo]

[`createLiveblocksSyncPlugin`](/docs/api-reference/liveblocks-codemirror#createLiveblocksSyncPlugin)
wires the editor’s standard keyboard shortcuts, such as `Mod-z` and `Mod-y`, to
the room’s history, so each user can independently undo and redo their own
changes. Connect the same history to custom buttons with
[`useUndo`](/docs/api-reference/liveblocks-react#useUndo),
[`useRedo`](/docs/api-reference/liveblocks-react#useRedo),
[`useCanUndo`](/docs/api-reference/liveblocks-react#useCanUndo), and
[`useCanRedo`](/docs/api-reference/liveblocks-react#useCanRedo).

```tsx
import {
  useCanRedo,
  useCanUndo,
  useRedo,
  useUndo,
} from "@liveblocks/react/suspense";

function CodeEditorToolbar() {
  // +++
  const undo = useUndo();
  const redo = useRedo();
  const canUndo = useCanUndo();
  const canRedo = useCanRedo();
  // +++

  return (
    <>
      // +++
      <button onClick={undo} disabled={!canUndo}>
        Undo
      </button>
      <button onClick={redo} disabled={!canRedo}>
        Redo
      </button>
      // +++
    </>
  );
}
```

Learn more under
[Multiplayer undo/redo](/docs/products/sync/storage#multiplayer-undo-redo).

### Comments [#comments]

Add review discussions with [Comments](/docs/products/comments) by attaching a
line number to each thread’s metadata. Create threads with the ready-made
[`Composer`](/docs/api-reference/liveblocks-react-ui#Composer) component, then
list them beside the editor with
[`useThreads`](/docs/api-reference/liveblocks-react#useThreads) and
[`Thread`](/docs/api-reference/liveblocks-react-ui#Thread).

```tsx
import { useThreads } from "@liveblocks/react/suspense";
import { Thread } from "@liveblocks/react-ui";

function CodeReviewThreads() {
  // +++
  const { threads } = useThreads({ query: { resolved: false } });
  // +++

  return (
    <aside>
      // +++
      {threads.map((thread) => (
        <div key={thread.id}>
          <span>Line {thread.metadata.line}</span>
          <Thread thread={thread} />
        </div>
      ))}
      // +++
    </aside>
  );
}
```

Learn more under [Comments](/docs/products/comments).

### Permissions [#permissions]

Each code file is contained inside a room in your Liveblocks app, and permission
groups can set access to the file. For example, your file may have an editor
group and a viewer group. This can be set when modifying or creating a room, for
example with
[`Liveblocks.createRoom`](/docs/api-reference/liveblocks-node#post-rooms).

```ts
await liveblocks.createRoom(`my-room-id`, {
  defaultAccesses: [
    // No access by default
  ],
  groupsAccesses: {
    // "viewers" group has read access
    viewers: ["*:read"],
  },
  usersAccesses: {
    // "olivier" has write access
    olivier: ["*:write"],
  },
});
```

More complex controls can be set too, learn more under
[Permissions](/docs/api-reference/authentication/permissions).

## Examples [#examples]

Explore complete implementations in our example gallery.

<ListGrid columns={2}>
  <ExampleCard
    example={{
      title: "Collaborative Code Editor (CodeMirror)",
      slug: "collaborative-code-editor/nextjs-yjs-codemirror",
      image: "/images/examples/thumbnails/code-editor.jpg",
    }}
    technologies={["nextjs"]}
    openInNewWindow
  />
  <ExampleCard
    example={{
      title: "Collaborative Code Editor (Monaco)",
      slug: "collaborative-code-editor/nextjs-yjs-monaco",
      image: "/images/examples/thumbnails/code-editor.jpg",
    }}
    technologies={["nextjs"]}
    openInNewWindow
  />
</ListGrid>

---

For an overview of all available documentation, see [/llms.txt](/llms.txt).
