Fumadocs Editor

Auth

Decide who may read and write which documents

By default, anyone who can reach the sync server can edit, like Vite's own dev server. Pass an authenticate hook to the server to put the websocket, uploads and assets behind your own policy.

Add an authenticate hook

authenticate receives the request and returns a scope, or null to reject. It runs on every connection and reconnect, so refreshed credentials apply.

Cookies come with every request, so only the server changes:

server.ts
import type { SyncAuthenticate } from "@fumadocs-editor/core/node";

declare function verifySession(
  cookie: string | undefined,
): Promise<{ name: string; isEditor: boolean } | null>;

export const authenticate: SyncAuthenticate = async ({ request }) => {
  const session = await verifySession(request.headers.cookie);
  if (!session) return null;
  return {
    user: { name: session.name },
    write: session.isEditor,
  };
};

Scopes

Prop

Type

read and write can be functions of a document's path, relative to the server root:

  • Paths are normalized first, so team-a/../secret.mdx does not match a team-a/ rule.
  • The functions must be synchronous. Fetch what they need inside authenticate, as the token example does.

Media uploads

Upload requests do not share the websocket, so send the same payload in the AUTH_HEADER header. Cookie setups can skip this.

media.ts
import { AUTH_HEADER, UPLOAD_ENDPOINT } from "@fumadocs-editor/core/sync";
import type { MediaProvider } from "@fumadocs-editor/ui";

declare function getAccessToken(): Promise<string>;

export const media: MediaProvider = {
  async upload(file) {
    const res = await fetch(UPLOAD_ENDPOINT, {
      method: "POST",
      body: file,
      headers: {
        "x-filename": encodeURIComponent(file.name),
        [AUTH_HEADER]: JSON.stringify(await getAccessToken()),
      },
    });
    if (!res.ok) throw new Error(`upload failed: ${res.status}`);
    const { src } = (await res.json()) as { src: string };
    return src;
  },
};

In the editor

The server enforces the scope; the editor never restricts itself. When a user cannot write, see Read-only to show the editor as read-only. Rejected credentials show the denied status.

Last updated on

On this page