Skip to main content

Multi-store support

Evolve supports multiple stores, locales, and currencies from a single backend deployment. Every request carries a StoreContext that tells each service which store, language, and currency to use. This enables scenarios like running separate country stores, localized storefronts, or B2B stores with different assortments from the same infrastructure.

StoreContext

The StoreContext has three properties:

  • storeKey: identifies the store
  • locale: the language for the request (e.g. nl-NL, en-GB)
  • currency: the currency for prices and carts (e.g. EUR, GBP)

These three values flow through the entire request lifecycle, from the frontend through the gateway to every domain service. All services receive the same StoreContext, regardless of which SAAS backend they integrate with.

How it flows

Frontend

The frontend sets the three X-StoreContext-* headers on every GraphQL request, both server-side and client-side:

const fetcher = initClientFetcher("/graphql", {
defaultHeaders: {
"X-StoreContext-StoreKey": storeConfig.storeKey,
"X-StoreContext-Locale": locale,
"X-StoreContext-Currency": storeConfig.currency,
},
});

The store key and currency come from the store configuration (selected per deployment via the STORE_KEY environment variable). The locale comes from the URL path via next-intl routing (e.g. /nl-NL/products/...).

Gateway

The GraphQL gateway reads the headers from the incoming request and forwards them to every subgraph service. This happens in the gateway's custom data source, which intercepts outgoing requests:

async willSendRequest(options) {
const storeContext = options.context.storeContext();

options.request.http?.headers.set(
"X-StoreContext-StoreKey", storeContext.storeKey,
);
options.request.http?.headers.set(
"X-StoreContext-Locale", storeContext.locale,
);
options.request.http?.headers.set(
"X-StoreContext-Currency", storeContext.currency,
);
}

Domain services

Each service reads the headers from the incoming request and constructs a StoreContext object that resolvers can access via context.storeContext:

import { readStoreContextFromRequest } from "@evolve-framework/core";

const storeContext = readStoreContextFromRequest(request);

All three headers are required. If any is missing, the request fails with a GraphQL error.

How services use StoreContext

Every service uses StoreContext, but what each property means depends on the integration.

Commerce services

Commerce services use all three properties to scope operations to the correct store:

  • storeKey: scopes API calls to a specific store. Carts, orders, customers, and shopping lists are all queried within the store context.
  • locale: determines which translations are returned for product names, descriptions, and category names.
  • currency: selects the correct prices. Together with the country (derived from the locale), it drives price selection, tax calculation, and cart operations.
  • Product projections: the store context provides parameters for product queries that filter results by store assortment, locale, and pricing.

CMS services

CMS services (Contentful, Storyblok) use StoreContext primarily for content localization:

  • locale: determines which language version of content to fetch from the CMS. Contentful uses it to filter entries by locale, Storyblok extracts the language code for its API.
  • storeKey: used in cache keys to keep content separated per store when different stores serve different content.
  • currency: included in cache keys to ensure different currency contexts don't share cached data.

Cache scoping

All services use StoreContext to generate cache key prefixes. This ensures that cached data from one store, locale, or currency does not leak into another:

public get cacheKeyPrefix(): string {
return `${this.storeKey}-${this.locale}-${this.currency}`;
}

Store configuration

Each frontend deployment is configured for a specific store through the STORE_KEY environment variable. This selects a store configuration that defines:

  • Store key: identifies the store across all backend services
  • Default locale and available locales: which languages the store supports
  • Currency: the store's currency
  • Country: used for tax and shipping calculations
  • Available payment methods: which payment providers are enabled
  • Checkout configuration: checkout-specific settings
  • Distribution channel: for store-specific product assortment and pricing

To run multiple stores, deploy multiple frontend instances with different STORE_KEY values. They all share the same backend.

B2B store override

In B2B scenarios, a customer may belong to a business unit that is assigned to a specific store. When this is the case, the ClientContext overrides the store key from the request with the business unit's store key:

get storeKey(): string {
if (!this.isAuthenticated()) {
return this.storeContext.storeKey;
}
return this._info?.businessContext?.storeKey
?? this.storeContext.storeKey;
}

This allows B2B customers to see store-specific assortments and pricing based on their business unit, even when accessing the platform through a shared storefront. See B2B & B2C architecture for more details on business units and the BusinessContext.