## 1. Project Overview & Quickstart (ueberdosis/tiptap)

## File: README.md

[](https://insights.linuxfoundation.org/project/tiptap)
[](https://github.com/ueberdosis/tiptap/actions/workflows/build.yml)
[](https://www.npmjs.com/package/@tiptap/core)
[](https://npmcharts.com/compare/@tiptap/core?minimal=true)
[](https://www.npmjs.com/package/@tiptap/core)
[](https://discord.gg/WtJ49jGshW)
[](https://github.com/sponsors/ueberdosis)
[](https://insights.linuxfoundation.org/project/tiptap)

# Tiptap Editor

The Tiptap Editor is a headless, framework-agnostic rich text editor that's customizable and extendable through extensions. Its headless nature means it comes without a set user interface, offering full design freedom (for a jumpstart, see linked [UI templates](#examples-codesandbox-and-ui-templates) below). Tiptap is based on the highly reliable [ProseMirror](https://prosemirror.net/) library.

Tiptap Editor is complemented by the collaboration open-source backend [Hocuspocus](https://github.com/ueberdosis/hocuspocus). Both the Editor and Hocuspocus form the foundation of the [Tiptap Suite](https://tiptap.dev/).

### How does the Tiptap Editor work?

- **Headless Framework:** Tiptap does not rely on a user interface. So there is no need for class overrides or code hacks. If you do need an example UI feel free to browse our [UI templates](#examples-codesandbox-and-ui-templates) linked below.
- **Framework-agnostic:** The Tiptap Editor is designed to work across different frontend frameworks. This means whether you're using Vue, React, or plain JavaScript, Tiptap integrates without compatibility issues.
- **Extension based:** Extensions in Tiptap allow for a tailored editing experience, from simple text styling to advanced features like drag-and-drop block editing. You have the option to choose from over 100 extensions available in the [documentation](https://tiptap.dev/docs/editor/extensions) and [community](https://github.com/ueberdosis/awesome-tiptap/#community-extensions) to enhance your editor's functionality.
- **Customize your UX:** The editor was built to give you control to define your own [extensions](https://tiptap.dev/docs/editor/guide/custom-extensions) and [nodes](https://tiptap.dev/docs/editor/api/nodes).

### Editor Pro Extensions

The **Pro Extensions** are a set of advanced functionalities that enhance the capabilities of the Tiptap Editor. They are additional features that can be integrated into the base editor to provide more sophisticated editing options.

Key functionalities include collaborative editing, commenting, versioning, document conversion and AI related features.
Review the docs right [here](https://tiptap.dev/docs/editor/extensions).

Pro Extensions need a valid subscription.

### Make your editor collaborative

Interested in collaborative editing? Check out our open-source package [Hocuspocus](https://github.com/ueberdosis/hocuspocus) - a collaboration backend built around the CRDT power of [Yjs](https://github.com/yjs/yjs). Hocuspocus serves as the backbone for the [Tiptap Suite](https://tiptap.dev/).

## Documentation

For more detailed information, make sure to check out our [documentation](https://tiptap.dev/docs/editor/installation). If you encounter any problems or have suggestions for our system, please open an issue.

### Examples, CodeSandbox and UI Templates

Have a look at the [examples to see Tiptap in action](https://tiptap.dev/examples) or review and fork our codesandboxes.

- [Basic example of the Tiptap editor.](https://codesandbox.io/p/devbox/editor-9x9dkd?embed=1&file=%2Fsrc%2FApp.js)
- [Collaboration ready Tiptap CodeSandbox](https://codesandbox.io/p/devbox/collaboration-4stk94)
- React notion-like block editor template: [Demo](https://templates.tiptap.dev/)

## About Tiptap

Tiptap is a collection of developer components based on open-source technology, forming the basis of our advanced, paid features. It includes the open-source editor component, collaboration features, Content AI, and Tiptap Cloud. We are developing open-source products that also shape our paid features. We're committed to improving both, ensuring quality and reliability in every update.

For more details, visit the Tiptap [documentation](https://tiptap.dev/docs/editor/introduction) or [website](https://tiptap.dev/).

### Community

For help, discussion about best practices, or any other conversation that would benefit from being searchable:

[Discuss Tiptap on GitHub](https://github.com/ueberdosis/tiptap/discussions)

### Sponsors 💖



| **Complish** | **Storyblok** | **PostHog** | **Reflect** | **Ziff Media** | **Basewell** | **Poggio** |
| --- | --- | --- | --- | --- | --- | --- |



[iFixit](https://www.ifixit.com/), [ApostropheCMS](https://apostrophecms.com/), [Novadiscovery](http://www.novadiscovery.com/), [Omics Data Automation](https://www.omicsautomation.com), [Flow Mobile](https://www.flowmobile.app/), [DocIQ](https://www.dociq.io/) and [hundreds of awesome individuals](https://github.com/sponsors/ueberdosis).

### Contributing

Feel like adding some magic of your own to Tiptap Editor Core? We welcome contributions! Please see our [CONTRIBUTING](CONTRIBUTING.md) guidelines for how to get started.

### Contributors

[Sam Willis](https://github.com/samwillis),
[Brian Hung](https://github.com/BrianHung),
[Dirk Holtwick](https://github.com/holtwick),
[Sam Duvall](https://github.com/SamDuvall),
[Christoph Flathmann](https://github.com/Chrissi2812),
[Erick Wilder](https://github.com/erickwilder),
[Marius Tolzmann](https://github.com/mariux),
[jjangga0214](https://github.com/jjangga0214),
[Maya Nedeljkovich](https://github.com/mayacoda),
[Ryan Bliss](https://github.com/ryanbliss),
[Gregor](https://github.com/gambolputty) and [many more](../../contributors).

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

---

## File: .changeset/README.md

# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

---

## File: packages/vue-3/README.md

# @tiptap/vue-3

[](https://www.npmjs.com/package/@tiptap/vue-3)
[](https://npmcharts.com/compare/tiptap?minimal=true)
[](https://www.npmjs.com/package/@tiptap/vue-3)
[](https://github.com/sponsors/ueberdosis)

## Introduction

Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.

## Official Documentation

Documentation can be found on the [Tiptap website](https://tiptap.dev).

## License

Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

---

## File: packages/vue-2/README.md

# @tiptap/vue-2

[](https://www.npmjs.com/package/@tiptap/vue-2)
[](https://npmcharts.com/compare/tiptap?minimal=true)
[](https://www.npmjs.com/package/@tiptap/vue-2)
[](https://github.com/sponsors/ueberdosis)

## Introduction

Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.

## Official Documentation

Documentation can be found on the [Tiptap website](https://tiptap.dev).

## License

Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

---

## File: packages/suggestion/README.md

# @tiptap/suggestion

[](https://www.npmjs.com/package/@tiptap/suggestion)
[](https://npmcharts.com/compare/tiptap?minimal=true)
[](https://www.npmjs.com/package/@tiptap/suggestion)
[](https://github.com/sponsors/ueberdosis)

## Introduction

Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.

## Official Documentation

Documentation can be found on the [Tiptap website](https://tiptap.dev).

## License

Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

---

## File: packages/static-renderer/README.md

# @tiptap/static-renderer

[](https://www.npmjs.com/package/@tiptap/static-renderer)
[](https://npmcharts.com/compare/tiptap?minimal=true)
[](https://www.npmjs.com/package/@tiptap/static-renderer)
[](https://github.com/sponsors/ueberdosis)

## Introduction

Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.

## Official Documentation

Documentation can be found on the [Tiptap website](https://tiptap.dev).

## Limitations & workarounds

The static renderer builds the ProseMirror schema and runs each extension's
`renderHTML`, but it does **not** instantiate an `Editor`. As a result:

- `addProseMirrorPlugins`, `onCreate`, `onUpdate`, and transaction hooks do
  not run.
- Extensions that assign attributes via those mechanisms — such as
  `UniqueID` (`data-id`) and `TableOfContents` (`id`, `data-toc-id`) — will
  not populate those attributes on their own.

For these cases, pre-process the JSON document before rendering:

```ts
import { generateUniqueIds } from '@tiptap/extension-unique-id'
import { generateTocIds } from '@tiptap/extension-table-of-contents'
import { renderToHTMLString } from '@tiptap/static-renderer/pm/html-string'

let doc = sourceJson
doc = generateUniqueIds(doc, extensions) // if using UniqueID
doc = generateTocIds(doc, extensions) // if using TableOfContents

const html = renderToHTMLString({
  content: doc,
  extensions,
  staticEditorOptions: { textDirection: 'auto' }, // mirrors a subset of EditorOptions
})
```

Editor-level options that affect output are accepted via the
`staticEditorOptions` object (currently `textDirection`). Other editor options
that depend on a runtime view or transaction stream are out of scope.

## License

Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

---

## File: packages/starter-kit/README.md

# @tiptap/starter-kit

[](https://www.npmjs.com/package/@tiptap/starter-kit)
[](https://npmcharts.com/compare/tiptap?minimal=true)
[](https://www.npmjs.com/package/@tiptap/starter-kit)
[](https://github.com/sponsors/ueberdosis)

## Introduction

Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.

## Official Documentation

Documentation can be found on the [Tiptap website](https://tiptap.dev).

## License

Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

---

## File: packages/react/README.md

# @tiptap/react

[](https://www.npmjs.com/package/@tiptap/react)
[](https://npmcharts.com/compare/tiptap?minimal=true)
[](https://www.npmjs.com/package/@tiptap/react)
[](https://github.com/sponsors/ueberdosis)

## Introduction

Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.

## Official Documentation

Documentation can be found on the [Tiptap website](https://tiptap.dev).

## License

Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

---

## File: packages/pm/README.md

# @tiptap/pm

[](https://www.npmjs.com/package/@tiptap/pm)
[](https://npmcharts.com/compare/tiptap?minimal=true)
[](https://www.npmjs.com/package/@tiptap/pm)
[](https://github.com/sponsors/ueberdosis)

## Introduction

Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.

## What is this `pm` package?

The `pm` package re-exports [ProseMirror](https://ProseMirror.net) packages under a single version range. It bundles all ProseMirror packages that are required to run Tiptap into one consistent dependency, so you don't need to install and align multiple ProseMirror packages yourself.

## Official Documentation

Documentation can be found on the [Tiptap website](https://tiptap.dev).

## License

Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

---

## File: packages/markdown/README.md

# @tiptap/markdown

[](https://www.npmjs.com/package/@tiptap/markdown)
[](https://npmcharts.com/compare/tiptap?minimal=true)
[](https://www.npmjs.com/package/@tiptap/markdown)
[](https://github.com/sponsors/ueberdosis)

## Introduction

Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.

## Official Documentation

Documentation can be found on the [Tiptap website](https://tiptap.dev).

## License

Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

## 2. Official Technical Reference & Guides (ueberdosis/tiptap-docs)

## File: README.md

# Tiptap Documentation

Welcome to the official documentation for all Tiptap products! Here, you'll find comprehensive guides, detailed examples, and in-depth documentation covering everything you need to know about our offerings.

## Available Documentation

Our documentation includes resources for all our major products:

- **Editor**: Learn how to integrate, customize, and extend the Tiptap editor.
- **Collaboration**: Find out how to enable real-time collaboration features in your projects.
- **Document AI**: Discover how to leverage AI for document processing and automation.
- **Comments**: Implement and manage a robust commenting system.

Explore the full documentation at [tiptap.dev](https://tiptap.dev/docs).

## Contributing

We greatly appreciate and encourage contributions from the community. If you're interested in contributing to our documentation, please make sure to read and follow our [contributing guidelines](CONTRIBUTING.md). Your efforts help us improve and keep our documentation up to date, ensuring that everyone has access to the best resources possible.

Thank you for being a part of the Tiptap community!

## License

This project is licensed under the [Tiptap Documentation Software License](LICENSE) license. This applies to all files except those in the `src/content` directory, which contains the documentation content or the `public/assets/fonts` directory which contains our fonts. The documentation content is licensed under the [MIT](LICENSE-DOCS) license. The fonts licenses can be found in `src/assets/fonts/LICENSE.txt`.

---

## File: .changeset/README.md

# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

---

## File: src/content/hocuspocus/server/extensions/database.mdx

---
title: Database
meta:
  title: Database | Hocuspocus Docs
  category: Hocuspocus
tags:
  - type: image
    src: https://img.shields.io/npm/v/@hocuspocus/extension-database.svg?label=version
    url: https://www.npmjs.com/package/@hocuspocus/extension-database
    label: Version
  - type: image
    src: https://img.shields.io/npm/dm/@hocuspocus/extension-database.svg
    url: https://npmcharts.com/compare/@hocuspocus/extension-database?minimal=true
    label: Downloads
  - type: image
    src: https://img.shields.io/npm/l/@hocuspocus/extension-database.svg
    url: https://www.npmjs.com/package/@hocuspocus/extension-database
    label: License
  - type: image
    src: https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true
    url: https://discord.gg/WtJ49jGshW
    label: Chat
---

Store your data in whatever data store you already have with the generic database extension.
It takes a Promise to fetch data and another Promise to store the data, that’s all. Hocuspocus will handle the rest.

## Installation

Install the database extension like this:

```bash
npm install @hocuspocus/extension-database
```

## Configuration

**fetch**

Expects an async function (or Promise) which returns a Y.js compatible Uint8Array (or null).
Make sure to return the same Uint8Array that was saved in store(), and do not create a new Ydoc,
as doing so would lead to a new history (and duplicated content).

If you want to initially create a Ydoc based off raw text/json, you can do so here using a transformer of your choice
(e.g. `TiptapTransformer.toYdoc`, or `ProsemirrorTransformer.toYdoc`)

**store**

Expects an async function (or Promise) which persists the Y.js binary data somewhere.

## Usage

The following example uses SQLite to store and retrieve data. You can replace that part with whatever data store you
have. As long as you return a Promise you can store data with PostgreSQL, MySQL, MongoDB, S3 … If you actually want to
use SQLite, you can have a look at the [SQLite extension](/hocuspocus/server/extensions/sqlite).

```js
import { Server } from "@hocuspocus/server";
import { Database } from "@hocuspocus/extension-database";
import sqlite3 from "sqlite3";

const server = new Server({
  extensions: [
    new Database({
      // Return a Promise to retrieve data …
      fetch: async ({ documentName }) => {
        return new Promise((resolve, reject) => {
          this.db?.get(
            `
            SELECT data FROM "documents" WHERE name = $name ORDER BY rowid DESC
          `,
            {
              $name: documentName,
            },
            (error, row) => {
              if (error) {
                reject(error);
              }

              resolve(row?.data);
            }
          );
        });
      },
      // … and a Promise to store data:
      store: async ({ documentName, state }) => {
        this.db?.run(
          `
          INSERT INTO "documents" ("name", "data") VALUES ($name, $data)
            ON CONFLICT(name) DO UPDATE SET data = $data
        `,
          {
            $name: documentName,
            $data: state,
          }
        );
      },
    }),
  ],
});

server.listen();
```

---

## File: src/content/hocuspocus/server/extensions/logger.mdx

---
title: Logger
meta:
  title: Logger | Hocuspocus Docs
  category: Hocuspocus
tags:
  - type: image
    src: https://img.shields.io/npm/v/@hocuspocus/extension-logger.svg?label=version
    url: https://www.npmjs.com/package/@hocuspocus/extension-logger
    label: Version
  - type: image
    src: https://img.shields.io/npm/dm/@hocuspocus/extension-logger.svg
    url: https://npmcharts.com/compare/@hocuspocus/extension-logger?minimal=true
    label: Downloads
  - type: image
    src: https://img.shields.io/npm/l/@hocuspocus/extension-logger.svg
    url: https://www.npmjs.com/package/@hocuspocus/extension-logger
    label: License
  - type: image
    src: https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true
    url: https://discord.gg/WtJ49jGshW
    label: Chat
---

Hocuspocus doesn’t log anything. Thanks to this simple extension it will.

## Installation

Install the Logger package with:

```bash
npm install @hocuspocus/extension-logger
```

## Configuration

**Instance name**

You can prepend all logging messages with a configured string.

```js
import { Server } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";

const server = new Server({
  name: "hocuspocus-fra1-01",
  extensions: [new Logger()],
});

server.listen();
```

**Disable messages**

You can disable logging for specific messages.

```js
import { Server } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";

const server = new Server({
  extensions: [
    new Logger({
      onLoadDocument: false,
      onChange: false,
      onConnect: false,
      onDisconnect: false,
      onUpgrade: false,
      onRequest: false,
      onListen: false,
      onDestroy: false,
      onConfigure: false,
    }),
  ],
});

server.listen();
```

**Custom logger**

You can even pass a custom function to log messages.

```js
import { Server } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";

const server = new Server({
  extensions: [
    new Logger({
      log: (message) => {
        // do something custom here
        console.log(message);
      },
    }),
  ],
});

server.listen();
```

---

## File: src/content/hocuspocus/server/extensions/overview.mdx

---
title: Server Extensions
meta:
  title: Server Extensions | Hocuspocus Docs
  category: Hocuspocus
---

You can see [our guide to custom extensions](/hocuspocus/guides/custom-extensions) to find out how to create your own.

## Table of Contents

We already created some very useful extensions you should check out for sure:

| Extension                                    | Description                                                                    |
|----------------------------------------------|--------------------------------------------------------------------------------|
| [Database](/hocuspocus/server/extensions/database) | A generic database driver that is easily adjustable to work with any database. |
| [Logger](/hocuspocus/server/extensions/logger)     | Add logging to Hocuspocus.                                                     |
| [Redis](/hocuspocus/server/extensions/redis)       | Scale Hocuspocus horizontally with Redis.                                      |
| [SQLite](/hocuspocus/server/extensions/sqlite)     | Persist documents to SQLite.                                                   |
| [Throttle](/hocuspocus/server/extensions/throttle) | Throttle connections by ips.                                                   |
| [Webhook](/hocuspocus/server/extensions/webhook)   | Send document changes via webhook to your API.                                 |
| [S3](/hocuspocus/server/extensions/s3)             | Persist documents to Amazon S3 or any S3-compatible storage service.           |

---

## File: src/content/hocuspocus/server/extensions/redis.mdx

---
title: Redis
meta:
  title: Redis | Hocuspocus Docs
  category: Hocuspocus
tags:
  - type: image
    src: https://img.shields.io/npm/v/@hocuspocus/extension-redis.svg?label=version
    url: https://www.npmjs.com/package/@hocuspocus/extension-redis
    label: Version
  - type: image
    src: https://img.shields.io/npm/dm/@hocuspocus/extension-redis.svg
    url: https://npmcharts.com/compare/@hocuspocus/extension-redis?minimal=true
    label: Downloads
  - type: image
    src: https://img.shields.io/npm/l/@hocuspocus/extension-redis.svg
    url: https://www.npmjs.com/package/@hocuspocus/extension-redis
    label: License
  - type: image
    src: https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true
    url: https://discord.gg/WtJ49jGshW
    label: Chat
---

Hocuspocus can be scaled horizontally using the Redis extension. You can spawn multiple instances of the server behind a
load balancer and sync changes and awareness states through Redis. Hocuspocus will propagate all received updates to all other instances
using Redis and thus forward updates to all clients of all Hocuspocus instances.

The Redis extension does not persist data; it only syncs data between instances. Use the [Database](/hocuspocus/server/extensions/database) extension to store your documents.

Please note that all messages will be handled on all instances of Hocuspocus, so if you are trying to reduce cpu load by spawning multiple
servers, you should not connect them via Redis.

Thanks to [@tommoor](https://github.com/tommoor) for writing the initial implementation of that extension.

## Installation

Install the Redis extension with:

```bash
npm install @hocuspocus/extension-redis
```

## Configuration

For a full documentation on all available Redis and Redis cluster options, check out the
[ioredis API docs](https://github.com/luin/ioredis/blob/master/API.md).

```js
import { Server } from "@hocuspocus/server";
import { Redis } from "@hocuspocus/extension-redis";

const server = new Server({
  extensions: [
    new Redis({
      // [required] Hostname of your Redis instance
      host: "127.0.0.1",

      // [required] Port of your Redis instance
      port: 6379,

      // [optional] Maximum time in milliseconds to wait for the current state
      // from another instance when loading a document. Defaults to 1000.
      awaitInitialSyncTimeout: 1000,
    }),
  ],
});

server.listen();
```

### Consistent state on load

When an instance loads a document that is already open on another instance, the Redis extension waits for that instance to send its current state. This ensures that newly connected clients and direct connections receive changes that may not have been persisted yet. If no other instance has the document open, loading is not delayed.

The `awaitInitialSyncTimeout` option limits how long the extension waits, in milliseconds. It defaults to `1000`. Set it to `0` to disable waiting and use fire-and-forget synchronization instead.

## Usage

The Redis extension works well with the database extension. Once an instance stores a document, it’s blocked for all
other instances to avoid write conflicts.

```js
import { Server } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";
import { Redis } from "@hocuspocus/extension-redis";
import { SQLite } from "@hocuspocus/extension-sqlite";

// Server 1
const server = new Server({
  name: "server-1", // make sure to use unique server names
  port: 1234,
  extensions: [
    new Logger(),
    new Redis({
      host: "127.0.0.1", // make sure to use the same Redis instance :-)
      port: 6379,
    }),
    new SQLite(),
  ],
});

server.listen();

// Server 2
const anotherServer = new Server({
  name: "server-2",
  port: 1235,
  extensions: [
    new Logger(),
    new Redis({
      host: "127.0.0.1",
      port: 6379,
    }),
    new SQLite(),
  ],
});

anotherServer.listen();
```

---

## File: src/content/hocuspocus/server/extensions/s3.mdx

---
title: S3
meta:
  title: S3 | Hocuspocus Docs
  category: Hocuspocus
tags:
  - type: image
    src: https://img.shields.io/npm/v/@hocuspocus/extension-s3.svg?label=version
    url: https://www.npmjs.com/package/@hocuspocus/extension-s3
    label: Version
  - type: image
    src: https://img.shields.io/npm/dm/@hocuspocus/extension-s3.svg
    url: https://npmcharts.com/compare/@hocuspocus/extension-s3?minimal=true
    label: Downloads
  - type: image
    src: https://img.shields.io/npm/l/@hocuspocus/extension-s3.svg
    url: https://www.npmjs.com/package/@hocuspocus/extension-s3
    label: License
  - type: image
    src: https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true
    url: https://discord.gg/WtJ49jGshW
    label: Chat
---

The S3 extension provides S3-compatible storage persistence for Hocuspocus. It supports Amazon S3 and S3-compatible services like MinIO, DigitalOcean Spaces, Wasabi, and more. Documents are stored as binary files in S3, making your collaborative documents persistent and scalable.

## Installation

Install the S3 extension like this:

```bash
npm install @hocuspocus/extension-s3
```

## Configuration

**bucket** (required)

The S3 bucket name where documents will be stored. The bucket must exist and be accessible with your credentials.

**region**

AWS region for the S3 service.

Default: `us-east-1`

**prefix**

Key prefix for document storage. All documents will be stored under this prefix.

Default: `hocuspocus-documents/`

**credentials**

AWS credentials object containing `accessKeyId` and `secretAccessKey`. If not provided, the extension will use the default AWS credential chain (environment variables, IAM roles, etc.).

**endpoint**

Custom S3 endpoint URL for S3-compatible services like MinIO or DigitalOcean Spaces.

**forcePathStyle**

Use path-style URLs instead of virtual hosted-style URLs. Required for MinIO and some S3-compatible services.

Default: `false`

**s3Client**

Custom S3Client instance. If provided, other connection options will be ignored.

## Usage

### Basic AWS S3 Setup

```js
import { Server } from "@hocuspocus/server";
import { S3 } from "@hocuspocus/extension-s3";

const server = new Server({
  extensions: [
    new S3({
      bucket: 'my-documents-bucket',
      region: 'us-east-1',
      credentials: {
        accessKeyId: 'your-access-key',
        secretAccessKey: 'your-secret-key'
      }
    }),
  ],
});

server.listen();
```

### Using Environment Variables

```js
import { Server } from "@hocuspocus/server";
import { S3 } from "@hocuspocus/extension-s3";

// Set environment variables:
// AWS_ACCESS_KEY_ID=your-access-key
// AWS_SECRET_ACCESS_KEY=your-secret-key
// AWS_REGION=us-west-2

const server = new Server({
  extensions: [
    new S3({
      bucket: 'my-documents-bucket',
      // Credentials will be loaded from environment variables
    }),
  ],
});

server.listen();
```

### Using IAM Roles (EC2/Lambda)

```js
import { Server } from "@hocuspocus/server";
import { S3 } from "@hocuspocus/extension-s3";

const server = new Server({
  extensions: [
    new S3({
      bucket: 'my-documents-bucket',
      region: 'us-east-1',
      // No credentials needed when using IAM roles
    }),
  ],
});

server.listen();
```

### MinIO Configuration

For local development with MinIO, the extension automatically enables path-style URLs when an endpoint is provided:

```js
import { Server } from "@hocuspocus/server";
import { S3 } from "@hocuspocus/extension-s3";

const server = new Server({
  extensions: [
    new S3({
      bucket: 'hocuspocus-documents',
      endpoint: 'http://localhost:9000',
      forcePathStyle: true, // Required for MinIO
      credentials: {
        accessKeyId: 'minioadmin',
        secretAccessKey: 'minioadmin'
      }
    }),
  ],
});

server.listen();
```

### DigitalOcean Spaces

```js
import { Server } from "@hocuspocus/server";
import { S3 } from "@hocuspocus/extension-s3";

const server = new Server({
  extensions: [
    new S3({
      bucket: 'my-spaces-bucket',
      region: 'nyc3',
      endpoint: 'https://nyc3.digitaloceanspaces.com',
      credentials: {
        accessKeyId: 'your-spaces-key',
        secretAccessKey: 'your-spaces-secret'
      }
    }),
  ],
});

server.listen();
```

### Custom S3 Client

```js
import { Server } from "@hocuspocus/server";
import { S3 } from "@hocuspocus/extension-s3";
import { S3Client } from "@aws-sdk/client-s3";

const customS3Client = new S3Client({
  region: 'eu-west-1',
  credentials: {
    accessKeyId: 'your-access-key',
    secretAccessKey: 'your-secret-key'
  },
  // Add any custom S3Client configuration here
});

const server = new Server({
  extensions: [
    new S3({
      bucket: 'my-documents-bucket',
      s3Client: customS3Client
    }),
  ],
});

server.listen();
```

## Document Storage

Documents are stored as binary files in S3 with the following naming convention:

- **Key**: `{prefix}{documentName}.bin`
- **Content-Type**: `application/octet-stream`

For example, a document named "my-document" with the default prefix would be stored at:
`hocuspocus-documents/my-document.bin`

## CLI Usage

You can also use the S3 extension with the Hocuspocus CLI:

```bash
# Basic usage
hocuspocus --s3 --s3-bucket my-documents

# With custom region and prefix
hocuspocus --s3 --s3-bucket my-docs --s3-region eu-west-1 --s3-prefix "collab/"

# MinIO setup (forcePathStyle automatically enabled)
hocuspocus --s3 --s3-bucket hocuspocus-documents --s3-endpoint http://localhost:9000

# Using environment variables
export S3_BUCKET=my-documents
export AWS_ACCESS_KEY_ID=your-key
export AWS_SECRET_ACCESS_KEY=your-secret
hocuspocus --s3
```

### CLI Environment Variables

- `AWS_ACCESS_KEY_ID` - AWS access key ID
- `AWS_SECRET_ACCESS_KEY` - AWS secret access key
- `AWS_REGION` - AWS region
- `S3_BUCKET` - S3 bucket name

## IAM Permissions

Your AWS credentials or IAM role needs the following permissions for the specified bucket:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:HeadObject"
      ],
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:HeadBucket"
      ],
      "Resource": "arn:aws:s3:::your-bucket-name"
    }
  ]
}
```

## Scaling with Redis

The S3 extension works seamlessly with the Redis extension for horizontal scaling. Redis handles real-time synchronization between server instances, while S3 provides persistent storage.

### Basic S3 + Redis Setup

```js
import { Server } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";
import { Redis } from "@hocuspocus/extension-redis";
import { S3 } from "@hocuspocus/extension-s3";

// Server 1
const server1 = new Server({
  name: "server-1",
  port: 8001,
  extensions: [
    new Logger(),
    new Redis({
      host: "127.0.0.1",
      port: 6379,
    }),
    new S3({
      bucket: 'my-documents-bucket',
      region: 'us-east-1',
      credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
      }
    }),
  ],
});

server1.listen();

// Server 2
const server2 = new Server({
  name: "server-2",
  port: 8002,
  extensions: [
    new Logger(),
    new Redis({
      host: "127.0.0.1",
      port: 6379,
    }),
    new S3({
      bucket: 'my-documents-bucket',
      region: 'us-east-1',
      credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
      }
    }),
  ],
});

server2.listen();
```

### MinIO + Redis for Local Development

```js
import { Server } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";
import { Redis } from "@hocuspocus/extension-redis";
import { S3 } from "@hocuspocus/extension-s3";

// Development server setup with MinIO and Redis
const createServer = (name, port) => new Server({
  name,
  port,
  extensions: [
    new Logger(),
    new Redis({
      host: "127.0.0.1",
      port: 6379,
    }),
    new S3({
      bucket: 'hocuspocus-documents',
      endpoint: 'http://localhost:9000',
      forcePathStyle: true,
      credentials: {
        accessKeyId: 'minioadmin',
        secretAccessKey: 'minioadmin'
      }
    }),
  ],
});

// Start multiple instances
createServer("dev-server-1", 8001).listen();
createServer("dev-server-2", 8002).listen();
```

### Environment-based Configuration

```js
import { Server } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";
import { Redis } from "@hocuspocus/extension-redis";
import { S3 } from "@hocuspocus/extension-s3";

const server = new Server({
  name: process.env.SERVER_NAME || `server-${Math.random()}`,
  port: Number(process.env.PORT) || 8000,
  extensions: [
    new Logger(),
    new Redis({
      host: process.env.REDIS_HOST || "127.0.0.1",
      port: Number(process.env.REDIS_PORT) || 6379,
    }),
    new S3({
      bucket: process.env.S3_BUCKET,
      region: process.env.S3_REGION || 'us-east-1',
      endpoint: process.env.S3_ENDPOINT, // For MinIO
      forcePathStyle: process.env.S3_ENDPOINT ? true : false,
      prefix: process.env.S3_PREFIX || 'hocuspocus-documents/',
      credentials: process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY ? {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
      } : undefined
    }),
  ],
});

server.listen();
```

## Development Environment Setup

### Quick Start

For local development with MinIO (S3-compatible storage), use the built-in development scripts:

```bash
# Set up complete development environment
npm run dev:setup

# Test S3 configuration
npm run dev:test-s3

# Start S3 playground examples
npm run playground:s3
```

### Available Development Scripts

```bash
# Environment setup
npm run dev:setup         # Complete setup (.env + Docker services)
npm run dev:env           # Create .env file from template
npm run dev:services      # Start Docker services (Redis + MinIO)
npm run dev:services:down # Stop Docker services
npm run dev:services:reset # Reset services and data

# S3 configuration testing
npm run dev:test-s3        # Test complete S3 configuration
npm run dev:test-s3:minio  # Test MinIO connection only
npm run dev:test-s3:nodejs # Test Node.js S3 client only

# Playground examples
npm run playground:s3      # S3 extension examples (ports 8000-8003)
npm run playground:s3-redis # S3 + Redis scaling examples
```

### Local MinIO Setup

The development environment includes:

- **MinIO Server**: http://localhost:9000 (S3-compatible API)
- **MinIO Console**: http://localhost:9001 (Web UI)
- **Redis**: localhost:6379 (for scaling examples)

Default credentials for local development:
- Access Key: `minioadmin`
- Secret Key: `minioadmin`

### Environment Variables

Create a `.env` file (automatically done by `npm run dev:setup`):

```env
# MinIO S3 Configuration (for local development)
S3_ENDPOINT=http://localhost:9000
S3_BUCKET=hocuspocus-documents
S3_REGION=us-east-1
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin

# Production AWS S3 Configuration
# S3_BUCKET=your-production-bucket
# S3_REGION=us-west-2
# AWS_ACCESS_KEY_ID=your-aws-access-key
# AWS_SECRET_ACCESS_KEY=your-aws-secret-key
```

## Best Practices

### Security

- Use IAM roles instead of access keys when running on AWS infrastructure
- Store credentials in environment variables, not in code
- Follow the principle of least privilege for IAM permissions
- Enable S3 bucket encryption for additional security

### Performance

- Use regions closest to your users for better latency
- Consider S3 Transfer Acceleration for global applications
- Monitor S3 request metrics and costs

### Reliability

- Enable S3 versioning to protect against accidental deletions
- Set up appropriate S3 lifecycle policies for cost optimization
- Consider cross-region replication for critical applications

### Scaling Architecture

- **Redis**: Handles real-time synchronization between server instances
- **S3**: Provides persistent storage and acts as the source of truth
- **Load Balancer**: Distributes connections across server instances
- **Multiple Regions**: Deploy in multiple AWS regions for global performance

## Troubleshooting

### Common Issues

**"Bucket not found" or access denied errors:**
- Verify the bucket name and region
- Check IAM permissions
- Ensure credentials are correctly configured

**Connection timeouts with MinIO:**
- Make sure `forcePathStyle: true` is set
- Verify the endpoint URL is accessible
- Check firewall and network settings

**Large document performance:**
- Monitor S3 transfer times
- Consider using S3 multipart uploads for very large documents
- Optimize document structure to reduce size

---

## File: src/content/hocuspocus/server/extensions/sqlite.mdx

---
title: SQLite
meta:
  title: SQLite | Hocuspocus Docs
  category: Hocuspocus
tags:
  - type: image
    src: https://img.shields.io/npm/v/@hocuspocus/extension-sqlite.svg?label=version
    url: https://www.npmjs.com/package/@hocuspocus/extension-sqlite
    label: Version
  - type: image
    src: https://img.shields.io/npm/dm/@hocuspocus/extension-sqlite.svg
    url: https://npmcharts.com/compare/@hocuspocus/extension-sqlite?minimal=true
    label: Downloads
  - type: image
    src: https://img.shields.io/npm/l/@hocuspocus/extension-sqlite.svg
    url: https://www.npmjs.com/package/@hocuspocus/extension-sqlite
    label: License
  - type: image
    src: https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true
    url: https://discord.gg/WtJ49jGshW
    label: Chat
---

## Introduction

For local development purposes it’s nice to have a database ready to go with a few lines of code. That’s what the SQLite
extension is for.

## Installation

Install the SQLite extension like this:

```bash
npm install @hocuspocus/extension-sqlite better-sqlite3
npm install -D @types/better-sqlite3
```

Since v4, the extension uses [`better-sqlite3`](https://github.com/WiseLibs/better-sqlite3) instead of the unmaintained `sqlite3` package. If you're upgrading from v3, uninstall the old dependency with `npm uninstall sqlite3`. Existing SQLite database files are fully compatible — no data migration needed.

## Configuration

**database**

Valid values are filenames, ":memory:" for an anonymous in-memory database and an empty
string for an anonymous disk-based database. Anonymous databases are not persisted and
when closing the database handle, their contents are lost.

https://github.com/mapbox/node-sqlite3/wiki/API#new-sqlite3databasefilename-mode-callback

Default: `:memory:`

**schema**

The SQLite schema that’s created for you.

Default:

```sql
CREATE TABLE IF NOT EXISTS "documents" (
  "name" varchar(255) NOT NULL,
  "data" blob NOT NULL,
  UNIQUE(name)
)
```

If you wrote a **custom schema** under v3, note that v4's `better-sqlite3` uses named parameters without the `$` prefix — rename `$name` to `name` and `$data` to `data` in your queries.

**fetch**

An async function to retrieve data from SQLite. If you change the schema, you probably want to override the query.

**store**

An async function to store data in SQLite. If you change the schema, you probably want to override the query.

**Usage**

By default, data is just “stored” in `:memory:`, so it’s wiped when you stop the server. You can pass a file name to
persist data on the disk.

```js
import { Server } from "@hocuspocus/server";
import { SQLite } from "@hocuspocus/extension-sqlite";

const server = new Server({
  extensions: [
    new SQLite({
      database: "db.sqlite",
    }),
  ],
});

server.listen();
```

## Troubleshooting

### `Could not locate the bindings file`

`better-sqlite3` ships prebuilt native bindings, but they don't always match fresh Node.js versions on day one. If the server throws `Error: Could not locate the bindings file` on start-up, rebuild the binding from source in your server project:

```bash
npm rebuild better-sqlite3 --build-from-source
```

This compiles the native addon for your exact Node.js + OS + architecture combo. Requires a working C++ toolchain (Xcode Command Line Tools on macOS, `build-essential` on Debian/Ubuntu, or the Visual Studio Build Tools on Windows).

---

## File: src/content/hocuspocus/server/extensions/throttle.mdx

---
title: Throttle
meta:
  title: Throttle | Hocuspocus Docs
  category: Hocuspocus
tags:
  - type: image
    src: https://img.shields.io/npm/v/@hocuspocus/extension-throttle.svg?label=version
    url: https://www.npmjs.com/package/@hocuspocus/extension-throttle
    label: Version
  - type: image
    src: https://img.shields.io/npm/dm/@hocuspocus/extension-throttle.svg
    url: https://npmcharts.com/compare/@hocuspocus/extension-throttle?minimal=true
    label: Downloads
  - type: image
    src: https://img.shields.io/npm/l/@hocuspocus/extension-throttle.svg
    url: https://www.npmjs.com/package/@hocuspocus/extension-throttle
    label: License
  - type: image
    src: https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true
    url: https://discord.gg/WtJ49jGshW
    label: Chat
---

This extension throttles connection attempts and bans ip-addresses if it crosses the configured threshold.

Make sure to register it **before** any other extensions!

## Installation

Install the Throttle package with:

```bash
npm install @hocuspocus/extension-throttle
```

## Configuration

```js
import { Server } from "@hocuspocus/server";
import { Throttle } from "@hocuspocus/extension-throttle";

const server = new Server({
  extensions: [
    new Throttle({
      // [optional] allows up to 15 connection attempts per ip address per minute.
      // set to null or false to disable throttling, defaults to 15
      throttle: 15,

      // [optional] bans ip addresses for 5 minutes after reaching the threshold
      // defaults to 5
      banTime: 5,
    }),
  ],
});

server.listen();
```

---

## File: src/content/hocuspocus/server/extensions/webhook.mdx

---
title: Webhooks
meta:
  title: Webhooks | Hocuspocus Docs
  category: Hocuspocus
tags:
  - type: image
    src: https://img.shields.io/npm/v/@hocuspocus/extension-webhook.svg?label=version
    url: https://www.npmjs.com/package/@hocuspocus/extension-webhook
    label: Version
  - type: image
    src: https://img.shields.io/npm/dm/@hocuspocus/extension-webhook.svg
    url: https://npmcharts.com/compare/@hocuspocus/extension-webhook?minimal=true
    label: Downloads
  - type: image
    src: https://img.shields.io/npm/l/@hocuspocus/extension-webhook.svg
    url: https://www.npmjs.com/package/@hocuspocus/extension-webhook
    label: License
  - type: image
    src: https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true
    url: https://discord.gg/WtJ49jGshW
    label: Chat
---
The webhook extension allows you to connect Hocuspocus to your existing application by triggering webhooks on certain
events.

## Installation

Install the Webhook package with:

```bash
npm install @hocuspocus/extension-webhook
```

## Configuration

```js
import { Server } from "@hocuspocus/server";
import { Webhook, Events } from "@hocuspocus/extension-webhook";
import { TiptapTransformer } from "@hocuspocus/transformer";

const server = new Server({
  extensions: [
    new Webhook({
      // [required] url of your application
      url: "https://example.com/api/hocuspocus",

      // [required] a random string that will be used to verify the request signature
      secret: "459824aaffa928e05f5b1caec411ae5f",

      // [required] a transformer for your document
      transformer: TiptapTransformer,

      // [optional] array of events that will trigger a webhook
      // defaults to [ Events.onChange ]
      events: [Events.onConnect, Events.onCreate, Events.onChange, Events.onDisconnect],

      // [optional] time in ms the change event should be debounced,
      // defaults to 2000
      debounce: 2000,

      // [optional] time in ms after that the webhook will be sent
      // regardless of the configured debouncing, defaults to 10000
      debounceMaxWait: 10000,
    }),
  ],
});

server.listen();
```

## How it works

The webhook extension listens on up to four configurable events/hooks that will trigger a POST request to the
configured url.

### onConnect

When a new user connects to the server, the onConnect webhook will be triggered with the following payload:

```json
{
  "event": "connect",
  "payload": {
    "documentName": "example-document",
    "requestHeaders": {
      "Example-Header": "Example"
    },
    "requestParameters": {
      "example": "12345"
    }
  }
}
```

You can respond with a JSON payload that will be set as context throughout the rest of the application. For example:

```js
// authorize the user by the request parameters or headers
if (payload.requestParameters?.get("token") !== "secret-api-token") {
  response.writeHead(403, "unauthorized");
  return response.end();
}

// return context if authorized
response.writeHead(200, { "Content-Type": "application/json" });
response.end(
  JSON.stringify({
    user: {
      id: 1,
      name: "Jane Doe",
    },
  })
);
```

### onCreate

When a new document is created the onCreate webhook will be triggered with the following payload:

```json
{
  "event": "create",
  "payload": {
    "documentName": "example-document"
  }
}
```

You can use this to import a document into Hocuspocus. The webhook extension will first load the document from the
primary storage and only import it if it doesn't already exist in there.

Just respond with all the single documents keyed by their field name. For example:

```js
response.writeHead(200, { "Content-Type": "application/json" });
response.end(
  JSON.stringify({
    // Document for the "secondary" field
    secondary: {},
    // Document for the "default" field
    default: {
      type: "doc",
      content: [
        {
          type: "paragraph",
          content: [
            {
              type: "text",
              text: "What is love?",
            },
          ],
        },
      ],
    },
  })
);
```

### onChange

When a document is changed the onChange webhook will be triggered with the following payload including the context you
set before:

```json
{
  "event": "change",
  "payload": {
    "documentName": "example-document",
    "document": {
      "another-field-name": {},
      "field-name": {
        "type": "doc",
        "content": [
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "What is love?"
              }
            ]
          }
        ]
      }
    },
    "context": {
      "user_id": 1,
      "name": "Jane Doe"
    }
  }
}
```

Because this happens on every keystroke up to multiple times a second, the webhook is debounced by default. You can
configure this (or shut it off entirely) with the `debounce` and `debounceMaxWait` configuration options.

### onDisconnect

When a user disconnects the onDisconnect webhook will be triggered with the following payload:

```json
{
  "event": "disconnect",
  "payload": {
    "documentName": "example-document",
    "context": {
      "user_id": 1,
      "name": "Jane Doe"
    }
  }
}
```

### Transformation

The Y-Doc must be serialized into something readable by your application and when importing a document it must be
converted into a Y-Doc respectively.

Because Hocuspocus doesn't know how your data is structured, you need to pass a transformer to the Webhook extension.
You can use one of the transformers from the `@hocuspocus/transformer` package. Make sure to configure them properly.
In this example we used the TiptapTransformer that needs the list of extensions:

```js
import { Server } from "@hocuspocus/server";
import { Webhook } from "@hocuspocus/extension-webhook";
import { TiptapTransformer } from "@hocuspocus/transformer";
import Document from "@tiptap/extension-document";
import Paragraph from "@tiptap/extension-paragraph";
import Text from "@tiptap/extension-text";

const server = new Server({
  extensions: [
    new Webhook({
      url: "https://example.com/api/webhook",
      secret: "459824aaffa928e05f5b1caec411ae5f",

      transformer: TiptapTransformer.extensions([Document, Paragraph, Text]),
    }),
  ],
});

server.listen();
```

Alternatively you can write your own implementation by simply passing functions that convert a Y-Doc to your
representation and vice versa:

```js
import { Server } from "@hocuspocus/server";
import { Webhook } from "@hocuspocus/extension-webhook";
import { Doc } from "yjs";

const server = new Server({
  extensions: [
    new Webhook({
      url: "https://example.com/api/webhook",
      secret: "459824aaffa928e05f5b1caec411ae5f",

      transformer: {
        toYdoc(document: any, fieldName: string): Doc {
          // convert the given document (from your api) to a ydoc using the provided fieldName
          return new Doc();
        },
        fromYdoc(document: Doc): any {
          // convert the ydoc to your representation
          return document.toJSON();
        },
      },
    }),
  ],
});

server.listen();
```

### Verify Request Signature

On your application server you should verify the signature coming from the webhook extension to secure the route.

The extension sends POST requests, and the signature is stored in the `X-Hocuspocus-Signature-256` header containing a
message authentication code created with sha256.

Here are some examples how you could do that in different languages:

**PHP**

```php
use Symfony\Component\HttpFoundation\Request;

function verifySignature(Request $request) {
  $secret = '459824aaffa928e05f5b1caec411ae5f';

  if (($signature = $request->headers->get('X-Hocuspocus-Signature-256')) == null) {
      throw new Exception('Header not set');
  }

  $parts = explode('=', $signature);

  if (count($parts) != 2) {
      throw new Exception('Invalid signature format');
  }

  $digest = hash_hmac('sha256', $request->getContent(), $secret);

  return hash_equals($digest, $parts[1]);
}

```

**JavaScript**

```js
import { IncomingMessage } from 'http'

const secret = '459824aaffa928e05f5b1caec411ae5f'

const verifySignature = (request: IncomingMessage): boolean => {
  const signature = Buffer.from(request.headers['x-hocuspocus-signature-256'] as string)

  const hmac = createHmac('sha256', secret)
  const digest = Buffer.from(`sha256=${hmac.update(body).digest('hex')}`)

  return signature.length !== digest.length || timingSafeEqual(digest, signature)
}
```