Storefront Next Configuration
All app settings live in one typed file, config.server.ts. Environment variables override its values per environment; defineConfig() deep-merges them at startup. Full references ship in the project: docs/README-CONFIG.md (required/optional variable tables) and docs/README-CONFIG-OPTIONS.md (every option).
Model
config.server.ts default-exports defineConfig<Config>({ metadata, runtime, app }, { protectedPaths }), imported from @salesforce/storefront-next-runtime/config.
| Section | Visible to | Contents |
| --- | --- | --- |
| app | Server and browser (window.__APP_CONFIG__) | Commerce API, sites, features, URL/SEO routes, images, security, engagement |
| runtime | Server only | MRT settings such as ssrParameters (ssrFunctionNodeVersion, envBasePath), ssrOnly, ssrShared |
| metadata | Server only | Project name/slug |
Because app reaches the browser, never put secrets in it.
Add a config value
- Add the field to
AppConfiginsrc/types/config.ts(Config = BaseConfig<AppConfig>; theAppConfigShape/ClientFacingAppConfigShapeaugmentations in that file givegetConfiganduseConfigtheir types). - Set a default under
appinconfig.server.ts. Define the key even if the default is empty ('',[]); env overrides are only accepted for paths that exist here. - Override per environment:
PUBLIC__app__myFeature__enabled=true. - Run
pnpm typecheck.
// src/types/config.ts (inside AppConfig)
myFeature: { enabled: boolean; maxItems: number };
// config.server.ts (inside app)
myFeature: { enabled: false, maxItems: 10 },
Read config
import { getConfig, useConfig } from '@salesforce/storefront-next-runtime/config';
// Loaders, actions, middleware (server): context is REQUIRED
export function loader({ context }: LoaderFunctionArgs) {
const config = getConfig(context); // full AppConfig incl. serverExtension
return { max: config.myFeature.maxItems };
}
// Components
const config = useConfig(); // client-facing shape (no serverExtension)
getConfig()with no argument is for browser-only modules (readswindow.__APP_CONFIG__); it returns the client-facing shape. Calling it on the server without a context throws "Configuration not available".- Routes have no
clientLoader(forbidden by the project rules), so server code always passescontext. - Middleware can read
context.get(appConfigContext)(exported from the same module). - In tests use
mockConfig,mockBuildConfig,ConfigWrapper,createConfigWrapperfrom@/test-utils/config.
Environment variable rules
PUBLIC__app__<path>__<to>__<key>=value -> config.app.<path>.<to>.<key>
| Rule | Detail |
| --- | --- |
| PUBLIC__ prefix | Merged into config and exposed to the browser. Client IDs, flags, site lists only |
| No prefix | Server-only; not part of config. Read with process.env in server code |
| Path must exist | An unknown path is skipped with [Config Warning] Ignoring environment variable ... in the server log, so a typo is silent to the user |
| Parsing | Numbers, true/false (as strings), JSON arrays/objects; empty value becomes '' |
| Precedence | Deeper (more specific) paths win over a JSON blob at a parent path |
| Case | Path matching is case-insensitive |
| Depth | Max 10 segments; use a JSON value for deeper structures |
| Restart | .env is read at startup; restart pnpm dev |
Required variables
Only three are required to boot (.env locally; MRT environment variables when deployed):
PUBLIC__app__commerce__api__clientId=...
PUBLIC__app__commerce__api__organizationId=...
PUBLIC__app__commerce__api__shortCode=...
Do not set PUBLIC__app__commerce__api__siteId; that path does not exist and is ignored with a warning. To point at your own site, set PUBLIC__app__defaultSiteId and PUBLIC__app__commerce__sites (see MULTI-SITE-URLS.md). Everything else is optional; see ENV-VARIABLES.md.
Debug "my env var is not applied"
pnpm config:inspect # shows each value and whether it comes from config.server.ts, .env, or MRT
pnpm sfnext config inspect --project my-project --environment staging # include MRT env values
pnpm config:push-env # push .env variables to an MRT environment (b2c mrt env var push)
The config:inspect script echoes a tip after the command, so call sfnext config inspect directly when passing flags.
Checklist: PUBLIC__ with a double underscore, path exists in config.server.ts, booleans are the string true, dev server restarted, variable set on the right MRT environment, and the path is not protected.
Protected paths
defineConfig(..., { protectedPaths }) locks paths against env override. Setting a protected path, or a parent of one (e.g. PUBLIC__app__url), throws at startup. The template protects:
app__url__prefix,app__url__excludeRoutes,app__url__seoRoutes(compiled into routes at build time; changeconfig.server.tsand rebuild/redeploy)app__engagement__adapters__einstein,...__data360,...__activeData__enabled,...__activeData__eventToggles
The rest of app.engagement is overridable. Check the protectedConfigPaths export in your config.server.ts for the current list.
Secrets
Server-only values are plain env vars read via process.env in server routes/middleware, never through config:
COMMERCE_API_SLAS_SECRET: only for a private SLAS client (commerce.api.privateKeyEnabled). A public client needs no secret.GUEST_ORDER_LOOKUP_COOKIE_SECRET: required when guest order lookup is enabled.MARKETING_CLOUD_*: only for email-mode passwordless/reset with your own Marketing Cloud tenant.
Extension config.ts / server-config.ts must not read process.env (an AST check fails discovery).
Extension config
Extensions add config without touching core files:
src/extensions/<name>/config.ts(default-export an object) is merged intoapp.extension.<camelCaseFolder>; override withPUBLIC__app__extension__<key>__<setting>.src/extensions/<name>/server-config.tsis merged intoapp.serverExtension.<camelCaseFolder>, available only throughgetConfig(context); no env override, and the build fails if a client chunk imports it.pnpm config:aggregate-extensionsregenerates the merged files (run automatically bydev,build,typecheck).
Multi-site and URLs
Short version: the current site comes from the request (site-context middleware), not from commerce.sites[0]. Use useSite() from @salesforce/storefront-next-runtime/site-context in components and context.get(siteContext) (same module) on the server. URL shape, seoRoutes, aliases, MRT Data Store sites, cookie domain, multiple domains, and base path are in MULTI-SITE-URLS.md.
Pre-launch config gate
Before going live, review these defaults in config.server.ts:
app.engagement.adapters(Einstein, Active Data, Data 360): defaults carry demo IDs/hosts. Enable only with your own values, or disable them; also confirm consent categories.images.hostdefaults to the DIS staging host (edge.disstg...); use the production DIS host, and addrealmHostMappingsfor custom domains..env/ MRT variables point at your own client ID, organization ID, short code, and site(s), not the demo backend.commerce.sites,defaultSiteId, andsiteAliasMapmatch your Business Manager sites;url.seoRouteshas an entry for every active site if enabled.- Cookie domain (
cookies.domain) matches Business Manager Hybrid Auth level if used. - Security headers / CSP (
app.security.headers) and Turnstile keys are reviewed (storefront-next:sfnext-security).
Related Skills
storefront-next:sfnext-project-setup- Create the project,.envbasics, scriptsstorefront-next:sfnext-deployment- Push bundles and sync MRT env varsstorefront-next:sfnext-routing- Site-aware links and SEO route behaviorstorefront-next:sfnext-i18n- Locales, translations, locale switchingstorefront-next:sfnext-extensions- Extension authoringstorefront-next:sfnext-hybrid-storefronts- Hybrid proxy and shared cookies with SFRAstorefront-next:sfnext-security- Security headers, CSP, Turnstileb2c-cli:b2c-mrt- MRT environment variable management
Finding more
Project docs/README-CONFIG.md, docs/README-CONFIG-OPTIONS.md, docs/README-MULTI-SITE.md; b2c docs search "<topic>" --category sfnext.