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.

SectionPurposeClient Access
metadataProject name and slug for deploymentServer only
runtimeSSR and deployment settingsServer only
appApplication 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:

  1. Types defined in src/types/config.tsAppConfig defines all app fields, Config = BaseConfig<AppConfig>
  2. Defaults defined in config.server.ts — clean, with no process.env references
  3. Environment variables with PUBLIC__ prefix are automatically merged by defineConfig() — this happens at server startup
  4. Final config is made available via:
    • getConfig(context) for server loaders/actions (returns full AppConfig)
    • 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:

VariablePurpose
PUBLIC__app__commerce__api__clientIdSLAS client ID provisioned in B2C Commerce
PUBLIC__app__commerce__api__organizationIdB2C Commerce organization or realm ID (for example, f_ecom_zzrf_001)
PUBLIC__app__commerce__api__shortCodeSCAPI short code for your tenant (for example, kv7kzm78)
VariableDefaultNotes
MRT_PROJECTfalls back to package.json#nameMRT project slug. Owned by the MRT/Fast Setup team.
MRT_TARGETMRT deploy target (for example, development, production).

For more information, see Storefront Next CLI.

VariableUsed byNotes
COMMERCE_API_SLAS_SECRETsrc/lib/api-clients.server.ts, e2e/src/utils/scapi-helper.tsRequired only with private-client SCAPI auth.
GUEST_ORDER_LOOKUP_COOKIE_SECRETsrc/lib/order/session.server.tsSigns 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_URLPasswordless login email deliveryRequired only when passwordlessLogin.mode = 'email' and you ship your own MC tenant.
SCAPI_PROXY_HOSTvite-plugins/env-validation.ts, src/middlewares/app-config.server.tsInternal-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.

VariableDefaultEffect
HYBRID_PROXY_ENABLEDfalseEnable Vite hybrid proxy
HYBRID_ROUTING_RULESCloudflare-style routing expression for hybrid proxy
HYBRID_PROXY_LOCALEfalls back to i18n.fallbackLngLocale for SFRA path transformation
SFCC_ORIGINSFCC origin URL (required when hybrid proxy enabled)
SFCC_LOG_LEVELwarn (prod) / info (dev)Log verbosity (error | warn | info | debug)

For feature-specific configuration, see the dedicated guides:

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.default as 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__ ForUse Non-Prefixed For
Client IDsAPI secrets
Site IDsPrivate keys
Locales and currenciesDatabase credentials
Feature flagsAuthentication tokens
Public API endpointsSLAS 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:

  1. The client config extractor (src/lib/app-config-client.ts) strips app.serverExtension before writing window.__APP_CONFIG__.
  2. A Vite plugin (vite-plugins/server-only-config-guard.ts) fails the build if any client chunk imports src/extensions/config/server.
  3. useConfig() and getConfig()’s client-facing overloads (no-arg and getConfig(ctx | undefined)) are type-narrowed to omit app.serverExtension, so reading .serverExtension from any of them is a TypeScript error in client code. The server getConfig(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 full Config object with realistic test values
  • mockConfig — the app section (i.e., mockBuildConfig.app)
  • ConfigWrapper — a ready-to-use wrapper component for renderHook / render
  • createConfigWrapper(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.ts or exposed to the client
  • ✅ Read them directly from process.env in server-side code

When deploying to Managed Runtime (MRT), set your environment variables in the Runtime Admin.

  1. Log in to the Runtime Admin.
  2. Navigate to your project → Environment Variables.
  3. Add the required PUBLIC__ variables.
  4. Add any server-only secrets without the PUBLIC__ prefix.
  5. 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 .env file 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 bare true

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:

  1. The locale is included in both supportedLocales for the relevant site in commerce.sites and in i18n.supportedLngs.
  2. Translation files exist for the locale (for example, public/locales/de-DE/translation.json).
  3. The locale ID uses BCP 47 hyphen format (en-US, not en_US).
  4. Review src/middlewares/i18next.ts to 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:

VariableDefaultEffect
PUBLIC__app__commerce__api__proxy/mobify/proxy/apiSCAPI proxy path
PUBLIC__app__commerce__api__callback/callbackOAuth callback path
PUBLIC__app__commerce__api__privateKeyEnabledfalseUse private SLAS client
PUBLIC__app__commerce__api__guestRefreshTokenExpirySecondsfrom API responseOverride guest refresh-token TTL
PUBLIC__app__commerce__api__registeredRefreshTokenExpirySecondsfrom API responseOverride registered refresh-token TTL
VariableDefaultEffect
PUBLIC__app__hybrid__enabledfalseHybrid PWA mode
PUBLIC__app__auth__otpLength6OTP length (6 or 8)
PUBLIC__app__features__passwordlessLogin__modeemailemail | callback
PUBLIC__app__features__otpRequest__modeemailemail | callback
PUBLIC__app__features__resetPassword__modeemailemail | callback
PUBLIC__app__features__mrtBasedPageDesignerResolutionfalseResolve PD pages via MRT Data Store
PUBLIC__app__features__socialLogin__enabledtrueApple/Google login button
PUBLIC__app__features__socialLogin__callbackUri/social-callbackSocial login callback path
PUBLIC__app__features__socialLogin__providers["Apple","Google"]Provider list
PUBLIC__app__features__shopperContext__enabledfalseShopper context API
PUBLIC__app__features__googleCloudAPI__apiKeyGoogle Address Autocomplete
VariableDefaultEffect
PUBLIC__app__defaultSiteId(single-site default)Override default site
PUBLIC__app__commerce__sites(single-site default)Multi-site JSON config
PUBLIC__app__cookies__domainhost-onlyGlobal default cookie domain for all cookies (e.g. .example.com); per-site commerce.sites[].cookies.domain overrides it
VariableDefaultEffect
PUBLIC__app__security__turnstile__enabledfalseTurnstile bot protection
PUBLIC__app__security__turnstile__sitesTurnstile 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.

OptionTypeDefaultNotes
features.passkey.enabledbooleanfalseRequires sfcc.pwdless_login scope on your SLAS client.
features.passkey.mode'email' | 'callback'emailSLAS does not support 'sms' mode for passkey authorization.
features.passkey.callbackUristringCallback URI for passkey authorization redirect flow.
OptionTypeDefaultNotes
features.guestCheckoutbooleantrueAllow unauthenticated checkout
OptionTypeDefaultNotes
features.socialShare.enabledbooleantrueEnable product social sharing
features.socialShare.providersstring[]['Twitter', 'Facebook', 'LinkedIn', 'Email']List of share provider names
OptionTypeDefaultNotes
features.shopperContext.dwsourcecodeCookieSuffixstringCookie 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.

OptionTypeDefaultNotes
uiConfig.pages.category.pagination.mode'load-more' | 'traditional''load-more'Pagination UI mode
uiConfig.pages.category.pagination.batchSizenumber25Products per page/load-more batch
uiConfig.pages.category.pagination.mobileBatchSizenumber25Products per batch on mobile
uiConfig.pages.category.pagination.maxProductsnumber200Maximum 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.

OptionNotes
engagement.adapters[].consentCategoryConsent category required before firing events
engagement.adapters[].eventTogglesPer-event on/off switches
engagement.adapters[].webStoreIdWeb store ID required for Data 360 adapter

See Also