Project Configuration
Storefront Next uses a centralized, type-safe configuration system that gives you:
- IDE autocomplete: Full TypeScript support with suggestions as you type.
- Environment overrides: Customize settings per environment without code changes.
- Validation: Catch configuration errors at startup, not at run time.
- Security by design: Clear separation between public and server-only values.
All configuration lives in a single file (config.server.ts) with defaults that you can override by using environment variables.
Storefront Next provides two APIs for accessing configuration.
getConfig() returns the full AppConfig when called with context on the server. useConfig() returns a narrowed Omit<AppConfig, 'serverExtension'> so client-side reads can’t reach server-only namespaces. This type narrowing happens automatically through the template’s configuration in src/types/config.ts.
The main configuration file is config.server.ts in your project root.
For detailed descriptions of all configuration options, see the Configuration Options Reference section below.
The configuration is organized into three main sections.
| Section | Purpose | Client Access |
|---|---|---|
metadata | Project name and slug for deployment | Server only |
runtime | SSR and deployment settings | Server only |
app | Application settings (commerce, features, UI) | Server and client |
The app section is automatically made available to client-side code. The metadata and runtime sections remain server-only.
The runtime.ssrOnly option accepts an array of glob patterns for files that must be available on the server but aren’t publicly accessible from the client. Use this option for server-side resources that aren’t exposed via public URLs.
Don’t import config.server.ts directly in your app code. The .server.ts suffix prevents the file from being bundled into client-side code (a React Router framework feature). Always use getConfig() or useConfig() to access configuration values. These APIs ensure that the configuration is properly loaded and available in the correct context.
The configuration system follows this flow:
- Types defined in
src/types/config.ts—AppConfigdefines all app fields,Config = BaseConfig<AppConfig> - Defaults defined in
config.server.ts— clean, with noprocess.envreferences - Environment variables with
PUBLIC__prefix are automatically merged bydefineConfig()— this happens at server startup - Final config is made available via:
getConfig(context)for server loaders/actions (returns fullAppConfig)getConfig()for client loaders (returns narrowed config without server-only namespaces)useConfig()for React components (returns narrowed config)window.__APP_CONFIG__for client code
The type narrowing is automatic because the template fills two augmentation slots in src/types/config.ts:
ClientAppConfig is Omit<AppConfig, ServerOnlyNamespace>, which keeps server-only configuration off the client at both runtime and type level.
:::note Multi-template caveat
If you build two templates in the same TypeScript program (rare), only one extends per slot wins. Fall back to explicit per-call generics: getConfig<MyAppConfig>(context) and useConfig<MyClientAppConfig>().
:::
Every variable the storefront recognizes is listed in this guide. Set the Required variables in .env. Everything else has a working default in config.server.ts.
Copy .env.default to .env and set these required B2C Commerce credentials:
| Variable | Purpose |
|---|---|
PUBLIC__app__commerce__api__clientId | SLAS client ID provisioned in B2C Commerce |
PUBLIC__app__commerce__api__organizationId | B2C Commerce organization or realm ID (for example, f_ecom_zzrf_001) |
PUBLIC__app__commerce__api__shortCode | SCAPI short code for your tenant (for example, kv7kzm78) |
| Variable | Default | Notes |
|---|---|---|
MRT_PROJECT | falls back to package.json#name | MRT project slug. Owned by the MRT/Fast Setup team. |
MRT_TARGET | — | MRT deploy target (for example, development, production). |
For more information, see Storefront Next CLI.
| Variable | Used by | Notes |
|---|---|---|
COMMERCE_API_SLAS_SECRET | src/lib/api-clients.server.ts, e2e/src/utils/scapi-helper.ts | Required only with private-client SCAPI auth. |
GUEST_ORDER_LOOKUP_COOKIE_SECRET | src/lib/order/session.server.ts | Signs the guest order lookup state cookie. Required when guestOrderLookup.enabled is true. Falls back to CLIENT_SECRET if unset; if neither is set, the feature fails closed with a CONFIGURATION_ERROR. |
MARKETING_CLOUD_CLIENT_ID, MARKETING_CLOUD_CLIENT_SECRET, MARKETING_CLOUD_AUTH_BASE_URL, MARKETING_CLOUD_REST_BASE_URL | Passwordless login email delivery | Required only when passwordlessLogin.mode = 'email' and you ship your own MC tenant. |
SCAPI_PROXY_HOST | vite-plugins/env-validation.ts, src/middlewares/app-config.server.ts | Internal-developer-only override (workspace proxy). |
Don’t add server-only secrets to config.server.ts and don’t give them a PUBLIC__ prefix. Read them directly from process.env in server-side code, such as loaders, actions, and middleware. Anything with the PUBLIC__ prefix is bundled into the browser-visible JavaScript.
For SLAS private client setup in B2C Commerce, see Authorization for Shopper APIs.
For a comprehensive list of optional environment variables with their defaults and effects, see the Optional Configuration Variables section below.
| Variable | Default | Effect |
|---|---|---|
HYBRID_PROXY_ENABLED | false | Enable Vite hybrid proxy |
HYBRID_ROUTING_RULES | — | Cloudflare-style routing expression for hybrid proxy |
HYBRID_PROXY_LOCALE | falls back to i18n.fallbackLng | Locale for SFRA path transformation |
SFCC_ORIGIN | — | SFCC origin URL (required when hybrid proxy enabled) |
SFCC_LOG_LEVEL | warn (prod) / info (dev) | Log verbosity (error | warn | info | debug) |
For feature-specific configuration, see the dedicated guides:
- Configure Multisite URLs — sites, locales, currencies, and URL patterns
- Set Up Hybrid Proxy Locally —
HYBRID_PROXY_*,SFCC_ORIGIN, routing rules - Passwordless Login for Storefront Next — passwordless login, OTP request, and reset password modes
- Shopper Context — qualifier-based personalization
- Shopping Agent for Storefront Next — embedded chat configuration
Override any configuration value by using environment variables with the PUBLIC__ prefix. With environment variables, you can customize settings per environment (development, staging, production) without modifying code.
The double underscore (__) lets you navigate nested config paths. Think of it as replacing the dot (.) in JavaScript object notation:
Values are automatically parsed to the correct type:
You can also set entire nested objects at once using JSON:
Case doesn’t matter: You can use any casing (lowercase, UPPERCASE, or MixedCase), and it will normalize to match your config.server.ts:
Paths must exist in config: You can only override paths that are already defined in config.server.ts. This prevents typos from silently failing:
More specific paths win: When paths overlap, deeper paths take precedence:
Depth limit: Paths are limited to 10 levels deep. For deeper structures, use JSON values instead:
PUBLIC__ prefix → Exposed to the browser (bundled into client JavaScript)
- ✅ Use for: Client IDs, site IDs, locales, feature flags, public API endpoints
- ❌ Never use for: API secrets, passwords, private keys, authentication tokens
No prefix → Server-only (never exposed to client)
- ✅ Use for: SLAS secrets, database credentials, private tokens
Read server-only secrets directly from process.env in your server code—never add them to config.
Environment variables are deep merged into defaults from config.server.ts:
The template defines its own AppConfig type with all the fields it needs — SCAPI credentials,
pages, features, and any custom domain fields. BaseConfig<AppConfig> wraps it with metadata
and runtime sections:
In React components:
In loaders/actions:
In config-meta.json:
- Add the name and key value to the config array
- This will cause the create-storefront script to ask for user input, using the value in
.env.defaultas default value
The PUBLIC__ prefix indicates values that are safe to expose to the browser. These values are bundled into client-side JavaScript.
Don’t use PUBLIC__ for secrets, API keys, passwords, or authentication tokens. These values are visible to anyone who uses your site.
Use PUBLIC__ For | Use Non-Prefixed For |
|---|---|
| Client IDs | API secrets |
| Site IDs | Private keys |
| Locales and currencies | Database credentials |
| Feature flags | Authentication tokens |
| Public API endpoints | SLAS secrets |
For server-only secrets, use environment variables without the PUBLIC__ prefix and read them directly from process.env.
Site-level settings (default locale, default currency, supported locales/currencies, cookie domain) come from the commerce.sites config array. You can override it with the environment variable PUBLIC__app__commerce__sites, set to a JSON array.
Multi-line JSON is supported in .env files:
Each site can specify its own cookies.domain to override the global app.cookies.domain setting. This is useful when different sites need different cookie scoping:
For the full schema, all properties, and troubleshooting, see Configure Multisite URLs.
Use getConfig() with the router context.
Use the useConfig() hook:
Client loaders don’t have access to router context. Call getConfig() without arguments.
Custom middleware can read the resolved app config from appConfigContext:
To add a new configuration value, follow these steps.
The template defines its own AppConfig type with all the fields it needs. Update src/types/config.ts:
Add the default in config.server.ts:
No code changes needed. Use the PUBLIC__ prefix:
In React components:
In loaders/actions:
If you want the create-storefront script to prompt for this value during app creation:
This causes the create-storefront script to ask for user input, using the value in .env.default as the default value.
Extensions add configuration without editing src/types/config.ts or config.server.ts.
Drop a config.ts in your extension folder that default-exports a plain object. The build prestep (pnpm dev / pnpm build) discovers it, merges it into config.app.extension.<camelCaseFolder>, and derives the type automatically.
Merchants override per environment with PUBLIC__app__extension__<key>__<setting>:
No core-file edits needed. Extension keys are set via PUBLIC__ env vars / .env; they are not added to config-meta.json, so they don’t appear in create-storefront prompts.
config.ts reaches the browser by design. For values an extension needs at runtime that must never be serialized into window.__APP_CONFIG__ (vendor-side SCAPI service overrides, retry budgets, internal-only endpoints), drop a server-config.ts next to config.ts:
Read the value from a server loader, action, or middleware:
The build prestep aggregates every extension’s server-config.ts into src/extensions/config/server.ts (auto-generated, do not edit) and merges it into config.app.serverExtension.<camelCaseFolder>.
Three structural guarantees keep the values off the client:
- The client config extractor (
src/lib/app-config-client.ts) stripsapp.serverExtensionbefore writingwindow.__APP_CONFIG__. - A Vite plugin (
vite-plugins/server-only-config-guard.ts) fails the build if any client chunk importssrc/extensions/config/server. useConfig()andgetConfig()’s client-facing overloads (no-arg andgetConfig(ctx | undefined)) are type-narrowed to omitapp.serverExtension, so reading.serverExtensionfrom any of them is a TypeScript error in client code. The servergetConfig(context)overload still returns the full shape.
There is no PUBLIC__ override path by design — the AST validator runs on server-config.ts, so a process.env.X read throws at discovery time. For true secrets that must vary per environment (SLAS secrets, Marketing Cloud credentials), keep using process.env from a server route — never put them in server-config.ts.
For more information, see the Extensions documentation in the template GitHub repo.
The template provides shared test utilities for components and hooks that depend on config:
mockBuildConfig— a fullConfigobject with realistic test valuesmockConfig— theappsection (i.e.,mockBuildConfig.app)ConfigWrapper— a ready-to-use wrapper component forrenderHook/rendercreateConfigWrapper(overrides?)— creates a wrapper with custom config (deep-merges nested overrides)
For tests that need all providers (config + currency + store locator), use AllProvidersWrapper from @/test-utils/context-provider.
Marketing Cloud is used for sending emails in features like passwordless login and password reset. The configuration is optional and only required if you’re using these features.
Important Security Notes:
- ❌ These variables do NOT have the
PUBLIC__prefix - they are server-only - ❌ They are NOT included in
config.server.tsor exposed to the client - ✅ Read them directly from
process.envin server-side code
When deploying to Managed Runtime (MRT), set your environment variables in the Runtime Admin.
- Log in to the Runtime Admin.
- Navigate to your project → Environment Variables.
- Add the required
PUBLIC__variables. - Add any server-only secrets without the
PUBLIC__prefix. - Deploy your app.
All the same rules apply: use the PUBLIC__ prefix for client-safe values, use the __ path syntax for nested config, and read server-only secrets directly from process.env.
MRT limits: Variable names max 512 characters, total PUBLIC__ values max 32 KB. Use JSON to consolidate related settings if needed.
Learn more about MRT environment variables →
Restart your dev server. Environment variables are loaded at startup.
- Verify the variable name starts with
PUBLIC__(double underscore after PUBLIC) - Check the
.envfile is in the project root - Ensure that the path exists in
config.server.ts—you can only override existing paths - For booleans, use string
"true"not baretrue
Update both src/types/config.ts (types) and config.server.ts (defaults) to match.
Copy .env.default to .env and set the required B2C Commerce credentials. See Required for the app to start.
The configuration system validates that environment variable paths exist in your config. If you see an error like "local" doesn't exist, check for typos. The system suggests similar valid paths when possible.
If a locale or translation is missing at runtime, verify:
- The locale is included in both
supportedLocalesfor the relevant site incommerce.sitesand ini18n.supportedLngs. - Translation files exist for the locale (for example,
public/locales/de-DE/translation.json). - The locale ID uses BCP 47 hyphen format (
en-US, noten_US). - Review
src/middlewares/i18next.tsto confirm the locale is listed in the server-side i18next configuration.
The following optional PUBLIC__* environment variables can override the defaults in config.server.ts:
| Variable | Default | Effect |
|---|---|---|
PUBLIC__app__commerce__api__proxy | /mobify/proxy/api | SCAPI proxy path |
PUBLIC__app__commerce__api__callback | /callback | OAuth callback path |
PUBLIC__app__commerce__api__privateKeyEnabled | false | Use private SLAS client |
PUBLIC__app__commerce__api__guestRefreshTokenExpirySeconds | from API response | Override guest refresh-token TTL |
PUBLIC__app__commerce__api__registeredRefreshTokenExpirySeconds | from API response | Override registered refresh-token TTL |
| Variable | Default | Effect |
|---|---|---|
PUBLIC__app__hybrid__enabled | false | Hybrid PWA mode |
PUBLIC__app__auth__otpLength | 6 | OTP length (6 or 8) |
PUBLIC__app__features__passwordlessLogin__mode | email | email | callback |
PUBLIC__app__features__otpRequest__mode | email | email | callback |
PUBLIC__app__features__resetPassword__mode | email | email | callback |
PUBLIC__app__features__mrtBasedPageDesignerResolution | false | Resolve PD pages via MRT Data Store |
PUBLIC__app__features__socialLogin__enabled | true | Apple/Google login button |
PUBLIC__app__features__socialLogin__callbackUri | /social-callback | Social login callback path |
PUBLIC__app__features__socialLogin__providers | ["Apple","Google"] | Provider list |
PUBLIC__app__features__shopperContext__enabled | false | Shopper context API |
PUBLIC__app__features__googleCloudAPI__apiKey | — | Google Address Autocomplete |
| Variable | Default | Effect |
|---|---|---|
PUBLIC__app__defaultSiteId | (single-site default) | Override default site |
PUBLIC__app__commerce__sites | (single-site default) | Multi-site JSON config |
PUBLIC__app__cookies__domain | host-only | Global default cookie domain for all cookies (e.g. .example.com); per-site commerce.sites[].cookies.domain overrides it |
| Variable | Default | Effect |
|---|---|---|
PUBLIC__app__security__turnstile__enabled | false | Turnstile bot protection |
PUBLIC__app__security__turnstile__sites | — | Turnstile per-site configuration |
Engagement adapter settings (Einstein, Data 360, Active Data) cannot be overridden with PUBLIC__ environment variables. To change adapter settings, update config.server.ts directly. This restriction exists because engagement configuration affects build-time validation for analytics instrumentation.
Each block in this section is a copy-pasteable env snippet. Drop it into your .env and uncomment to enable the feature.
Single-site is the default. To enable multiple sites, define them as a JSON array:
See Configure Multisite URLs for site-context routing details.
For MRT environment variables, convert to a single line and remove the surrounding quotes. Multi-line format works only in local .env files.
By default, Storefront Next syncs site, locale, and currency data from Business Manager per request via the Data Access Layer (DAL). This means you don’t need to edit config.server.ts or redeploy to pick up new sites or locale changes — Business Manager is the source of truth.
To disable DAL-sourced sites and fall back to the static commerce.sites array in config.server.ts:
When sitesFromDal is enabled (the default) and the DAL is unavailable, the storefront automatically falls back to the static commerce.sites configuration. See Configure Multisite URLs for DAL-sourced site fallback rules.
Silent HTTP proxying with cookie rewriting for a unified storefront experience. Local-dev only — production routing should use Cloudflare eCDN. Requires SFCC_ORIGIN and PUBLIC__app__defaultSiteId.
See Set Up Hybrid Proxy Locally.
When using mode=email, also set the server-only Marketing Cloud secrets (see Marketing Cloud Configuration). See Passwordless Login for Storefront Next.
Cloudflare Turnstile is disabled by default. The test site key below always passes — production sites must set their own keys via MRT env vars.
Set as a single JSON string. Required fields: enabled, commerceClientScriptSourceUrl, scrt2Url, salesforceOrgId, esDeveloperName. See src/components/cimulate/README.md for setup.
See Shopping Agent for Storefront Next for environment-specific setup.
Sets the default Domain on every cookie the storefront writes — auth/session and site-context (site_id, locale, currency). A per-site commerce.sites[].cookies.domain overrides it for that site. Unset = host-only scoping; setting a domain is opt-in.
See the Cookie Domain Configuration guide for the full guide, including the matching Business Manager setting and rollout guidance.
If unset, the storefront uses the expiry returned by SCAPI.
Shared with the SDK logger (storefront-next-dev) for unified control.
Already in .env.default — listed here for completeness.
Read directly from process.env in server-side code (loaders, actions, middleware). Never prefix with PUBLIC__.
Complex values can be encoded as JSON strings—the merge mechanism parses any value that looks like JSON.
This section provides detailed documentation for all configuration options available in config.server.ts. For the complete reference with all options, descriptions, defaults, and examples, see the Configuration Options Reference in the template GitHub repo.
Key configuration categories include:
- metadata - Project identification and metadata
- runtime - Runtime deployment settings for MRT
- app - Application-specific configuration
- pages - Page-specific settings (navigation, cart, search, home)
- commerce - B2C Commerce API details and site configuration
- hybrid - Hybrid mode configuration
- auth - Authentication configuration (OTP length, etc.)
- security - Security headers and Turnstile configuration
- features - Feature flags (passwordless login, social login, etc.)
- i18n - Internationalization settings
- global - Global UI and component settings (branding, badges, recommendations)
- links - Link hints for browser resource loading
- images - Dynamic Imaging Service settings
- search - Search-specific settings
- performance - Performance optimization settings
- engagement - Analytics and engagement adapters
- commerceAgent - Shopper Agent (Embedded Messaging / Agentforce)
- development - Development tools and features
The following options are commonly needed but not covered elsewhere in this guide.
| Option | Type | Default | Notes |
|---|---|---|---|
features.passkey.enabled | boolean | false | Requires sfcc.pwdless_login scope on your SLAS client. |
features.passkey.mode | 'email' | 'callback' | email | SLAS does not support 'sms' mode for passkey authorization. |
features.passkey.callbackUri | string | — | Callback URI for passkey authorization redirect flow. |
| Option | Type | Default | Notes |
|---|---|---|---|
features.guestCheckout | boolean | true | Allow unauthenticated checkout |
| Option | Type | Default | Notes |
|---|---|---|---|
features.socialShare.enabled | boolean | true | Enable product social sharing |
features.socialShare.providers | string[] | ['Twitter', 'Facebook', 'LinkedIn', 'Email'] | List of share provider names |
| Option | Type | Default | Notes |
|---|---|---|---|
features.shopperContext.dwsourcecodeCookieSuffix | string | — | Cookie suffix for campaign attribution via shopper context source code |
The PLP (Product Listing Page) pagination is configured in src/lib/config.ui.ts rather than config.server.ts and cannot be overridden via PUBLIC__ env vars.
| Option | Type | Default | Notes |
|---|---|---|---|
uiConfig.pages.category.pagination.mode | 'load-more' | 'traditional' | 'load-more' | Pagination UI mode |
uiConfig.pages.category.pagination.batchSize | number | 25 | Products per page/load-more batch |
uiConfig.pages.category.pagination.mobileBatchSize | number | 25 | Products per batch on mobile |
uiConfig.pages.category.pagination.maxProducts | number | 200 | Maximum total products rendered |
Engagement adapter settings must be configured directly in config.server.ts. They cannot be overridden via PUBLIC__ environment variables because engagement configuration affects build-time validation for analytics instrumentation.
| Option | Notes |
|---|---|
engagement.adapters[].consentCategory | Consent category required before firing events |
engagement.adapters[].eventToggles | Per-event on/off switches |
engagement.adapters[].webStoreId | Web store ID required for Data 360 adapter |
See Also