Configuration

This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page was written against Docusaurus 3.10.2 (the last release of the 3.x line) and checked on 2026-10-26. Every site has one configuration file, docusaurus.config.ts, that is read in Node.js when you run docusaurus start, build or deploy. This page walks through each part of that file using the Pocket Units example site (a tiny units-conversion library with C# and Python implementations), shows the validation errors you get when a value is wrong, and explains the fields that are deprecated or about to change in v4.

What the configuration file is

The file lives at the site root. Docusaurus looks for docusaurus.config with one of these extensions, in this order: .ts, .mts, .cts, .js, .mjs, .cjs. A different file can be selected with the --config option of the CLI commands. The file runs only in Node.js, so it must not use browser APIs or JSX.

The default export can be a config object, a promise of one, or a function (sync or async) that returns the object. The async form is useful when you must read a file or call an API before the site is built.

The loaded object goes through a schema validation (Joi). It is then normalised and some defaults are resolved from the future flags. Unknown top-level fields are rejected; custom data goes in customFields.

flowchart LR A["docusaurus.config.ts"] --> B["Load module
(object, promise or function)"] B --> C["Validate with schema
unknown fields rejected"] C --> D["Normalise
url, baseUrl, flag defaults"] D --> E["Presets expand into
plugins and themes"] E --> F["start / build / deploy"]

Figure: the path of the configuration file from disk to the commands that use it.

The required fields and site metadata

Three fields are required: title, url and baseUrl. The rest have defaults.

Field Meaning

title

Site title, used in the browser tab title and metadata.

tagline

Short description of the site. Default is an empty string.

favicon

Path of the favicon, relative to static/ (for example img/favicon.ico).

titleDelimiter

Separator in the generated <title> tag. Default is a vertical bar.

noIndex

When true, adds a robots meta tag with noindex, nofollow to every page. Default false.

staticDirectories

Directories copied as-is into the build output. Default is static.

url and baseUrl

url is the top-level origin of the site and baseUrl is the path under which it is served. For the page https://example-org.github.io/pocket-units/docs/intro, url is https://example-org.github.io and baseUrl is /pocket-units/.

The validation is strict, and I ran each of the following in a scratch Docusaurus 3.10.2 site:

  • url without a protocol fails with "example-org.github.io" does not look like a valid URL. Make sure it has a protocol; for example, "https://example.com".

  • url with a path fails with The url is not supposed to contain a sub-path like "/pocket-units". Please use the baseUrl field for sub-paths.

  • A trailing slash on url is accepted and removed. A baseUrl of pocket-units is normalised to /pocket-units/ (a leading and a trailing slash are added). Loading the configuration {url: 'https://example-org.github.io/', baseUrl: 'pocket-units'} produced url equal to https://example-org.github.io and baseUrl equal to /pocket-units/.

A wrong baseUrl is the most common reason for a deployed site that loads without CSS or JavaScript. The option baseUrlIssueBanner (default true) shows a banner on the page when the site cannot load its own assets, to make the cause easier to find.

trailingSlash

trailingSlash controls whether links end with a slash and how HTML files are written:

Value Effect

not set (default)

Links are left as written. The page docs/intro is written to docs/intro/index.html.

true

Trailing slashes are added to links. Files are written as docs/intro/index.html.

false

Trailing slashes are removed. Files are written as docs/intro.html.

With trailingSlash: false in the example site, the build produced build/docs/intro.html. The value matters for the host: GitHub Pages adds trailing slashes itself, so the deployment guide recommends setting it explicitly to true or false, not leaving it unset. A mismatch between the setting and the host can produce redirects or 404 pages on some hosts; the official docs link a third-party trailing-slash guide for the details.

Deployment fields

organizationName and projectName are the GitHub user or organisation and the repository name. deploymentBranch is the branch that docusaurus deploy pushes to, and githubHost and githubPort support GitHub Enterprise. They are only used by the deploy command, not by the build. See Deployment for the full workflow.

Four fields choose what happens when Docusaurus finds a problem. Each accepts ignore, log, warn or throw.

Field Default What it checks

onBrokenLinks

throw

Links between pages found while building. Checked in production builds only.

onBrokenAnchors

warn

Links to a heading anchor that does not exist.

onDuplicateRoutes

warn

Two pages that resolve to the same route.

markdown.hooks.onBrokenMarkdownLinks

warn

Markdown file links (./other.md) that cannot be resolved.

markdown.hooks.onBrokenMarkdownImages

throw

Markdown images whose file cannot be found.

An invalid value is reported by the schema: onBrokenLinks: 'error' fails with "onBrokenLinks" must be one of [ignore, log, warn, throw].

The top-level onBrokenMarkdownLinks field is deprecated since 3.9 and will be removed in v4. Its replacement is markdown.hooks.onBrokenMarkdownLinks, next to the new markdown.hooks.onBrokenMarkdownImages. For 3.x compatibility, if the old field is set it wins over the new one. Running a build with onBrokenMarkdownLinks: 'throw' printed this warning (shown without its log level prefix):

The `siteConfig.onBrokenMarkdownLinks` config option is deprecated and will be removed in Docusaurus v4.
Please migrate and move this option to `siteConfig.markdown.hooks.onBrokenMarkdownLinks` instead.

With the hook set to throw and a document that contains [missing](./nope.md), the build failed with the message that the Markdown link with URL ./nope.md could not be resolved and must reference a local Markdown file that exists within the current plugin. The message suggests two ways out: change the hook, or use the pathname:// protocol on links that are not Markdown files. A hook can also be a function that receives sourceFilePath, url and node and may return a replacement URL.

Presets, plugins and themes

presets, plugins and themes are arrays. A preset is a bundle of plugins and themes with shared options; the classic preset contains the docs, blog and pages plugins, the sitemap and other plugins, and the classic theme. Each entry has one of these forms:

  • a string: 'classic'

  • a pair of name and options: ['classic', { ... }]

  • for plugins and themes, a function, or a pair of function and options

  • false or null to disable an entry (presets, plugins and themes)

A malformed entry fails with Bad Docusaurus plugin value plugins[0] followed by examples of the valid forms. Plugin and theme names are resolved as npm packages or local paths. See Plugins and presets for how to write one.

In a TypeScript config, satisfies Preset.Options on the options object gives type checking and completion for the preset without widening the type.

themeConfig

themeConfig is an object with options for the theme: color mode, navbar, footer, Prism code highlighting, the announcement bar, the search and so on. The base type is an open object ([key: string]: unknown), and each theme documents its own keys. With the classic theme, satisfies Preset.ThemeConfig provides the types. See Styling, theming and swizzling for the visual options.

customFields

Unknown top-level fields make the build fail:

[ERROR] Error: These field(s) ("tagLine",) are not recognized in cfgtests/unknown.mjs.
If you still want these fields to be in your configuration, put them in the "customFields" field.
See https://docusaurus.io/docs/api/docusaurus-config/#customfields

customFields is the place for your own data. It is available to React components through the site config:

src/components/Packages/index.tsx
import type {ReactNode} from 'react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';

export default function Packages(): ReactNode {
  const {siteConfig} = useDocusaurusContext();
  const {nugetPackage, pypiPackage} = siteConfig.customFields as {
    nugetPackage: string;
    pypiPackage: string;
  };
  return (
    <ul>
      <li>NuGet: <code>{nugetPackage}</code></li>
      <li>PyPI: <code>{pypiPackage}</code></li>
    </ul>
  );
}

Used from an MDX page with import Packages from '@site/src/components/Packages'; and <Packages />, the build rendered NuGet: PocketUnits and PyPI: pocket-units. Because customFields is typed as an open object, the cast is needed. The object is serialised into the client bundle, so never put secrets in it. Environment variables read with process.env work only in the configuration file itself (Node.js), and customFields is the way to pass them to the browser.

Head and asset fields

Field Meaning

headTags

Array of objects with tagName and attributes that are inserted in the HTML head. attributes is required (an object without it fails with "headTags[0].attributes" is required).

scripts

Scripts to load, as a string (the src) or an object with a src and attributes such as async and defer. They are inserted in the head and block rendering unless async or defer is set.

stylesheets

Stylesheets to load, as a string (the href) or an object with an href.

clientModules

Modules executed on the client at startup, for global code or CSS.

ssrTemplate

An Eta HTML template used to render the application shell, for custom body attributes or extra meta tags. The template must keep the structure Docusaurus expects.

webpack.jsLoader

Selects the JavaScript loader, 'babel' or a custom rule function.

i18n

i18n declares defaultLocale, locales, an optional path (default i18n) and localeConfigs with per-locale label, htmlLang, direction, calendar, path, translate, and, since 3.9, url and baseUrl. A site with one locale still needs defaultLocale: 'en' and locales: ['en'], as the generated config shows. The details are in Internationalization.

markdown

The markdown object configures how Markdown and MDX are compiled.

Key Default Meaning

format

mdx

mdx, md or detect. detect picks the format from the file extension (.md or .mdx).

mermaid

false

Renders mermaid code blocks as diagrams. Also needs @docusaurus/theme-mermaid in themes.

emoji

true

Converts emoji shortcodes to Unicode (added in 3.9).

anchors.maintainCase

false

Keeps the case of the heading text when generating anchor ids.

hooks.onBrokenMarkdownLinks

warn

Severity or function for unresolved Markdown links.

hooks.onBrokenMarkdownImages

throw

Severity or function for unresolved Markdown images.

mdx1Compat.comments

true

Accept HTML comments in MDX.

mdx1Compat.admonitions

true

Accept the :::note My Title form of admonition titles.

mdx1Compat.headingIds

true

Accept {#id} heading ids.

preprocessor

none

A function of one argument (file path and content) that returns the content to compile.

parseFrontMatter

built in

A function to read front matter from another source or to post-process it.

remarkRehypeOptions

none

Options passed to remark-rehype.

The three mdx1Compat defaults are true in v3 and become false when future.v4.mdx1CompatDisabledByDefault is on; explicit values always win. See MDX and React components for the consequences.

storage

storage chooses where the site keeps small browser state such as the theme and the selected tab:

  • type: localStorage (default) or sessionStorage; any other value fails with "storage.type" must be one of [localStorage, sessionStorage].

  • namespace: false by default, a string to choose the prefix, or true. The default becomes true when future.v4.siteStorageNamespacing is on.

Namespacing matters when several Docusaurus sites share an origin (for example, sites under different base URLs of the same GitHub Pages host), because they would otherwise overwrite each other’s keys. Turning it on for an existing site resets the stored choices of current visitors once.

future

future holds opt-in flags for features that will become defaults. In 3.10.2 the object has these members:

  • v4: true enables all v4 flags; or an object with removeLegacyPostBuildHeadAttribute, useCssCascadeLayers, siteStorageNamespacing, fasterByDefault and mdx1CompatDisabledByDefault.

  • faster: true enables all Docusaurus Faster flags; or an object with swcJsLoader, swcJsMinimizer, swcHtmlMinimizer, lightningCssMinimizer, mdxCrossCompilerCache, rspackBundler, rspackPersistentCache, ssgWorkerThreads and gitEagerVcs. It requires the @docusaurus/faster package.

  • experimental_vcs and experimental_router (browser or hash), which keep the experimental_ prefix.

The key future.faster replaced future.experimental_faster, and storage was promoted from future.experimental_storage. The old names now fail at startup with a message saying that future.experimental_faster has been renamed to future.faster, and that future.experimental_storage has been promoted to the top-level storage attribute.

Two dependencies between flags are enforced when the configuration loads:

  • future.faster.ssgWorkerThreads requires future.v4.removeLegacyPostBuildHeadAttribute.

  • future.faster.rspackPersistentCache requires future.faster.rspackBundler.

Setting future: {v4: true}, as the generated site does, has a larger effect than it looks, because fasterByDefault turns every Faster flag on. I loaded the Pocket Units configuration below and printed the normalised result: all nine faster flags were true, the three mdx1Compat flags were false, and storage.namespace was the string set in the file. The same file with future removed gave all faster flags false, the mdx1Compat flags true and storage.namespace false. See Docusaurus Faster and v4 readiness for what each flag does.

TypeScript types

The Config type from @docusaurus/types describes the object as you write it: a deep partial of the validated configuration, where title, url and baseUrl stay required. future.v4 and future.faster also accept a boolean. The preset package exports Preset.Options and Preset.ThemeConfig. A new site from the TypeScript template ships a tsconfig.json that extends @docusaurus/tsconfig and sets strict and ignoreDeprecations: "6.0" (needed with TypeScript 6 for now), and a typecheck script that runs tsc. The tsconfig.json is not used by start or build; the config file is compiled by Docusaurus itself.

Complete example: Pocket Units

This is the full configuration used for the checks above. With npx tsc it type-checked without errors, and npx docusaurus build completed with Generated static files in "build".

docusaurus.config.ts
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';

// This file runs in Node.js: no browser APIs and no JSX here.
const config: Config = {
  title: 'Pocket Units',
  tagline: 'Convert lengths in C# and Python',
  favicon: 'img/favicon.ico',

  url: 'https://example-org.github.io',
  baseUrl: '/pocket-units/',
  trailingSlash: false,

  organizationName: 'example-org',
  projectName: 'pocket-units',
  deploymentBranch: 'gh-pages',

  onBrokenLinks: 'throw',
  onBrokenAnchors: 'warn',

  i18n: {
    defaultLocale: 'en',
    locales: ['en'],
  },

  storage: {
    type: 'localStorage',
    namespace: 'pocket-units',
  },

  future: {
    v4: true,
  },

  markdown: {
    format: 'mdx',
    emoji: false,
    anchors: {maintainCase: false},
    hooks: {
      onBrokenMarkdownLinks: 'throw',
      onBrokenMarkdownImages: 'throw',
    },
  },

  customFields: {
    nugetPackage: 'PocketUnits',
    pypiPackage: 'pocket-units',
  },

  headTags: [
    {
      tagName: 'meta',
      attributes: {name: 'author', content: 'Pocket Units contributors'},
    },
  ],

  presets: [
    [
      'classic',
      {
        docs: {
          sidebarPath: './sidebars.ts',
          editUrl: 'https://github.com/example-org/pocket-units/tree/main/',
        },
        blog: false,
        theme: {
          customCss: './src/css/custom.css',
        },
      } satisfies Preset.Options,
    ],
  ],

  themeConfig: {
    colorMode: {respectPrefersColorScheme: true},
    navbar: {
      title: 'Pocket Units',
      items: [
        {
          type: 'docSidebar',
          sidebarId: 'tutorialSidebar',
          position: 'left',
          label: 'Docs',
        },
        {
          href: 'https://github.com/example-org/pocket-units',
          label: 'GitHub',
          position: 'right',
        },
      ],
    },
    footer: {
      style: 'dark',
      copyright: `Copyright ${new Date().getFullYear()} Pocket Units contributors.`,
    },
    prism: {
      theme: prismThemes.github,
      darkTheme: prismThemes.dracula,
      additionalLanguages: ['csharp', 'python'],
    },
  } satisfies Preset.ThemeConfig,
};

export default config;

The built page for docs/intro had the title Tutorial Intro | Pocket Units and the canonical link https://example-org.github.io/pocket-units/docs/intro, which combines url, baseUrl and the route without a trailing slash. The docs/intro.html file name follows from trailingSlash: false. The blog option of the preset is false here, so the blog plugin is not loaded and the blog/ folder of the template is unused.

Pitfalls and version notes

  • Unknown top-level keys fail the build; use customFields.

  • url has no path and baseUrl has both slashes. Fix a deployed site without styles by checking baseUrl first.

  • Keep onBrokenLinks: 'throw' for CI; it applies to production builds, not to docusaurus start.

  • Do not use onBrokenMarkdownLinks at the top level in new sites (deprecated in 3.9, removed in v4).

  • Do not use future.experimental_faster or future.experimental_storage; both fail at startup in 3.10.

  • future.v4: true also enables Faster and turns off the mdx1Compat leniency, so a site that relied on old comment or admonition syntax must be fixed or must set those options explicitly.

  • TypeScript 6 and strict mode are the default for new TypeScript sites since 3.10.

  • Do not put secrets into customFields, headTags or scripts: they are shipped to every visitor.