# Collaborative Editing Milkdown supports collaborative editing powered by [Y.js](https://docs.yjs.dev/). We provide the [@milkdown/plugin-collab](/docs/api/plugin-collab) plugin to help you use milkdown with yjs easily. This plugin includes basic collaborative editing features like: - Sync between clients. - Remote cursor support. - Undo/Redo support. ::iframe{src="https://stackblitz.com/github/Milkdown/examples/tree/main/vanilla-collab"} ## Configure Plugin First you need to install the plugin and yjs through npm: ```bash npm install @milkdown/plugin-collab npm install yjs y-protocols y-prosemirror ``` And you also need to choose a [provider for yjs](https://docs.yjs.dev/ecosystem/connection-provider), here we use [y-websocket](https://docs.yjs.dev/ecosystem/connection-provider/y-websocket) as an example. After the installation, you can configure your editor: ```typescript // ...import other plugins import { collab, collabServiceCtx } from '@milkdown/plugin-collab' async function setup() { const editor = await Editor.make() .config(nord) .use(commonmark) .use(collab) .create() const doc = new Doc() const wsProvider = new WebsocketProvider('', 'milkdown', doc) editor.action((ctx) => { const collabService = ctx.get(collabServiceCtx) collabService // bind doc and awareness .bindDoc(doc) .setAwareness(wsProvider.awareness) // connect yjs with milkdown .connect() }) } ``` Now your editor can support collaborative editing. Isn't it easy? ## Connect and Disconnect You may want to control the connect status of the editor manually. ```typescript editor.action((ctx) => { const collabService = ctx.get(collabServiceCtx) const doc = new Doc() const wsProvider = new WebsocketProvider('', 'milkdown', doc) collabService.bindDoc(doc).setAwareness(wsProvider.awareness) document.getElementById('connect').onclick = () => { wsProvider.connect() collabService.connect() } document.getElementById('disconnect').onclick = () => { wsProvider.disconnect() collabService.disconnect() } }) ``` ## Default Template By default, the editor will show a empty document. You may want to use a template to show a document. ```typescript const template = `# Heading` editor.action((ctx) => { const collabService = ctx.get(collabServiceCtx) const doc = new Doc() const wsProvider = new WebsocketProvider('', 'milkdown', doc) collabService.bindDoc(doc).setAwareness(wsProvider.awareness) wsProvider.once('synced', async (isSynced: boolean) => { if (isSynced) { collabService // apply your template .applyTemplate(template) // don't forget connect .connect() } }) }) ``` Keep in mind that applying a template multiple times may cause some unexpected behavior, such as duplicate content. Because of this you need to make sure **the template is applied only once**. By default, the template will only be applied if _document get from remote server is empty_. You can control this behavior through passing second parameter to `applyTemplate`: ```typescript collabService .applyTemplate(template, (remoteNode, templateNode) => { // return true to apply template }) // don't forget connect .connect() ``` Here the nodes we get are [prosemirror nodes](https://prosemirror.net/docs/ref/#model.Node). You should return `true` if the template should be applied, and `false` if not.