---
title: "Hooks"
canonical_url: "https://tiptap.dev/docs/hocuspocus/server/hooks"
---

# Hooks

## Introduction

Hocuspocus offers hooks to extend its functionality and integrate it into existing applications. Hooks are configured as simple methods the same way as [other configuration options](https://tiptap.dev/docs/hocuspocus/server/configuration.md) are.

Hooks accept a hook payload as first argument. The payload is an object that contains data you can use and manipulate, allowing you to built complex things on top of this simple mechanic, like [extensions](https://tiptap.dev/docs/hocuspocus/guides/custom-extensions.md).

Hooks are required to return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise); the easiest way to do that is to mark the function as `async` (Node.js 22+ is required since v4). In this way, you can do things like executing API requests, running DB queries, trigger webhooks or whatever you need to do to integrate it into your application.

Since v4, hook payloads use web-standard `Request` and `Headers` objects instead of Node.js `IncomingMessage` and `IncomingHttpHeaders`. Read headers with `requestHeaders.get('authorization')` rather than `requestHeaders['authorization']`. The `onUpgrade` and `onRequest` hooks are the exception — they still use Node.js types because they operate at the HTTP level before the WebSocket upgrade.

## Lifecycle

Hooks will be called on different stages of the Hocuspocus lifecycle. For example the `onListen` hook will be called when you call the `listen()` method on the server instance.

Some hooks allow you not only to react to those events but also to intercept them. For example the `onConnect` hook will be fired when a new connection is made to underlying websocket server. By rejecting the Promise in your hook (or throwing an empty exception if using async) you can terminate the connection and stop the chain.

## The hook chain

Extensions use hooks to add additional functionality to Hocuspocus. They will be called one after another in the order of their registration with your configuration as the last part of the chain.

If the Promise in a hook is rejected it will not be called for the following extensions or your configuration. It's like a stack of middlewares a request has to go through. Keep that in mind when working with hooks.

By way of illustration, if a user isn’t allowed to connect: Just throw an error in the `onAuthenticate()` hook. Nice, isn’t it?

## Summary Table

| Hook                       | Description                                 | Link                                                                                       |
| -------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `beforeHandleMessage`      | Before handling a message                   | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#before-handle-message)      |
| `beforeHandleAwareness`    | Before applying an inbound awareness update | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#before-handle-awareness)    |
| `onConnect`                | When a connection is established            | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-connect)                 |
| `connected`                | After a connection has been establied       | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#connected)                  |
| `onAuthenticate`           | When authentication is required             | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-authenticate)            |
| `onAwarenessUpdate`        | When awareness changed                      | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-awareness-update)        |
| `onLoadDocument`           | During the creation of a new document       | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-load-document)           |
| `afterLoadDocument`        | After a document is created                 | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#after-load-document)        |
| `onChange`                 | When a document has changed                 | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-change)                  |
| `onDisconnect`             | When a connection was closed                | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-disconnect)              |
| `onListen`                 | When the server is initialized              | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-listen)                  |
| `onDestroy`                | When the server will be destroyed           | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#ondestroy)                  |
| `onConfigure`              | When the server has been configured         | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-configure)               |
| `onRequest`                | When a HTTP request comes in                | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-request)                 |
| `onStoreDocument`          | When a document has been changed            | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-store-document)          |
| `onUpgrade`                | When the WebSocket connection is upgraded   | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-upgrade)                 |
| `onStateless`              | When the Stateless message is received      | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#on-stateless)               |
| `beforeBroadcastStateless` | Before broadcast a stateless message        | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#before-broadcast-stateless) |
| `afterUnloadDocument`      | When a document is closed                   | [Read more](https://tiptap.dev/docs/hocuspocus/server/hooks.md#after-unload-document)      |

## Usage

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async onAuthenticate({ documentName, token }) {
    // Could be an API call, DB query or whatever …
    // The endpoint should return 200 OK in case the user is authenticated, and an http error
    // in case the user is not.
    return axios.get("/user", {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
  },
});

server.listen();
```

## Hooks

### beforeHandleMessage

The `beforeHandleMessage` hooks are called when a message was received by the server, directly before
handling / applying it. The hook can be used to reject a message (e.g. if the authentication token has
expired), or even to check the update message and reject / accept it based on custom rules. If you
throw an error in the hook, the connection will be closed. You can return a custom code / reason by
throwing an error that implements CloseEvent (see example below).

**Hook payload**

The `data` passed to the `beforeHandleMessage` hook has the following attributes:

```js
import { URLSearchParams } from "url";
import { Doc } from "yjs";
import { CloseEvent } from "@hocuspocus/common";

const data = {
  clientsCount: number,
  context: any,
  document: Doc,
  documentName: string,
  instance: Hocuspocus,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  update: Uint8Array,
  socketId: string,
};
```

Context contains the data provided in former `onConnect` hooks.

**Example**

```js
import { debounce } from "debounce";
import { Server } from "@hocuspocus/server";
import { TiptapTransformer } from "@hocuspocus/transformer";
import { writeFile } from "fs";

let debounced;

const server = new Server({
  beforeHandleMessage(data) {
    if (data.context.tokenExpiresAt <= new Date()) {
      const error: CloseEvent = {
        reason: "Token expired",
      };

      throw error;
    }
  },
});

server.listen();
```

### connected

The `connected` hooks are called after a new connection has been successfully established.

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async connected() {
    console.log("connections:", server.getConnectionsCount());
  },
});

server.listen();
```

### onAuthenticate

The `onAuthenticate` hook will be called when the server receives an authentication request from the client provider.
It should return a Promise. Throwing an exception or rejecting the Promise will terminate the connection. Note, if you do not
specify a token, the hook is called with an empty string.

**Hook payload**

The `data` passed to the `onAuthenticate` hook has the following attributes:

```js
import { URLSearchParams } from "url";
import { Doc } from "yjs";

const data = {
  documentName: string,
  instance: Hocuspocus,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  request: Request,
  socketId: string,
  token: string,
  providerVersion: string | undefined,
  connection: {
    readOnly: boolean,
  },
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async onAuthenticate(data) {
    const { token } = data;

    // Example test if a user is authenticated using a
    // request parameter
    if (token !== "super-secret-token") {
      throw new Error("Not authorized!");
    }

    // Example to set a document to read only for the current user
    // thus changes will not be accepted and synced to other clients
    if (someCondition === true) {
      data.connection.readOnly = true;
    }

    // You can set contextual data to use it in other hooks
    return {
      user: {
        id: 1234,
        name: "John",
      },
    };
  },
});

server.listen();
```

### onTokenSync

The `onTokenSync` hook is called when the server receives a token synchronization request from a connected provider. This enables the server to validate user tokens during active sessions without requiring a full reconnection.

**Hook payload**

The `data` passed to the `onTokenSync` hook has the following attributes:

```js
const data = {
  context: any,
  document: Doc,
  documentName: string,
  instance: Hocuspocus,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  socketId: string,
  token: string,
  connectionConfig: {
    readOnly: boolean,
  },
  connection: Connection,
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async onTokenSync({ token, context, connection }) {
    // Validate the current token
    const isValid = await validateToken(token);

    if (!isValid) {
      throw new Error("Token has expired or is invalid");
    }

    // Update permissions if changed
    const permissions = await getUserPermissions(context.userId);
    if (permissions.readOnly !== connection.readOnly) {
      connection.readOnly = permissions.readOnly;
    }

    return { lastTokenSync: new Date() };
  },
});

server.listen();
```

**Provider Usage**

```js
// Provider sends token to server
provider.sendToken();
```

**Server Usage**

```js
// Server requests token from provider
const connection = document.connections.values().next().value?.connection;
connection.requestToken();
```

### beforeHandleAwareness

The `beforeHandleAwareness` hooks are called after the server has decoded an inbound awareness message and before it is applied to the document's awareness state. Unlike `onAwarenessUpdate`, which observes awareness changes after they have been applied, `beforeHandleAwareness` lets you inspect and rewrite the update before it lands in the document's awareness or is broadcast to peers.

The hook receives the decoded per-client `states` as a mutable `Map` keyed by Yjs `clientId`. Mutate the map and the contained state objects in place to:

- Change fields on a state object (e.g. stamp a verified `user` over a client-supplied value).
- `states.delete(clientId)` to drop a peer from the broadcast.
- `states.set(clientId, ...)` to add or replace a synthetic state.

The encoded update sent to peers reflects whatever the map looks like after every hook in the chain has run. Throw to reject the entire update — nothing is applied and nothing is broadcast.

Multiple extensions chain naturally: each extension sees the map as mutated by the previous ones and can mutate it further. Extensions run before the configuration-level hook.

**Hook payload**

The `data` passed to the `beforeHandleAwareness` hook has the following attributes:

```js
import { Awareness } from 'y-protocols/awareness'
import type { Connection, TransactionOrigin } from '@hocuspocus/server'

const data = {
  awareness: Awareness,
  clientsCount: number,
  // Connection context populated by `onAuthenticate`. `undefined` when the
  // update did not originate from a client connection (e.g. server-internal
  // writes via DirectConnection).
  context: any | undefined,
  document: Document,
  documentName: string,
  instance: Hocuspocus,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  socketId: string,
  // Per-client awareness states decoded from the inbound update, keyed by
  // Yjs clientId. Mutate this map in place to rewrite the update.
  states: Map<number, Record<string, any>>,
  // The TransactionOrigin that will be passed to applyAwarenessUpdate.
  // Use isTransactionOrigin() to discriminate sources.
  transactionOrigin: TransactionOrigin,
  // Convenience shortcut: `origin.connection` when transactionOrigin is a
  // ConnectionTransactionOrigin, otherwise undefined.
  connection?: Connection,
}
```

**Example: stamp a server-verified field over the client value**

```js
import { Server } from '@hocuspocus/server'

const server = new Server({
  async beforeHandleAwareness({ states, context }) {
    for (const state of states.values()) {
      // Overwrite whatever the client claimed with what the server knows.
      state.user = context?.user ?? null
    }
  },
})

server.listen()
```

**Example: reject the update entirely**

```js
import { Server } from '@hocuspocus/server'

const server = new Server({
  async beforeHandleAwareness({ context }) {
    if (context?.readOnly) {
      throw new Error('Awareness updates not allowed for read-only sessions')
    }
  },
})

server.listen()
```

### onAwarenessUpdate

The `onAwarenessUpdate` hooks are called when awareness changed ([Provider Awareness API](https://tiptap.dev/docs/hocuspocus/provider/events.md)).

**Hook payload**

The `data` passed to the `onAwarenessUpdate` hook has the following attributes:

```js
import { Awareness } from 'y-protocols/awareness'
import type { Connection, TransactionOrigin } from '@hocuspocus/server'

const data = {
  document: Document,
  documentName: string,
  instance: Hocuspocus,
  update: Uint8Array,
  added: number[],
  updated: number[],
  removed: number[],
  awareness: Awareness,
  states: { clientId: number, [key: string | number]: any }[],
  // Structured origin of the awareness update (since v4)
  transactionOrigin: TransactionOrigin,
  // Optional: the connection that triggered this update (since v4)
  connection?: Connection,
}
```

Since v4 the payload is simplified. Connection-specific fields (`context`, `requestHeaders`, `requestParameters`, `socketId`) have been removed. Access them through the optional `connection` (e.g. `connection?.context`).

**Example**

```js
const provider = new HocuspocusProvider({
  url: "ws://127.0.0.1:1234",
  name: "example-document",
  document: ydoc,
  onAwarenessUpdate: ({ states }) => {
    currentStates = states;
  },
});
```

### onChange

The `onChange` hooks are called when the document itself has changed. It should return a Promise.

It's important to understand that this hook is called just once per document. You can use it to react to changes
by a specific connection, because we're passing `context` and `update` in the payload (see below).

It's highly recommended to debounce extensive operations as this hook can be fired up to multiple times a second.

**Hook payload**

The `data` passed to the `onChange` hook has the following attributes:

```js
import { URLSearchParams } from "url";
import { Doc } from "yjs";
import type { TransactionOrigin } from "@hocuspocus/server";

const data = {
  clientsCount: number,
  context: any,
  document: Doc,
  documentName: string,
  instance: Hocuspocus,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  update: Uint8Array,
  socketId: string,
  // Structured origin (since v4) — use isTransactionOrigin() and switch on .source
  transactionOrigin: TransactionOrigin,
};
```

Context contains the data provided in former `onConnect` hooks.

**Example**

:::warning Use a primary storage
The following example is not intended to be your primary storage as serializing to and deserializing from JSON will not store collaboration history steps but only the resulting document. This example is only meant to store the resulting document for the views of your application. For a primary storage, check out the [Database extension](https://tiptap.dev/docs/hocuspocus/server/extensions/database.md).
:::

```js
import { debounce } from "debounce";
import { Server } from "@hocuspocus/server";
import { TiptapTransformer } from "@hocuspocus/transformer";
import { writeFile } from "fs";

let debounced;

const server = new Server({
  async onChange(data) {
    const save = () => {
      // Convert the y-doc to something you can actually use in your views.
      // In this example we use the TiptapTransformer to get JSON from the given
      // ydoc.
      const prosemirrorJSON = TiptapTransformer.fromYdoc(data.document);

      // Save your document. In a real-world app this could be a database query
      // a webhook or something else
      writeFile(`/path/to/your/documents/${data.documentName}.json`, prosemirrorJSON);

      // Maybe you want to store the user who changed the document?
      // Guess what, you have access to your custom context from the
      // onConnect hook here. See authorization & authentication for more
      // details
      console.log(`Document ${data.documentName} changed by ${data.context.user.name}`);
    };

    debounced?.clear();
    debounced = debounce(save, 4000);
    debounced();
  },
});

server.listen();
```

### onConfigure

The `onConfigure` hooks are called after the server was created. It should return a Promise.

**Default configuration**

If `configure()` is never called, you can get the default configuration by importing it:

```js
import { defaultConfiguration } from "@hocuspocus/server";
```

**Hook payload**

The `data` passed to the `onConfigure` hook has the following attributes:

```js
import { Configuration } from "@hocuspocus/server";

const data = {
  configuration: Configuration,
  version: string,
  instance: Hocuspocus,
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async onConfigure(data) {
    // Output some information
    console.log(`Server was configured!`);
  },
});

server.listen();
```

### onConnect

The `onConnect` hook will be called when a new connection is established. It should return a Promise. Throwing an exception or rejecting the Promise will terminate the connection.

**Hook payload**

The `data` passed to the `onConnect` hook has the following attributes:

```js
import { URLSearchParams } from "url";
import { Doc } from "yjs";

const data = {
  documentName: string,
  instance: Hocuspocus,
  request: Request,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  socketId: string,
  providerVersion: string | undefined,
  connection: {
    readOnly: boolean,
  },
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async onConnect(data) {
    // Output some information
    console.log(`New websocket connection`);
  },
});

server.listen();
```

### onDestroy

The `onDestroy` hooks are called after the server was shut down using the [destroy](https://tiptap.dev/docs/hocuspocus/server/usage.md#server) method. It should return a Promise.

**Hook payload**

The `data` passed to the `onDestroy` hook has the following attributes:

```js
const data = {
  instance: Hocuspocus,
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async onDestroy(data) {
    // Output some information
    console.log(`Server was shut down!`);
  },
});

server.listen();
```

### onDisconnect

The `onDisconnect` hooks are called when a connection is terminated. It should return a Promise.

**Hook payload**

The `data` passed to the `onDisconnect` hook has the following attributes:

```js
import { URLSearchParams } from "url";
import { Doc } from "yjs";

const data = {
  clientsCount: number,
  context: any,
  document: Document,
  documentName: string,
  instance: Hocuspocus,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  socketId: string,
};
```

Context contains the data provided in former `onConnect` hooks.

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async onDisconnect(data) {
    // Output some information
    console.log(`"${data.context.user.name}" has disconnected.`);
  },
});

server.listen();
```

### onListen

The `onListen` hooks are called after the server is started and accepts connections. It should return a Promise.

**Hook payload**

The `data` passed to the `onListen` hook has the following attributes:

```js
const data = {
  port: number,
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async onListen(data) {
    // Output some information
    console.log(`Server is listening on port "${data.port}"!`);
  },
});

server.listen();
```

### onLoadDocument

The `onLoadDocument` hooks are called to fetch existing data from your storage. You are probably used to loading some JSON/HTML document in your application, but that’s not the Y.js-way. For Y.js to work properly, we’ll need to store the history of changes. Only then changes from multiple sources can be merged.

You still can store a JSON/HTML document, but see it more as a “view” on your data, not as your data source.

**Create a Y.js document from JSON/HTML (once)**

You can create a Y.js document from your existing data, for example JSON. You should use this to migrate data only, not as a permanent way to store your data.

To do this, you can use the Transformer package. For Tiptap-compatible JSON it would look like this:

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

const ydoc = TiptapTransformer.toYdoc(
  // the actual JSON
  json,
  // the `field` you’re using in Tiptap. If you don’t know what that is, use 'default'.
  "default",
  // The Tiptap extensions you’re using. Those are important to create a valid schema.
  [Document, Paragraph, Text]
);
```

If you want to import HTML, you have to [convert it to Tiptap-compatible JSON first](https://tiptap.dev/api/utilities/html/#generate-json-from-html)

However, we expect you to return a Y.js document (or a raw `Uint8Array` Yjs update, since v4) from the `onLoadDocument` hook, no matter where it’s from.

```js
import { Server } from '@hocuspocus/server'

const server = new Server({
  async onLoadDocument(data) {
    // fetch the Y.js document from somewhere
    const ydoc = …

    return ydoc
  },
})

server.listen()
```

Since v4 you can also return a raw `Uint8Array` of Yjs updates directly, which is convenient for storage extensions:

```js
const server = new Server({
  async onLoadDocument({ documentName }) {
    const update = await loadUpdateFromStorage(documentName)
    return update // Uint8Array
  },
})
```

**Fetch your Y.js documents (recommended)**

There are multiple ways to store your Y.js documents (and their history) wherever you like. Basically, you should use the `onStoreDocument` hook, which is debounced and executed every few seconds for changed documents. It gives you the current Y.js document, and it’s up to you to store that somewhere. No worries, we provide some convenient ways for you.

If you just want to get it working, have a look at the [`SQLite`](https://tiptap.dev/docs/hocuspocus/server/extensions/sqlite.md) extension for local development, and the generic [`Database`](https://tiptap.dev/docs/hocuspocus/server/extensions/database.md) extension for a convenient way to fetch and store documents.

**Hook payload**

The `data` passed to the `onLoadDocument` hook has the following attributes:

```js
import { Doc } from "yjs";

const data = {
  context: any,
  document: Doc,
  documentName: string,
  instance: Hocuspocus,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  socketId: string,
};
```

Context contains the data provided in former `onConnect` hooks.

### afterLoadDocument

The `afterLoadDocument` hooks are called after a document is successfully loaded. This is different
to the `onLoadDocument` hooks which are part of the document creation process and could potentially
fail if for instance the document cannot be found in the database.

Because `afterLoadDocument` only runs after all `onLoadDocument` hooks are successful at this point
you know the document is considered open on the server.

**Hook payload**

The `data` passed to the `afterLoadDocument` hook has the following attributes:

```js
import { Doc } from "yjs";

const data = {
  context: any,
  document: Doc,
  documentName: string,
  instance: Hocuspocus,
  requestHeaders: Headers,
  requestParameters: URLSearchParams,
  socketId: string,
};
```

### onRequest

The `onRequest` hooks are called when the HTTP server inside Hocuspocus receives a new request. It should return a Promise. If you throw an empty exception or reject the returned Promise the following hooks in the chain will not run and thus enable you to respond to the request yourself. It's similar to the concept of request middlewares.

This is useful if you want to create custom routes on the same port Hocuspocus runs on.

**Hook payload**

The `data` passed to the `onRequest` hook has the following attributes:

```js
import { IncomingMessage, ServerResponse } from "http";

const data = {
  request: IncomingMessage,
  response: ServerResponse,
  instance: Hocuspocus,
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  onRequest(data) {
    return new Promise((resolve, reject) => {
      const { request, response } = data;

      // Check if the request hits your custom route
      if (request.url?.split("/")[1] === "custom-route") {
        // Respond with your custom content
        response.writeHead(200, { "Content-Type": "text/plain" });
        response.end("This is my custom response, yay!");

        // Rejecting the promise will stop the chain and no further
        // onRequest hooks are run
        return reject();
      }

      resolve();
    });
  },
});

server.listen();
```

### onStoreDocument

The `onStoreDocument` hooks are called after the document has been changed (after the onChange hook) and can
be used to store the changed document to a persistent storage. Calls to `onStoreDocument` are debounced by default
(see `debounce` and `maxDebounce` configuration options).

The easiest way to implement this functionality is by extending the extension `extension-database` and implementing
fetch() and store() methods, as we did that in `extension-sqlite`. You can implement the `onStoreDocument` yourself
with the hook directly, just make sure to apply / encode the states of the yDoc as we did in `extension-database`.

Since v4, `onStoreDocument` is triggered on any document change — not just WebSocket-originated ones. The payload has been restructured because the hook can now be triggered by sources that are not tied to a specific connection (e.g. direct connections, Redis replication). If the hook throws, the document stays in memory and is retried to avoid data loss, and `Server.destroy()` flushes any pending debounced stores on shutdown.

If you want to opt out of store hooks for a specific local mutation, set `skipStoreHooks` on the `LocalTransactionOrigin` when opening a direct connection. Extensions can also throw `SkipFurtherHooksError` (from `@hocuspocus/common`) to signal that persistence is handled and remaining hooks should be skipped.

**Hook payload**

The `data` passed to the `onStoreDocument` hook has the following attributes:

```js
import { Doc } from "yjs";
import type { TransactionOrigin } from "@hocuspocus/server";

const data = {
  clientsCount: number,
  document: Doc,
  documentName: string,
  instance: Hocuspocus,
  // Context of the last connection that triggered the hook (was `context` in v3)
  lastContext: any,
  // Structured origin of the last transaction (was `transactionOrigin` in v3)
  lastTransactionOrigin: TransactionOrigin,
};
```

In v3 this payload also contained `context`, `requestHeaders`, `requestParameters`, `socketId`, and `transactionOrigin`. In v4 those were removed or renamed — access connection-specific data through `lastContext` if needed.

### onUpgrade

The `onUpgrade` hooks are called when the HTTP server inside Hocuspocus receives a new upgrade request. It should return a Promise. If you throw an empty exception or reject the returned Promise the following hooks in the chain will not run and thus enable you to respond and upgrade the request yourself. It's similar to the concept of request middlewares.

This is useful if you want to create custom websocket routes on the same port Hocuspocus runs on.

**Hook payload**

The `data` passed to the `onUpgrade` hook has the following attributes:

```js
import { IncomingMessage } from "http";
import { Socket } from "net";

const data = {
  head: any,
  request: IncomingMessage,
  socket: Socket,
  instance: Hocuspocus,
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";
import WebSocket, { WebSocketServer } from "ws";

const server = new Server({
  onUpgrade(data) {
    return new Promise((resolve, reject) => {
      const { request, socket, head } = data;

      // Check if the request hits your custom route
      if (request.url?.split("/")[1] === "custom-route") {
        // Create your own websocket server to upgrade the request, make
        // sure noServer is set to true, because we're handling the upgrade
        // ourselves
        const websocketServer = new WebSocketServer({ noServer: true });
        websocketServer.on("connection", (connection: WebSocket, request: IncomingMessage) => {
          // Put your application logic here to respond to new connections
          // and subscribe to incoming messages
          console.log("A new connection to our websocket server!");
        });

        // Handle the upgrade request within your own websocket server
        websocketServer.handleUpgrade(request, socket, head, (ws) => {
          websocketServer.emit("connection", ws, request);
        });

        // Rejecting the promise will stop the chain and no further
        // onUpgrade hooks are run
        return reject();
      }

      resolve();
    });
  },
});

server.listen();
```

### onStateless

The `onStateless` hooks are called after the server has received a stateless message. It should return a Promise.

**Hook payload**

The `data` passed to the `onListen` hook has the following attributes:

```js
const data = {
  connection: Connection,
  documentName: string,
  document: Document,
  payload: string,
}
```

**Example**

```js
import { Server } from '@hocuspocus/server'

const server = new Server({
  async onStateless({ payload, document, connection }) {
    // Output some information
    console.log(`Server has received a stateless message "${payload}"!`)
    // Broadcast a stateless message to all connections based on document
    document.broadcastStateless('This is a broadcast message.')
    // Send a stateless message to a specific connection
    connection.sendStateless('This is a specific message.')
  },
})

server.listen()
```

### beforeSync

The `beforeSync` hooks are called before a sync message is handled. This is useful if you want to inspect the sync message that will be applied to the document.
Note that this hook is NOT async.

**Hook payload**

```js
const data = {
  documentName: string,
  document: Document,
  // The y-protocols/sync message type
  type: number,
  // The payload of the y-protocols/sync message
  payload: Uint8Array,
}
```

**Example**

```js
import { Server } from '@hocuspocus/server'

const server = new Server({
  beforeSync({ payload, document, documentName, type }) {
    console.log(`Server will handle a sync message: "${payload}"!`)
  },
})

server.listen()
```

### beforeBroadcastStateless

The `beforeBroadcastStateless` hooks are called before the server broadcast a stateless message.

**Hook payload**

The `data` passed to the `beforeBroadcastStateless` hook has the following attributes:

```js
import { Doc } from 'yjs'

const data = {
  documentName: string,
  document: Doc,
  payload: string,
}
```

**Example**

```js
import { Server } from '@hocuspocus/server'

const server = new Server({
  beforeBroadcastStateless({ payload }) {
    console.log(`Server will broadcast a stateless message: "${payload}"!`)
  },
})

server.listen()
```

### afterUnloadDocument

The `afterUnloadDocument` hooks are called after a document was closed on the server. You can no
longer access the document at this point as it has been destroyed, but you may notify anything
that was subscribed to the document.

**Hook payload**

The `data` passed to the `onDestroy` hook has the following attributes:

```js
const data = {
  instance: Hocuspocus,
  documentName: string,
};
```

**Example**

```js
import { Server } from "@hocuspocus/server";

const server = new Server({
  async afterUnloadDocument(data) {
    // Output some information
    console.log(`Document ${data.documentName} was closed`);
  },
});

server.listen();
```
