{"record":{"id":"94beaa003cdd7211","repo":"withastro/astro","slug":"file-path-must-be-relative-to-the-site-root-got","errorCode":null,"errorMessage":"File path must be relative to the site root. Got: ${filePath}","messagePattern":"File path must be relative to the site root\\. Got: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/astro/src/content/mutable-data-store.ts","lineNumber":456,"sourceCode":"\t\t\t\t\t\tconst src = val.replace(IMAGE_IMPORT_PREFIX, '');\n\t\t\t\t\t\tfoundAssets.add(src);\n\t\t\t\t\t\trecordImageImport(ctx.path.map((segment) => segment as string | number));\n\t\t\t\t\t\tctx.update(src);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tconst entry: DataEntry = {\n\t\t\t\t\tid,\n\t\t\t\t\tdata,\n\t\t\t\t};\n\t\t\t\t// We do it like this so we don't waste space stringifying\n\t\t\t\t// the fields if they are not set\n\t\t\t\tif (body) {\n\t\t\t\t\tentry.body = body;\n\t\t\t\t}\n\t\t\t\tif (filePath) {\n\t\t\t\t\tif (filePath.startsWith('/')) {\n\t\t\t\t\t\tthrow new Error(`File path must be relative to the site root. Got: ${filePath}`);\n\t\t\t\t\t}\n\t\t\t\t\tentry.filePath = filePath;\n\t\t\t\t}\n\n\t\t\t\tif (foundAssets.size) {\n\t\t\t\t\tentry.assetImports = Array.from(foundAssets);\n\t\t\t\t\tthis.addAssetImports(entry.assetImports, filePath);\n\t\t\t\t}\n\n\t\t\t\tif (imageImports.length) {\n\t\t\t\t\tentry.imageImports = imageImports;\n\t\t\t\t}\n\n\t\t\t\tif (digest) {\n\t\t\t\t\tentry.digest = digest;\n\t\t\t\t}\n\t\t\t\tif (rendered) {\n\t\t\t\t\tentry.rendered = rendered;","sourceCodeStart":438,"sourceCodeEnd":474,"githubUrl":"https://github.com/withastro/astro/blob/e294953aa8aadd98d5be92e60a03037b05dbdfd4/packages/astro/src/content/mutable-data-store.ts#L438-L474","documentation":"The data store requires `filePath` on `set()` to be a path relative to the project root, because the store is serialized and shared across machines. A filePath starting with `/` (an absolute path) is rejected with a plain Error so machine-specific paths never get persisted.","triggerScenarios":"A custom loader passing `fileURLToPath(entryUrl)` or `path.resolve(...)` output directly: `store.set({ id, data, filePath: '/home/me/project/src/content/a.md' })`.","commonSituations":"Custom loaders built from Node examples that use absolute paths; porting code that logged absolute paths for debugging and then fed them back into the store.","solutions":["Relativize against the project root before setting: compute `relative(fileURLToPath(config.root), filePath)` and convert separators to `/`.","Or omit `filePath` if you do not need file-tracking (watcher/HMR) features for these entries.","Use the same convention as built-in loaders: POSIX-style relative path like `src/content/blog/post.md`."],"exampleFix":"// before\nimport { fileURLToPath } from 'node:url';\nstore.set({ id, data, filePath: fileURLToPath(fileUrl) }); // absolute\n\n// after\nimport { relative, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nconst filePath = relative(fileURLToPath(config.root), fileURLToPath(fileUrl)).split(sep).join('/');\nstore.set({ id, data, filePath });","handlingStrategy":"validation","validationCode":"import { relative, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nfunction toRootRelative(root: URL, filePath: string): string {\n  const rel = relative(fileURLToPath(root), filePath);\n  if (rel.startsWith('..')) throw new Error(`filePath escapes project root: ${filePath}`);\n  return rel.split(sep).join('/');\n}\n\nstore.set({ id, data, filePath: toRootRelative(config.root, absolutePath) });","typeGuard":"const isRootRelativePosixPath = (p: string): boolean =>\n  !p.startsWith('/') && !p.includes('\\\\') && !p.startsWith('..');","tryCatchPattern":null,"preventionTips":["Always relativize against config.root in custom loaders; never persist fileURLToPath output.","Use forward slashes so the store stays portable to Windows."],"tags":["content-collections","data-store","file-path","custom-loader"],"backgroundTag":"invalid-file-path","analyzedSha":"e294953aa8aadd98d5be92e60a03037b05dbdfd4","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}