Fumadocs Editor

Providers

Where uploads go and which files documents can reference

There is no file system in the browser. The editor does not know where a pasted image should be stored, or which documents exist for <include> and page links. Both are small interfaces your host implements and passes as props:

PropWithout it
mediapaste and drop upload are off; images are added by URL
filesthe include picker and [[ page-link autocomplete are plain text inputs

Everything else works either way.

Media

providers.tsx
export const media: MediaProvider = {
  async upload(file) {
    const res = await fetch("/api/upload", { method: "POST", body: file });
    if (!res.ok) throw new Error(`upload failed: ${res.status}`);
    const { src } = (await res.json()) as { src: string };
    return src;
  },
  // turn document srcs (often relative) into displayable URLs
  resolve: (src) => (src.startsWith("./") ? `/content/${src.slice(2)}` : src),
};
  • upload stores the file and returns the src written into the document. Keep it relative if that is what your site expects.
  • resolve maps a document src to a display URL, so relative paths still preview.

On the dev server

The sync dev server has an upload endpoint, so the provider only needs to call it:

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

// uploads land in <root>/assets on the dev server; relative srcs display
// through the asset endpoint
export const devMedia: MediaProvider = {
  async upload(file) {
    const res = await fetch(UPLOAD_ENDPOINT, {
      method: "POST",
      body: file,
      headers: { "x-filename": encodeURIComponent(file.name) },
    });
    if (!res.ok) throw new Error(`upload failed: ${res.status}`);
    const { src } = (await res.json()) as { src: string };
    return src;
  },
  resolve: (src) =>
    /^(?:[a-z]+:|\/)/i.test(src) ? src : `${ASSET_ENDPOINT}/${src.replace(/^\.\//, "")}`,
};

Uploads land in <root>/assets. The server checks the content type against upload.types (default image/*) and the size against upload.maxBytes (default 10 MiB) before storing anything. With auth enabled, the upload also needs write on the stored path.

Files

providers.tsx
export const files: FileProvider = {
  // the exact relative paths to write into the document
  list: async () => {
    const res = await fetch("/api/pages");
    return (await res.json()) as string[];
  },
};

Return the relative paths exactly as they should be written into the document, relative to the open one (for example ./shared/props.mdx). They power:

  • in-place autocomplete of the <include> path
  • page-link suggestions when you type [[

On the dev server, transport.list() minus the open document is usually enough.

Last updated on

On this page