withastro/astro · error · AstroError

EnvPrefixConflictsWithSecret

EnvPrefixConflictsWithSecret

Error message

The following environment variables are declared with `access: "secret"` in `env.schema`, but their names match a prefix in `vite.envPrefix`, which would expose them in client-side bundles:

${conflicts}

Either remove the conflicting prefixes from `vite.envPrefix`, or rename these variables to use a prefix not in `vite.envPrefix`.

What it means

Astro validates that no variable declared with `access: "secret"` in `env.schema` has a name starting with any prefix listed in `vite.envPrefix`. Vite exposes any matching variable to client-side bundles, which would leak the secret to the browser. Astro refuses to build when it detects this overlap.

Source

Thrown at packages/astro/src/env/validators.ts:210

	const schema = config.env.schema;
	const envPrefix = config.vite?.envPrefix;

	// No schema or using default prefix — nothing to validate
	if (Object.keys(schema).length === 0 || !envPrefix) {
		return;
	}

	const prefixes = Array.isArray(envPrefix) ? envPrefix : [envPrefix];
	const conflicts: string[] = [];

	for (const [key, options] of Object.entries(schema)) {
		if (options.access === 'secret' && prefixes.some((prefix) => key.startsWith(prefix))) {
			conflicts.push(key);
		}
	}

	if (conflicts.length > 0) {
		throw new AstroError({
			...AstroErrorData.EnvPrefixConflictsWithSecret,
			message: AstroErrorData.EnvPrefixConflictsWithSecret.message(conflicts),
		});
	}
}

View on GitHub (pinned to d081033d5f)

Solutions

  1. Rename the secret variable so its name does not start with any string in `vite.envPrefix` (most reliable).
  2. Narrow `vite.envPrefix` to only the prefixes you truly intend to expose client-side, excluding the secret's prefix.
  3. If the variable must be public, change its `access` from `'secret'` to `'public'` — but only if it genuinely is non-sensitive.
  4. Audit the full schema and envPrefix together so no current or future secret name collides.

Example fix

// before
export default defineConfig({
  vite: { envPrefix: ['API_'] },
  env: { schema: { API_KEY: env.string({ access: 'secret' }) } }
})

// after: rename the secret so it no longer matches the public prefix
export default defineConfig({
  vite: { envPrefix: ['API_'] },
  env: { schema: { SECRET_API_KEY: env.string({ access: 'secret' }) } }
})
Defensive patterns

Strategy: validation

Validate before calling

function findSecretPrefixConflicts(schema, envPrefix) {
  const prefixes = Array.isArray(envPrefix) ? envPrefix : [envPrefix];
  return Object.entries(schema)
    .filter(([k, o]) => o.access === 'secret' && prefixes.some(p => k.startsWith(p)))
    .map(([k]) => k);
}
// call before build:
const conflicts = findSecretPrefixConflicts(envSchema, config.vite.envPrefix);
if (conflicts.length) throw new Error('rename secrets: ' + conflicts.join(', '));

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Configuring `env.schema` with a secret variable (e.g. `DATABASE_URL: env.string({ access: 'secret' })`) while `vite.envPrefix` includes a prefix that the variable name starts with (e.g. `envPrefix: ['DATABASE']`, or the default that admits names beginning with the configured prefix).

Common situations: Copying a secret name that happens to share a prefix with a public variable group; setting a broad `envPrefix` like `['APP_']` and later adding `APP_SECRET_KEY` as a secret; migrating from raw `import.meta.env` to `env.schema` without revisiting `envPrefix`.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/e04c66abe5345d5c. Report an issue: GitHub.