Skip to content

Live Documents

The live-doc plugin lets every member of a channel co-edit a single shared document in real time. Documents support rich formatting, images with float modes, tables, math (LaTeX), code blocks, and collaborative cursors showing where each participant is typing.

Content is synchronized using Yjs - a conflict-free replicated data type (CRDT) - transported over a dedicated WebSocket port. Changes made while offline merge automatically when the client reconnects.

Fancy Mumble clientMumble C++ serverlive-doc plugin(Rust, FFI)File server pluginport 64739 HTTPLive-doc WebSocketport 64740 WSStandard y-protocols frames:sync step 1 / 2 / update /awareness query & update.FancyLiveDocOpen (wire 141)FancyLiveDocInviteFancyLiveDocAnnounce (broadcast to channel)FFI callbackFancyLiveDocInvite (token + ws_url)WebSocket (Yjs sync)CRDT updatesPUT /admin/documents/{name} (snapshot)
  1. The client sends a FancyLiveDocOpen plugin-data message (wire ID 141).
  2. The server passes it to the live-doc plugin via FFI.
  3. The plugin mints a short-lived JWT and replies with FancyLiveDocInvite containing the token and the WebSocket URL.
  4. The server broadcasts FancyLiveDocAnnounce to the channel so every member can join.
  5. All participants connect to the WebSocket endpoint and exchange Yjs CRDT frames directly.
  6. After a configurable idle window the plugin flushes a binary snapshot to the file server for persistence across restarts.

The live-doc plugin depends on the file server plugin for persistence. Without it, documents are kept in memory only and lost whenever the container restarts or the teardown grace window expires.

See File server for setup instructions.

The plugin is configured only via a mounted INI file - the plugin.live-doc.* keys are not mapped to MUMBLE_CONFIG_* environment variables because they require absolute paths that vary per deployment.

  1. If you do not already have a custom config file, copy the template from the image:

    Terminal window
    docker cp mumble-server:/data/mumble_server_config.ini ./mumble-server.ini
  2. Add the following block to your mumble-server.ini, adjusting paths and URLs for your deployment:

    ; Master toggle. Default: false.
    plugin.live-doc.enabled=true
    ; Required: writable directory for JWT secret and room state.
    plugin.live-doc.state_path=/data/live-doc-state
    ; WebSocket bind address and port. Default: 0.0.0.0:64740.
    ;plugin.live-doc.host=0.0.0.0
    ;plugin.live-doc.port=64740
    ; Public base URL for clients (use wss:// when behind a TLS proxy).
    ;plugin.live-doc.public_url=wss://chat.example.com/live-doc
    ; Persistence bridge. Must match file-server admin token.
    plugin.live-doc.file_server_url=http://127.0.0.1:64739
    plugin.live-doc.file_server_admin_token=your-shared-secret
  3. Mount the file and create the state directory:

    environment:
    MUMBLE_CUSTOM_CONFIG_FILE: /data/mumble-server.ini
    volumes:
    - ./mumble-server.ini:/data/mumble-server.ini:ro
    - mumble-data:/data # live-doc-state lives inside here
  4. Expose the WebSocket port (default 64740) in docker-compose.yml:

    ports:
    - "64738:64738/tcp"
    - "64738:64738/udp"
    - "64739:64739/tcp"
    - "64740:64740/tcp" # live-doc WebSocket
  5. Restart:

    Terminal window
    docker compose up -d --force-recreate mumble-server
KeyDefaultDescription
plugin.live-doc.enabledfalseMaster toggle.
plugin.live-doc.state_path-Required. Absolute path to a writable directory. The plugin stores its JWT signing secret here.
plugin.live-doc.host0.0.0.0WebSocket bind address.
plugin.live-doc.port64740WebSocket TCP port.
plugin.live-doc.public_urlunsetURL clients use to connect (e.g. wss://chat.example.com/live-doc). Falls back to ws://<bind address>:<port> when empty.
plugin.live-doc.max_update_bytes4194304 (4 MiB)Maximum size of a single CRDT update.
plugin.live-doc.snapshot_idle_secs60Seconds of inactivity before the in-memory document is flushed to the file server.
plugin.live-doc.teardown_grace_secs30Seconds after the last viewer disconnects before the in-memory room is torn down.
plugin.live-doc.file_server_urlunsetBase URL of the file server admin endpoint (e.g. http://127.0.0.1:64739). When unset, documents are never persisted.
plugin.live-doc.file_server_admin_tokenunsetShared secret for the file server admin API. Required when file_server_url is set.

Like the file server, the live-doc WebSocket should be TLS-terminated by a reverse proxy on public deployments.

handle_path strips the matched prefix automatically:

chat.example.com {
handle_path /live-doc/* {
reverse_proxy localhost:64740
}
}

Then set plugin.live-doc.public_url=wss://chat.example.com/live-doc.

When both file_server_url and file_server_admin_token are set, the plugin automatically:

  • Flushes a binary CRDT snapshot to PUT /admin/documents/{name} every snapshot_idle_secs seconds after the last update.
  • Flushes once more when the last viewer disconnects and the teardown grace window expires.
  • Loads the latest saved snapshot when the first viewer opens the document.

Documents that have never been persisted start empty for every viewer.

  • Port 64740 not exposed: the client will time out waiting for the WebSocket invite reply. Check docker compose ps and confirm the port mapping.
  • state_path missing or not writable: the plugin logs an error and refuses to start. The client will never receive a FancyLiveDocInvite.
  • File server admin token mismatch: snapshots fail silently (logged server-side). Documents open and sync normally but are lost on restart.
  • MUMBLE_CUSTOM_CONFIG_FILE not set: the plugin keys are read from a mounted INI but the entrypoint-generated config does not include them. Confirm MUMBLE_CUSTOM_CONFIG_FILE points at your file.

You have walked through all of the feature deep-dives. Continue with Customize & disable features for the consolidated toggle reference.