withastro/astro · error · MarkdocError

Could not resolve image

Error message

Could not resolve image ${JSON.stringify(node.attributes.src)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?

What it means

While transforming Markdoc content, the integration tries to resolve each image node's `src` through Astro's asset pipeline (emitting optimized images). If `src` cannot be resolved to a local file, it throws this MarkdocError echoing the raw src and the containing file path. Only resolvable local images and handled component forms make it through; an unresolvable path aborts the build.

Solutions

  1. Check that the path in the error exists, relative to the content file shown in the message.
  2. Fix the relative prefix (`./`, `../`) or move the image into the referenced location.
  3. For remote images, use the full URL form supported by the {% image %} component path rather than a bare path.
  4. Watch for case mismatches between the filename in content and on disk.

Example fix

{% image src="../images/hero.png" alt="Hero" /%}

{% image src="./images/hero.png" alt="Hero" /%}
Defensive patterns

Strategy: validation

Validate before calling

// Verify image sources referenced by .mdoc files exist before build
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')) {
  for (const node of Markdoc.parse(fs.readFileSync(f, 'utf8')).walk()) {
    if (node.type !== 'image' && node.tag !== 'image') continue;
    const src = node.attributes?.src;
    if (typeof src === 'string' && !/^(https?:)?\/\//.test(src)) {
      if (!fs.existsSync(path.resolve(path.dirname(f), src))) {
        throw new Error(`${f}: image not found: ${src}`);
      }
    }
  }
}

Prevention

When it happens

Trigger: An image node / `{% image %}` tag in a .mdoc file whose `src` points to a nonexistent local file — wrong relative path, moved asset, missing extension — while not matching the component/remote-URL escape hatches.

Common situations: Content authors referencing images from a different directory depth; assets moved during refactors; case-sensitivity mismatches on case-sensitive filesystems/CI.

Related errors


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

Appendix: source

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

						? undefined
						: (opts: Parameters<typeof ctx.pluginContext.emitFile>[0]) =>
								emitClientAsset(ctx.pluginContext, opts);
					const src = await emitImageMetadata(resolved.id, fileEmitter);

					const fsPath = resolved.id;

					if (src) {
						// We cannot track images in Markdoc, Markdoc rendering always strips out the proxy. As such, we'll always
						// assume that the image is referenced elsewhere, to be on safer side.
						if (ctx.astroConfig.output === 'static') {
							if (globalThis.astroAsset.referencedImages)
								globalThis.astroAsset.referencedImages.add(fsPath);
						}

						node.attributes[attributeName] = { ...src, fsPath };
					}
				} else {
					throw new MarkdocError({
						message: `Could not resolve image ${JSON.stringify(
							node.attributes.src,
						)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`,
					});
				}
			} else if (isComponent) {
				// If the user is using the {% image %} tag, always pass the `src` attribute as `__optimizedSrc`, even if it's an external URL or absolute path.
				// That way, the component can decide whether or not to optimize it.
				node.attributes[attributeName] = node.attributes.src;
			}
		}
		await emitOptimizedImages(node.children, ctx);
	}
}

function shouldOptimizeImage(src: string) {
	// Optimize anything that is NOT external or an absolute path to `public/`
	return !isValidUrl(src) && !src.startsWith('/');

View on GitHub (pinned to 52e6c34790)