facebook/docusaurus · error

Heading ids can either be overwritten or migrated, not both

Error message

Heading ids can either be overwritten or migrated, not both at the same time

What it means

Thrown by writeMarkdownHeadingId() when its options object has both overwrite: true and migrate: true. These two modes are mutually exclusive: overwrite regenerates heading IDs from the heading text (discarding any existing ID), while migrate rewrites existing IDs into the target syntax (preserving their value). Permitting both would be ambiguous, so the function rejects the combination up front.

Source

Thrown at packages/docusaurus-utils/src/markdownHeadingIdUtils.ts:149

 * Takes Markdown content, returns new content with heading IDs written.
 * Respects existing IDs (unless `overwrite=true`) and never generates colliding
 * IDs (through the slugger).
 */
export function writeMarkdownHeadingId(
  content: string,
  options: WriteHeadingIDOptions = {},
): string {
  const {
    syntax = 'classic', // Maybe we'll want to change this default later?
    overwrite = false,
    migrate = false,
    maintainCase = false,
  } = options;

  // For now, we have 2 booleans (retro compatible)
  // but it could be useful to have a "mode" enum instead?
  if (overwrite && migrate) {
    throw new Error(
      'Heading ids can either be overwritten or migrated, not both at the same time',
    );
  }

  const lines = content.split('\n');
  const slugger = createSlugger();

  // Parse heading ID trying both syntaxes (classic first, then mdx-comment)
  function parseHeadingIdAnySyntax(heading: string) {
    const classic = parseMarkdownHeadingId(heading, 'classic');
    if (classic.id) {
      return classic;
    }
    return parseMarkdownHeadingId(heading, 'mdx-comment');
  }

  // If we can't overwrite existing slugs, make sure other headings don't
  // generate colliding slugs by first marking these slugs as occupied

View on GitHub (pinned to 3f483e80e3)

Solutions

  1. Decide which behavior you want: pass --overwrite to regenerate IDs from heading text, or --migrate to preserve existing IDs and only change their syntax — never both.
  2. If calling the API directly, set exactly one of { overwrite, migrate } to true (or neither for the default preserve-and-fill behavior).
  3. Re-run the CLI or your script with a single mode flag.
  4. If you genuinely need both effects (rewrite then change syntax), run the command twice sequentially: first overwrite, then migrate.

Example fix

// before
writeMarkdownHeadingId(content, { overwrite: true, migrate: true });

// after — pick one mode
writeMarkdownHeadingId(content, { overwrite: true });
// or
writeMarkdownHeadingId(content, { migrate: true });
Defensive patterns

Strategy: validation

Validate before calling

function isValidHeadingIdOptions(opts: { overwrite?: boolean; migrate?: boolean }): boolean {
  return !(opts.overwrite && opts.migrate);
}

if (!isValidHeadingIdOptions(options)) {
  throw new Error('Choose overwrite OR migrate, not both.');
}

Type guard

function isSingleHeadingMode(opts: { overwrite?: boolean; migrate?: boolean }): boolean {
  return !(opts.overwrite === true && opts.migrate === true);
}

Try / catch

try {
  writeMarkdownHeadingId(content, options);
} catch (err) {
  if (err instanceof Error && err.message.includes('overwritten or migrated')) {
    // pick one mode and retry
  }
  throw err;
}

Prevention

When it happens

Trigger: Invoking writeMarkdownHeadingId(content, { overwrite: true, migrate: true }) — most commonly via the `docusaurus write-heading-ids` CLI with both flags passed, or programmatically when wiring the function into a custom processor.

Common situations: Running `docusaurus write-heading-ids --overwrite --migrate` with both flags. A custom script that merges user options onto defaults where both booleans default to true. Confusion about the difference between the two modes leads to passing both 'to be safe'.

Related errors


AI-assisted analysis of facebook/docusaurus@3f483e80e3 (2026-08-12). Data as JSON: /api/errors/516cddbb5ece0b87. Report an issue: GitHub.