Writing Plugins
Add routes, UI and other features with your own plugin.
A plugin is an object with hooks. This one adds a link to the navbar:
createPress(content).plugins({
name: "site:blog-link",
layoutProps({ lang }) {
return {
links: [{ url: this.localizePath(lang, "/blog"), text: "Blog" }],
};
},
});layoutProps is a hook, and Fumapress calls it for each page it renders. this in a hook is the press context, the same object getPressContext() returns on custom pages.
Hooks
Routes
Add pages and API routes.
UI
Add buttons, navbar links and head tags, or render a source your way.
Middleware and Lifecycle
Handle requests, or run code at startup and after the build.
Custom Source
Render pages from anywhere, with content hooks like getPageBody.
Sharing Plugins
In these samples, AppShape and Plugin come from @fumapress/astro/config.
Reusable Plugins
To use a plugin on more than one site, export a function that returns it. The type parameter C gives the hooks the types of the site that uses the plugin. This plugin adds a page, an API route and a response header:
type Types = {
views: { "status.page": { ok: boolean } };
};
export function statusPlugin<C extends AppShape = AppShape>(): Plugin<C, Types> {
return {
name: "acme:status",
routes({ createApi, createView }) {
createView({
path: "/status",
render: "dynamic",
view: () => ({
type: "status.page",
props: { ok: true },
node: <p>OK</p>,
}),
});
createApi({ path: "/status.json", GET: () => Response.json({ ok: true }) });
},
async middleware(request, next) {
const response = await next();
const out = new Response(response.body, response);
out.headers.set("x-powered-by", "acme");
return out;
},
};
}Plugin Types
The second type parameter of Plugin lists what your plugin adds, so that ui.renderPage and other plugins have its types. Use only the keys you need:
type AcmeTypes = {
// plugin pages: `props.type === "showcase.index"`, with `props.items`
views: { "showcase.index": { items: ShowcaseItem[] } };
// content pages it renders: `props.type === "reviews"`, with `props.stars`
pages: { reviews: { stars: number } };
// the names of its slot items: `props.slots["acme:share"]`
slots: "acme:share" | "acme:rating";
};Publishing a Package
To support every framework Fumapress runs on, build on the plugin API of Fumadocs, and make the plugin the default export of a /press entry. That API has the content hooks and createApi(), with no UI hooks.
import type { Plugin } from "fumadocs-core/framework/plugin";
export default function acmePlugin(): Plugin {
return {
name: "acme",
routes({ createApi }) {
createApi({ path: "/api/acme", GET: () => Response.json({ ok: true }) });
},
};
}fumadocs-openapi/press is a complete example.
Sharing Data
To give custom pages and other plugins data of your plugin, export a function that takes the press context. Store the data by context, since one process can run more than one site:
const statuses = new WeakMap<Context, Promise<Status>>();
export function statusPlugin<C extends AppShape = AppShape>(): Plugin<C> {
return {
name: "acme:status",
configure() {
statuses.set(this, fetchStatus());
},
};
}
export function getStatus(ctx: Context): Promise<Status> | undefined {
return statuses.get(ctx);
}getBlogPosts() of @fumapress/blog works this way.
Custom Search Engine
A search engine is a plugin that returns its dialog from providerProps, and adds its own routes. A site using it sets search: false in recommendedPreset(). See Custom Search Dialog for the parts.
import { AcmeSearchDialog } from "./client";
export function acmeSearchPlugin<C extends AppShape = AppShape>(options: {
index: string;
}): Plugin<C> {
return {
name: "acme:search",
providerProps({ lang }) {
const api = this.withBase("/api/acme-search");
const options = { api, locale: lang };
return { search: { SearchDialog: AcmeSearchDialog, options } };
},
routes({ createApi }) {
createApi({
path: "/api/acme-search",
GET: (request) => {
const { search } = new URL(request.url);
return fetch(`https://search.acme.dev/${options.index}${search}`);
},
});
},
};
}Last updated on