Configuration Deep Dive
EverShop uses a powerful and flexible configuration system based on the node-config package. This guide will walk you through how to set up and manage your store's configuration effectively.
While many settings (like payment and shipping methods) are managed through the admin panel, the file-based configuration gives you low-level control over your application's core settings.
Configuration Layers
EverShop loads configuration in a layered hierarchy. The node-config package starts by loading config/default.json and then deep-merges subsequent configuration files on top of it. This means settings from more specific layers will override the settings from more general ones, while any unspecified settings will be inherited from the base file.
This allows you to define a complete base configuration and then selectively override just the parts that change for different environments.
The loading order is as follows:
config/default.json- The base configuration for your application. This file should be in your version control.config/[NODE_ENV].json- Environment-specific configuration (e.g.,config/production.jsonorconfig/development.json).config/local.json- For local overrides on your development machine. This file should not be committed to version control.
EverShop does not ship a config/custom-environment-variables.json file, so node-config does not map arbitrary environment variables onto configuration keys. Setting SHOP_TIMEZONE in your shell will not change shop.timezone. Environment variables are read directly by the code that needs them (see Environment Variables below). If you want env-var mapping for your own keys, create config/custom-environment-variables.json yourself — that is a stock node-config feature.
By default, EverShop does not create the config/ directory. You need to create it manually to add your custom configuration files.
Many settings that used to live in config/default.json are now admin settings stored in the database (the setting table) and edited under Settings in the admin panel. Where both exist, the database value wins and the config value is only a fallback. See Store Settings.
1. Base Configuration (default.json)
This file should contain all the default settings for your application.
{
"shop": {
"language": "en",
"timezone": "UTC"
},
"system": {
"theme": "default"
}
}
2. Environment-Specific Configuration
You can create separate files for each deployment environment. For example, to override the theme for production, you would create config/production.json:
{
"system": {
"theme": "my-production-theme"
}
}
When you run your application with NODE_ENV=production, the theme will be my-production-theme, but the language will still be en from default.json.
3. Local Overrides (local.json)
For settings specific to your local machine, like database credentials, use config/local.json. This file is perfect for sensitive information that should not be in Git.
{
"db": {
"user": "local_user",
"password": "local_password"
}
}
4. Environment Variables
Environment variables are read directly by the code that needs them — they are not merged into the node-config tree (see the warning above). The variables EverShop reads are:
| Variable | Purpose |
|---|---|
DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME | PostgreSQL connection. See Database. |
DB_SSLMODE, DB_SSLROOTCERT, DB_SSLCERT, DB_SSLKEY | PostgreSQL TLS options. |
PORT | HTTP port the server listens on. |
NODE_ENV | Selects config/[NODE_ENV].json and switches development/production behavior. |
EVERSHOP_HOME_URL | Overrides shop.homeUrl with the highest precedence. Must be an absolute http/https URL — boot fails fast otherwise. Precedence: EVERSHOP_HOME_URL → shop.homeUrl → http://localhost on the configured PORT. |
TRUST_PROXY_HOPS | Number of reverse-proxy hops to trust (default 1). Drives Express trust proxy, so request.ip — and therefore the per-IP rate limiter — sees the real client address. Set it to the number of proxies in front of the app. |
IMAGE_ALLOWED_HOSTS | Comma-separated allowlist of hosts the /images optimizer may fetch from. Cloud file-storage hosts are allowed automatically. |
Accessing Configuration in Code
EverShop provides two ways to access configuration values.
Using node-config
You can use the config package directly. This is useful in modules and extensions.
import config from 'config';
// Throws an error if the key is not found
const timezone = config.get('shop.timezone');
// Check if a key exists
if (config.has('shop.language')) {
const language = config.get('shop.language');
}
Using getConfig() Utility
EverShop includes a utility function that allows you to provide a default value if a configuration key doesn't exist.
import { getConfig } from '@evershop/evershop/lib/util/getConfig';
// Returns 'en' if 'shop.language' is not defined
const language = getConfig('shop.language', 'en');
Setting Module Defaults (For Extensions)
Extensions can register their own default configuration values using config.util.setModuleDefaults() in their bootstrap.ts. This deep-merges the extension's defaults with the existing configuration without overwriting other modules' settings:
import config from 'config';
export default () => {
config.util.setModuleDefaults('myExtension', {
apiKey: '',
maxRetries: 3,
timeout: 5000
});
};
The values are then accessible via getConfig('myExtension.apiKey') and can be overridden by the store owner in their config/default.json.
This is also how core modules register their defaults (e.g., catalog image sizes, OMS order statuses, checkout settings).
Core Configuration Reference
Here is a reference for the most important configuration sections.
Shop Configuration
This section contains general information about your shop.
{
"shop": {
"language": "en",
"timezone": "America/New_York",
"homeUrl": "http://localhost:3000"
}
}
shop.currency, shop.weightUnit and shop.dimensionUnit were removed from the typed getConfig surface. They are admin settings now — storeCurrency, weightUnit and dimensionUnit on Settings → Store — read through getStoreCurrency(), getWeightUnit() and getDimensionUnit() from @evershop/evershop/setting/services. The old config keys are still honoured as untyped legacy fallbacks when the corresponding setting row is empty, but they no longer autocomplete or typecheck with getConfig(). The store name moved the same way (getStoreName()); there is no typed shop.name.
shop.timezone is the operational timezone: it sets the PostgreSQL session timezone when the pool opens a connection, which is why it has to stay in a file (there is no database to read a setting from at that point). The display timezone is the separate storeTimeZone admin setting, read via getStoreTimezone(); shop.timezone is only its fallback. shop.language stays typed for the same reason — it is the locale system's synchronous fallback.
System Configuration
This section controls core system settings, such as the active theme, file storage, uploads, and enabled extensions.
{
"system": {
"file_storage": "local",
"upload_allowed_mime_types": [
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/avif"
],
"upload_max_file_size": 10485760,
"upload_max_file_size_per_type": {
"image/png": 5242880,
"application/pdf": 20971520
},
"theme": "mytheme",
"extensions": [
{
"name": "my-custom-extension",
"resolve": "extensions/my-custom-extension",
"enabled": true
}
],
"session": {
"maxAge": 86400000,
"resave": false,
"saveUninitialized": false,
"cookieSecret": "keyboard cat",
"cookieName": "sid"
}
}
}
| Key | Type | Description |
|---|---|---|
system.file_storage | enum local | s3 | azure | gcs | Which storage driver serves uploads. Defaults to local. |
system.upload_allowed_mime_types | string[] | MIME types accepted by the file upload endpoint. |
system.upload_max_file_size | number | Global upload size ceiling in bytes. |
system.upload_max_file_size_per_type | object | Per-MIME-type overrides, in bytes. |
system.admin_collection_size | number | Default page size for admin grids. |
Each non-local driver has its own block: system.s3 (region, bucket, accessKeyId, secretAccessKey, endpoint, forcePathStyle, baseUrl), system.azure (connectionString, containerName, containerAccess, baseUrl) and system.gcs (bucket, serviceAccountKey, baseUrl).
{
"system": {
"file_storage": "s3",
"s3": {
"region": "us-east-1",
"bucket": "my-store-media",
"baseUrl": "https://cdn.example.com"
}
}
}
The s3 / azure / gcs blocks are absent at runtime unless you configure them. Always read individual keys (getConfig('system.s3.bucket')), never the whole object.
See File Storage for the full driver setup, credentials, and the /images allowlist.
Catalog Configuration
Product image sizes, stock visibility, and collection page size are admin settings now, edited at Settings → Catalog (/admin/setting/catalog) and read via getShowOutOfStockProducts(), getCollectionPageSize() and getProductImageDimensions(). The catalog.* config keys below remain as fallbacks when the corresponding setting row is empty.
{
"catalog": {
"product": {
"image": {
"width": 1200,
"height": 1200
}
},
"showOutOfStockProduct": false,
"collectionPageSize": 20
}
}
See Store Settings for the admin surface.
Checkout Configuration
Configure settings related to the checkout process.
{
"checkout": {
"showShippingNote": true,
"allowGuestCheckout": true
}
}
allowGuestCheckout is also an admin toggle. It is read through getAllowGuestCheckout() from @evershop/evershop/checkout/services, which prefers the allowGuestCheckout setting row and falls back to this config value (default true). When it resolves to false, the checkout page redirects anonymous shoppers to login and the order validator rejects guest orders.
Pricing Configuration
Rounding behavior and precision for pricing and tax calculations are admin settings now, edited at Settings → Tax (/admin/setting/tax) and read via the checkout / tax settings services. The pricing.* config keys remain as fallbacks.
{
"pricing": {
"rounding": "round",
"precision": 2,
"tax": {
"rounding": "round",
"precision": 2,
"round_level": "total",
"price_including_tax": true
}
}
}
See Store Settings for the admin surface.
Theme Configuration
Configure theme-specific settings like your copyright notice and custom scripts or styles.
{
"themeConfig": {
"headTags": {
"links": [
{ "rel": "icon", "href": "/favicon.ico", "type": "image/x-icon" }
],
"metas": [
{ "name": "viewport", "content": "width=device-width, initial-scale=1" }
],
"scripts": [
{ "src": "/custom.js", "async": true }
],
"bases": [
{ "href": "/" }
]
},
"copyRight": "© 2025 My Shop. All Rights Reserved."
}
}
Breaking change: themeConfig.logo was removed. The typed getConfig surface keeps only themeConfig.headTags and themeConfig.copyRight, and the ThemeConfig GraphQL type exposes only those two fields. There is no fallback — a logo block left in config/default.json is simply ignored. The storefront logo now comes from admin settings (logo, logoWidth, logoHeight on Settings → Store), exposed through the BrandingSetting GraphQL type.
themeConfig.copyRight is itself only a fallback: the seeded shop / custom / copyright metafield overrides it when set. See Metafields.
Order Management (OMS) Configuration
The oms section contains all configurations related to order processing statuses.
Order Status
Define the main order statuses, their appearance, and transition logic.
{
"oms": {
"order": {
"status": {
"new": {
"name": "New",
"badge": "default",
"isDefault": true,
"next": ["processing", "canceled"]
},
"processing": {
"name": "Processing",
"badge": "default",
"next": ["completed", "canceled"]
},
"completed": {
"name": "Completed",
"badge": "success",
"next": ["closed"]
},
"canceled": {
"name": "Canceled",
"badge": "destructive",
"next": []
},
"closed": {
"name": "Closed",
"badge": "outline",
"next": []
}
}
}
}
}
Payment Status
Define the possible statuses for order payments.
{
"oms": {
"order": {
"paymentStatus": {
"pending": {
"name": "Pending",
"badge": "default",
"isDefault": true,
"isCancelable": true
},
"paid": {
"name": "Paid",
"badge": "success"
},
"canceled": {
"name": "Canceled",
"badge": "destructive"
}
}
}
}
}
Shipment Status
Define the possible statuses for an individual shipment. Every entry requires name, badge and phase; the schema forbids any other property, so progress, isDefault and isCancelable are rejected here. phase must be one of pending, shipped, delivered, canceled — it is what drives the transition rules and the order-level rollup.
{
"oms": {
"order": {
"shipmentStatus": {
"shipped": {
"name": "Shipped",
"badge": "warning",
"phase": "shipped"
},
"delivered": {
"name": "Delivered",
"badge": "success",
"phase": "delivered"
},
"canceled": {
"name": "Canceled",
"badge": "destructive",
"phase": "canceled"
}
}
}
}
}
There is no default pending shipment status: a shipment row only exists because something was actually shipped. pending is an order-level rollup value ("no items shipped yet"), not a per-shipment status. See Multi-Shipment and Fulfillment.
Shipment Rollup
order.shipment_status is a derived rollup recomputed after every shipment write. oms.order.shipmentRollup maps an item-count predicate (all: / any: over a phase) to the rollup value; the resolver walks the rules in priority order and returns the first match.
{
"oms": {
"order": {
"shipmentRollup": {
"all:delivered": "delivered",
"any:delivered": "partially_delivered",
"all:shipped": "shipped",
"any:shipped": "partially_shipped",
"all:canceled": "canceled",
"any:canceled": "partially_canceled",
"all:pending": "pending"
},
"shipmentRollupCancelable": {
"pending": true,
"partially_shipped": true,
"shipped": true,
"partially_delivered": true,
"delivered": false,
"partially_canceled": true,
"canceled": true
},
"reStockAfterCancellation": true
}
}
}
| Key | Description |
|---|---|
oms.order.shipmentRollup | Predicate → rollup value. Keys match ^(all|any):(pending|shipped|delivered|canceled)$; values are one of pending, partially_shipped, shipped, partially_delivered, delivered, partially_canceled, canceled. Also overridable with addProcessor('shipmentRollup', …). |
oms.order.shipmentRollupCancelable | Per-rollup-value cancelability. Replaces the old per-status isCancelable flag — cancelability is decided by the order's rollup, not by an individual shipment's status. |
oms.order.reStockAfterCancellation | Whether canceling an order returns its items to inventory. Defaults to true. |
The oms.carriers configuration block no longer exists. Carriers are not config and not a database table — they live in an in-memory registry populated by registerCarrier() from a module's bootstrap.ts. See Carrier Development.
See Also
- Store Settings — The database-backed settings that now take precedence over most of these config keys
- File Storage — S3 / Azure / GCS drivers and upload limits
- Carrier Development — Registering carriers with
registerCarrier() - Multi-Shipment and Fulfillment — Shipment statuses, phases, and the order rollup
- Order Status Management — Detailed guide to OMS status configuration and programmatic registration
- Payment Method Development — Payment-specific configuration
- Database — Database connection settings
- Styling — Theme configuration for styles and Tailwind
Support us
EverShop is an open-source project that relies on community support. If you find our project useful, please consider sponsoring us.