withastro/astro · error · Error

Glob patterns cannot start with `../`. Set the `base`…

Error message

Glob patterns cannot start with `../`. Set the `base` option to a parent directory instead.

What it means

The glob() loader's pattern is relative to its `base` option (the content directory by default). A pattern starting with `../` is rejected with a plain Error because it tries to walk outside the content root; the supported way to reach parent directories is to move the escape into `base`, which is validated separately.

Solutions

  1. Set `base` to the parent directory and keep the pattern relative to it: `glob({ pattern: '**/*.md', base: '../..' })` reaches the project root from src/content.
  2. Or relocate the content under the content directory so a plain relative pattern works.
  3. base also accepts a file URL (e.g. `import.meta.url`-relative), useful when the content sits inside a node_modules package.

Example fix

// before
const Docs = defineCollection({
  loader: glob({ pattern: '../docs/**/*.md' }),
});

// after
const Docs = defineCollection({
  loader: glob({ pattern: '**/*.md', base: '../..' }), // src/content -> project root
});
Defensive patterns

Strategy: validation

Validate before calling

function normalizeGlobOptions(pattern: string, base?: string) {
  if (pattern.replace(/^(\.\/|!)+/, '').startsWith('../')) {
    // fold the escape into base instead
    const segments = pattern.split('/');
    let i = 0;
    while (segments[i] === '..' || segments[i] === '.' || segments[i] === '!..') i += segments[i] === '..' ? 1 : 0, segments[i] === '..' ? i++ : i;
    // simpler: hand-write the base for known cases
  }
  return { pattern, base };
}

// pragmatic check:
if (/^(\.\/|!)*(\.\.\/)/.test(pattern)) {
  throw new Error('Move parent-directory traversal into the `base` option');
}

Type guard

const isSafeGlobPattern = (pattern: string): boolean =>
  !pattern.replace(/^(\.\/|!)+/, '').startsWith('../');

Prevention

When it happens

Trigger: `glob({ pattern: '../docs/**/*.md' })` — any pattern whose first characters are `../` (the check also strips leading `./` and `!` prefixes before testing).

Common situations: Content living outside src/content — e.g. a project-root docs/ folder, a monorepo shared content package, or generated content written to the repo root.

Related errors


AI-assisted analysis of withastro/astro@e294953aa8 (2026-08-18). Data as JSON: /api/errors/90688a4ebdc31a8e. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/content/loaders/glob.ts:88

}

function checkPrefix(pattern: string | Array<string>, prefix: string) {
	if (Array.isArray(pattern)) {
		return pattern.some((p) => p.startsWith(prefix));
	}
	return pattern.startsWith(prefix);
}

export const secretLegacyFlag = Symbol('astro.legacy-glob');

/**
 * Loads multiple entries, using a glob pattern to match files.
 * @param pattern A glob pattern to match files, relative to the content directory.
 */

export function glob(globOptions: GlobOptions & { [secretLegacyFlag]?: boolean }): Loader {
	if (checkPrefix(globOptions.pattern, '../')) {
		throw new Error(
			'Glob patterns cannot start with `../`. Set the `base` option to a parent directory instead.',
		);
	}
	if (checkPrefix(globOptions.pattern, '/')) {
		throw new Error(
			'Glob patterns cannot start with `/`. Set the `base` option to a parent directory or use a relative path instead.',
		);
	}

	const isLegacy = !!globOptions[secretLegacyFlag];
	const userGenerateId =
		globOptions?.generateId ?? ((opts: GenerateIdOptions) => generateIdDefault(opts, isLegacy));
	// Coerce to string so numeric ids from YAML don't cause Set strict-equality mismatches
	// against string store keys in the untouched-entries cleanup. See #17624.
	const generateId = (opts: GenerateIdOptions) => String(userGenerateId(opts));

	const fileToIdMap = new Map<string, string>();

View on GitHub (pinned to e294953aa8)