Zudoku is highly extensible. You can create custom plugins to add new functionality to your
documentation site. This guide will show you how to create and use plugins in your Zudoku
configuration.
Plugin Types
All plugins in Zudoku must implement the ZudokuPlugin type, which is a union of these plugin
interfaces:
CommonPlugin: Basic plugin with initialization, head elements, MDX component customization,
and a React provider that wraps the app
ProfileMenuPlugin: Add custom items to the profile menu
NavigationPlugin: Define custom routes and sidebar items
ApiIdentityPlugin: Provide API identities for testing
Below is a sample of adding the necessary scripts for GTM, but this could apply to any tag manager
or tracking script.
Code
import { ZudokuPlugin } from "zudoku";const commonPlugin: ZudokuPlugin = { getHead: () => { return ( <script> {` (function (w, d, s, l, i) { w[l] = w[l] || []; w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" }); var f = d.getElementsByTagName(s)[0], j = d.createElement(s), dl = l != "dataLayer" ? "&l=" + l : ""; j.async = true; j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl; f.parentNode.insertBefore(j, f); })(window, document, "script", "dataLayer", "GTM-<YOUR GTM ID HERE>");`} </script> ); },};
Tracking page_view Events
Zudoku is a single page application so typical page_view events are not captured by most analytics
scripts or tag managers. Instead, you must listen to the locationevent
with a plugin and log navigation changes in code.
If you are using TypeScript, you will also need to add the following type declaration to the file
this plugin is declared
Code
declare global { interface Window { dataLayer: Record<string, any>[]; }}
React Providers
Some libraries are set up by wrapping your app in a React context provider. Use the Provider
option to wrap the whole Zudoku app with one. For example, to use
posthog-js instead of adding a snippet with getHead:
Code
import { PostHogProvider } from "posthog-js/react";import type { ZudokuPlugin } from "zudoku";const posthogPlugin: ZudokuPlugin = { Provider: ({ children }) => ( <PostHogProvider apiKey={import.meta.env.ZUDOKU_PUBLIC_POSTHOG_KEY} options={{ api_host: "https://us.i.posthog.com" }} > {children} </PostHogProvider> ),};
Provider is a regular React component:
It renders inside Zudoku's own providers, so it can use hooks like useZudoku or useAuth from
zudoku/hooks. It mounts once all plugins have finished initialize.
It stays mounted when the user navigates between pages.
If several plugins define a Provider, they are nested in the order of the plugins array, so
the first plugin's provider is the outermost one.
It also renders during prerendering and server-side rendering. Keep browser-only code, like
accessing window, inside effects. Keep per-user data in component state instead of module scope,
where the server would share it between requests.
You can wrap your plugin's routes with a context provider or custom layout using React Router's
nested route pattern. The parent route renders an <Outlet /> where child routes will appear.
import { ZudokuPlugin } from "zudoku";const eventConsumerPlugin: ZudokuPlugin = { events: { location: ({ from, to }) => { if (!from) { console.log(`Initial navigation to: ${to.pathname}`); } else { console.log(`Navigation from ${from.pathname} to ${to.pathname}`); } }, },};
Transform Config Plugin
The transformConfig hook allows plugins to modify the Zudoku configuration at build-time. This is
useful for dynamically adding navigation items, modifying theme settings, or adjusting any other
configuration based on external data or conditions.
Code
import { ZudokuPlugin } from "zudoku";const transformConfigPlugin: ZudokuPlugin = { transformConfig: ({ config, merge }) => { // Option 1: Use merge helper for deep merging return merge({ slots: { "head-navigation-start": () => <a href="/pricing">Pricing</a>, }, }); // Option 2: Manual spread for full control return { ...config, navigation: [ ...(config.navigation ?? []), { type: "link", label: "System Status", to: "https://status.example.com", icon: "activity", }, ], }; },};
The transformConfig function receives an object with:
config: The current Zudoku configuration object
merge: A helper function that deep merges a partial config with the current config
The function must return a full configuration object (either via merge() or manual spreading), or
void to make no changes. The hook can also be async.