withastro/astro · error · MarkdocError

**${String(relativePartialPath)}** contains invalid content:

Error message

**${String(relativePartialPath)}** contains invalid content:
Could not read partial file `${file}`. Does the file exist?

What it means

When a `{% partial file="x" %}` tag does not match an entry in the Markdoc config's `partials` map, the integration tries to resolve `x` as a module from the containing file and read it from disk. If resolution or fs.readFile fails (missing file, wrong path, unreadable), it throws this MarkdocError naming the containing document and the requested partial. The generic `throw new Error()` inside the try block funnels every failure mode into this one message.

Source

Thrown at packages/integrations/markdoc/src/content-entry-type.ts:202

			let partialPath: string;
			let partialContents: string;
			try {
				const resolved = await pluginContext.resolve(file, fileURLToPath(fileUrl));
				let partialId = resolved?.id;
				if (!partialId) {
					const attemptResolveAsRelative = await pluginContext.resolve(
						'./' + file,
						fileURLToPath(fileUrl),
					);
					if (!attemptResolveAsRelative?.id) throw new Error();
					partialId = attemptResolveAsRelative.id;
				}

				partialPath = fileURLToPath(new URL(prependForwardSlash(partialId), 'file://'));
				partialContents = await fs.promises.readFile(partialPath, 'utf-8');
			} catch {
				throw new MarkdocError({
					message: [
						`**${String(relativePartialPath)}** contains invalid content:`,
						`Could not read partial file \`${file}\`. Does the file exist?`,
					].join('\n'),
				});
			}
			if (pluginContext.meta.watchMode) pluginContext.addWatchFile(partialPath);
			let partialTokens = tokenizer.tokenize(partialContents);
			if (allowHTML) {
				partialTokens = htmlTokenTransform(tokenizer, partialTokens);
			}
			const partialAst = Markdoc.parse(partialTokens);
			raisePartialValidationErrors(partialAst, partialPath);
			await resolvePartials({
				ast: partialAst,
				root,
				fileUrl: pathToFileURL(partialPath),
				tokenizer,

View on GitHub (pinned to 52e6c34790)

Solutions

  1. Verify the partial file exists at the path you reference, relative to the content file that includes it.
  2. Use the exact import specifier style that resolves: `./`-prefixed relative paths are the reliable form.
  3. Alternatively register partials explicitly via `partials` in the Markdoc config passed to the integration.
  4. Check file permissions if it exists but still fails.

Example fix

{% partial file="missing/header" /%}

{% partial file="./src/content/partials/header" /%}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-build check: every referenced partial file resolves relative to its content file
import { Markdoc } from '@markdoc/markdoc';
import { globSync } from 'glob';
import fs from 'node:fs';
import path from 'node:path';
for (const f of globSync('src/**/*.mdoc')) {
  const ast = Markdoc.parse(fs.readFileSync(f, 'utf8'));
  for (const node of ast.walk()) {
    if (node.type === 'tag' && node.tag === 'partial' && typeof node.attributes.file === 'string') {
      const p = path.resolve(path.dirname(f), node.attributes.file.replace(/^\//, ''));
      if (!fs.existsSync(p)) throw new Error(`${f}: partial not found at ${p}`);
    }
  }
}

Prevention

When it happens

Trigger: Referencing `{% partial file="headers/foo" %}` where src/.../headers/foo.md(md) does not exist; a path that neither resolves bare nor as `./path` relative to the .mdoc file; the file exists but cannot be read (permissions).

Common situations: Renaming or moving partial files without updating references; wrong relative depth (`../` vs `./`); missing file extension that the resolver expects; partials directory not where the author assumes.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/90d14269d1e1cf03. Report an issue: GitHub.