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
- Rename the secret variable so its name does not start with any string in `vite.envPrefix` (most reliable).
- Narrow `vite.envPrefix` to only the prefixes you truly intend to expose client-side, excluding the secret's prefix.
- If the variable must be public, change its `access` from `'secret'` to `'public'` — but only if it genuinely is non-sensitive.
- 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
- Keep secret variable names in a prefix that is explicitly NOT in vite.envPrefix (e.g. SECRET_).
- Review env.schema and vite.envPrefix together in code review.
- Add a CI lint that runs the conflict check before deploy.
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
- ServerOnlyModule
- ServerOnlyModule
- RemoteImageNotAllowed
- UnsupportedImageFormat
- MissingMiddlewareForInternationalization
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/e04c66abe5345d5c.
Report an issue: GitHub.