Affected upstream version: plone/volto main @ 2026-07-04
(packages/registry/src/addon-registry/addon-registry.ts); also observed in the
registry version bundled with Volto 19.1.5
Severity: medium (silent — the shadow is simply ignored; no build warning, no runtime error)
Summary
The addon customization (shadowing) machinery collects shadow candidates with:
// packages/registry/src/addon-registry/addon-registry.ts (main, 2026-07-04)
glob(
`${customPath}/**/*.*(svg|png|jpg|jpeg|gif|ico|less|js|jsx|ts|tsx)`,
)
.cjs and .mjs are not in the extension list, so a file like
<addon>/src/customizations/volto/constants/Languages.cjs is never picked up
and never becomes a webpack alias. Volto core ships .cjs modules that are
natural shadow targets — canonically packages/volto/src/constants/Languages.cjs,
the supported-languages map that language negotiation and supportedLanguages
defaults read. The same file's own docstring elsewhere in the registry already
acknowledges .cjs/.mts as first-class extensions for config files
("Gets the registry configuration from the project's config file (.js, .cjs,
.ts, .mts)"), so the omission in the shadow glob looks like an oversight, not
a policy.
The failure is silent: the build succeeds, the site runs, and language
negotiation quietly uses upstream's default language map instead of the
addon's. From the symptom (an SSR-negotiated locale=en for a language the
addon declares) there is no pointer back to the glob.
Reproduction
- Volto 19.x project with any addon.
- Add
src/customizations/volto/constants/Languages.cjs to the addon,
extending the language map (e.g. add zh, or extend to 60+ entries).
- Build and start.
- Expected: the shadow is aliased over
@plone/volto/constants/Languages.cjs
(the standing customizations contract for every other source extension).
Actual: the shadow is ignored — Accept-Language negotiation and
supportedLanguages behavior run on the upstream 16-language map. No
warning is emitted.
We found this while porting a 70-language translation-platform frontend to a
fresh Volto 19.1.5 scaffold: the negotiation probe (/sv SSR negotiated
locale=en) was the only symptom, and the root cause took a source-trace of
the registry to find.
Why this matters
Languages.cjs is exactly the kind of file the shadowing mechanism exists
for — projects with many/custom languages have no supported way to extend it
from an addon.
- The failure mode is a silent divergence between "the contract works for
.js/.tsx" and "the same contract no-ops for .cjs" — expensive to
debug because nothing errors.
Suggested fix
- Add
cjs|mjs to the customizations glob:
*.*(svg|png|jpg|jpeg|gif|ico|less|js|jsx|ts|tsx|cjs|mjs).
- Leave the alias-key extension-strip regex (
/\.(js|jsx|ts|tsx)$/) as-is:
.cjs/.mjs modules are imported WITH their extension (that is the point
of the suffix), so the alias key must retain it — stripping would break the
exact-specifier match.
- Optional hardening: emit a build-time warning when a file under
customizations/ matches no glob extension — that would have converted
this from a silent divergence into a one-line fix.
One interaction worth noting for the fix's test: an exact-key alias for a
.cjs file must be registered ahead of any @plone/volto prefix alias in the
consumer's webpack config (first-match ordering) or the prefix alias wins; we
hit this downstream when working around the glob.
Local mitigation in our project
volto-polyglot/razzle.extend.js registers the exact-key alias for the
shadowed Languages.cjs manually (prepended ahead of the @plone/volto
prefix alias). Removing the workaround is gated on this fix landing upstream.
Upstream-state check (outbound gate, 2026-07-04)
Per our outbound-report discipline: repos/plone/volto/commits?path=packages/registry
reviewed back to 2025-11-03 (15 commits: releases 3.0.0-alpha.8 → 3.0.1, dep
bumps, tsconfig cleanup, type-definition work, AGENTS.md) — none touch the
customizations glob; the main source quoted above still carries the
omission. Report justified; no existing fix to acknowledge.
Affected upstream version:
plone/voltomain@ 2026-07-04(
packages/registry/src/addon-registry/addon-registry.ts); also observed in theregistry version bundled with Volto 19.1.5
Severity: medium (silent — the shadow is simply ignored; no build warning, no runtime error)
Summary
The addon customization (shadowing) machinery collects shadow candidates with:
.cjsand.mjsare not in the extension list, so a file like<addon>/src/customizations/volto/constants/Languages.cjsis never picked upand never becomes a webpack alias. Volto core ships
.cjsmodules that arenatural shadow targets — canonically
packages/volto/src/constants/Languages.cjs,the supported-languages map that language negotiation and
supportedLanguagesdefaults read. The same file's own docstring elsewhere in the registry already
acknowledges
.cjs/.mtsas first-class extensions for config files("Gets the registry configuration from the project's config file (.js, .cjs,
.ts, .mts)"), so the omission in the shadow glob looks like an oversight, not
a policy.
The failure is silent: the build succeeds, the site runs, and language
negotiation quietly uses upstream's default language map instead of the
addon's. From the symptom (an SSR-negotiated
locale=enfor a language theaddon declares) there is no pointer back to the glob.
Reproduction
src/customizations/volto/constants/Languages.cjsto the addon,extending the language map (e.g. add
zh, or extend to 60+ entries).@plone/volto/constants/Languages.cjs(the standing customizations contract for every other source extension).
Actual: the shadow is ignored —
Accept-Languagenegotiation andsupportedLanguagesbehavior run on the upstream 16-language map. Nowarning is emitted.
We found this while porting a 70-language translation-platform frontend to a
fresh Volto 19.1.5 scaffold: the negotiation probe (
/svSSR negotiatedlocale=en) was the only symptom, and the root cause took a source-trace ofthe registry to find.
Why this matters
Languages.cjsis exactly the kind of file the shadowing mechanism existsfor — projects with many/custom languages have no supported way to extend it
from an addon.
.js/.tsx" and "the same contract no-ops for.cjs" — expensive todebug because nothing errors.
Suggested fix
cjs|mjsto the customizations glob:*.*(svg|png|jpg|jpeg|gif|ico|less|js|jsx|ts|tsx|cjs|mjs)./\.(js|jsx|ts|tsx)$/) as-is:.cjs/.mjsmodules are imported WITH their extension (that is the pointof the suffix), so the alias key must retain it — stripping would break the
exact-specifier match.
customizations/matches no glob extension — that would have convertedthis from a silent divergence into a one-line fix.
One interaction worth noting for the fix's test: an exact-key alias for a
.cjsfile must be registered ahead of any@plone/voltoprefix alias in theconsumer's webpack config (first-match ordering) or the prefix alias wins; we
hit this downstream when working around the glob.
Local mitigation in our project
volto-polyglot/razzle.extend.jsregisters the exact-key alias for theshadowed
Languages.cjsmanually (prepended ahead of the@plone/voltoprefix alias). Removing the workaround is gated on this fix landing upstream.
Upstream-state check (outbound gate, 2026-07-04)
Per our outbound-report discipline:
repos/plone/volto/commits?path=packages/registryreviewed back to 2025-11-03 (15 commits: releases 3.0.0-alpha.8 → 3.0.1, dep
bumps, tsconfig cleanup, type-definition work, AGENTS.md) — none touch the
customizations glob; the
mainsource quoted above still carries theomission. Report justified; no existing fix to acknowledge.