1. Project Overview & Quickstart (evershopcommerce/translation)
Technical Documentation: evershopcommerce/translation
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
translation
Agent Skills & Rules
- Discovered 21 agent rule(s) and prompt configurations.
--- METRICS ---
- Files Extracted: 1
- Estimated Token Budget: ~25 tokens
- Canonical Reference: https://codewiki.google/github.com/evershopcommerce/translation
2. Official Technical Reference & Guides (evershopcommerce/docs)
File: docs/development/deployment/deploy-evershop-to-aws.md
sidebar_position: 10
keywords:
- AWS deployment
- EC2 hosting
- RDS PostgreSQL
- Node.js production deployment
sidebar_label: Deploy to AWS
title: Deploy EverShop to AWS
Deploy EverShop to AWS
This comprehensive guide walks you through the process of deploying EverShop to Amazon Web Services (AWS) using EC2 for application hosting and RDS for PostgreSQL database management. This approach provides a scalable, reliable production environment for your ecommerce platform.
:::tip
Before you go live, run down the Production Checklist — it lists every environment variable EverShop reads at boot, the built-in per-IP rate limits, and what the build and start sequence actually does.
:::
Prerequisites
Before beginning, ensure you have:
- An active AWS account (this guide uses free tier resources where possible)
- Basic familiarity with AWS console navigation
- SSH client for connecting to your EC2 instance
EC2 Instance Setup
Creating an EC2 Instance
First, we need to provision a virtual server to host the EverShop application:
:::info
If you're new to AWS, follow the official EC2 getting started guide for detailed instructions on creating your first instance.
:::
For optimal compatibility with EverShop, we recommend:
- Operating System: Ubuntu 20.04 LTS
- Instance Type: t2.micro for testing (free tier eligible) or t3.small/medium for production use
- Storage: At least 20GB of EBS storage
- Security Group: Configure to allow HTTP (port 80), HTTPS (port 443), and SSH (port 22)
Installing Required Software
After creating and connecting to your EC2 instance via SSH, install the necessary software components:
1. Install Node.js and NPM
EverShop requires Node.js version 20.9 or higher and NPM version 9.x or higher:
sudo apt update
sudo apt install -y curl
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejsVerify the installation:
node -v # Should show v20.x.x or higher
npm -v # Should show 9.x.x or higher2. Install PM2 Process Manager
PM2 will ensure your EverShop application runs continuously and restarts automatically if the server reboots:
sudo npm install -g pm2Verify the installation:
pm2 -vConfigure PM2 to start automatically on server boot:
pm2 startup systemdFollow any additional instructions provided by the command.
3. Install and Configure Nginx
Nginx will act as a reverse proxy, directing traffic to your Node.js application:
sudo apt install -y nginxCreate a configuration file for your EverShop application:
sudo nano /etc/nginx/sites-available/evershop.confAdd the following configuration, replacing yourdomain.com with your actual domain name (or use the EC2 public DNS temporarily):
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Handle large file uploads
client_max_body_size 50M;
}Enable the configuration and disable the default site:
sudo ln -s /etc/nginx/sites-available/evershop.conf /etc/nginx/sites-enabled/
sudo unlink /etc/nginx/sites-enabled/defaultTest the configuration and restart Nginx:
sudo nginx -t
sudo systemctl restart nginxDatabase Setup
Creating an RDS PostgreSQL Instance
EverShop requires PostgreSQL 13 or higher. Let's create a managed database instance:
:::info
For detailed instructions on creating an RDS instance, refer to the AWS RDS documentation.
:::
- Navigate to the RDS dashboard in the AWS console
- Click "Create database"
- Select "Standard create" and choose PostgreSQL
- Select version 13 or higher
- Choose the appropriate instance size (db.t3.micro is free tier eligible)
- Configure storage, connectivity, and security settings
- Set your master username and password
- Complete the creation process
Configuring Security Groups
For your EC2 instance to access the RDS database:
- Identify the security group of your RDS instance
- Edit its inbound rules to allow PostgreSQL traffic (port 5432) from your EC2 instance's security group or IP address
:::info
For detailed instructions on configuring security groups, see the AWS RDS VPC documentation.
:::
Creating the Database and User
Connect to your RDS instance using a PostgreSQL client like pgAdmin or psql:
psql -h your-rds-endpoint.rds.amazonaws.com -U master_username -d postgresCreate a dedicated database and user for EverShop:
CREATE DATABASE evershop;
CREATE USER evershop WITH ENCRYPTED PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE evershop TO evershop;Source Code Deployment
Setting Up Your Repository
For streamlined deployment:
- Fork the EverShop project template to your GitHub account
- Add any custom themes or extensions to your forked repository
Creating a Deployment Script
Create a deployment script on your EC2 instance to automate the process:
sudo mkdir -p /var/www/evershop
cd /var/www/evershop
sudo nano deploy.shAdd the following script, replacing the placeholder values with your actual GitHub information:
#!/bin/bash
# GitHub authentication and repository details
TOKEN="your_github_personal_access_token"
REPO_OWNER="your_github_username"
REPO_NAME="your_repository_name"
BRANCH="main"
# Check if tar is installed
echo "Checking if tar is installed..."
if ! command -v tar &> /dev/null
then
echo "tar is not installed, installing now..."
sudo apt-get install tar -y
fi
# Set deployment directories
CURRENT_BUILD="current"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
NEW_BUILD="build_$TIMESTAMP"
# Download source from GitHub
echo "Downloading source from GitHub..."
wget --header="Authorization: token $TOKEN" -O $NEW_BUILD.tar.gz https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/tarball/${BRANCH}
# Extract source code
echo "Extracting source to $NEW_BUILD..."
mkdir $NEW_BUILD
tar -xf $NEW_BUILD.tar.gz -C $NEW_BUILD --strip-components 1
rm $NEW_BUILD.tar.gz
# Carry the runtime configuration into the new build BEFORE building.
# `evershop build` reads the .env file, so it has to be in place first.
if [ -f "$CURRENT_BUILD/.env" ]; then
echo "Copying .env into the new build..."
cp $CURRENT_BUILD/.env $NEW_BUILD/
fi
if [ -d "$CURRENT_BUILD/config" ]; then
cp -R $CURRENT_BUILD/config $NEW_BUILD/
fi
# Install dependencies
echo "Installing npm dependencies..."
cd $NEW_BUILD && npm install --production
# Build the application
echo "Building the application..."
npm run build
cd ..
# Backup current build
if [ -d "$CURRENT_BUILD" ]; then
echo "Backing up current build..."
if [ -d "previous-build" ]; then
rm -rf previous-build
fi
mv $CURRENT_BUILD previous-build
fi
# Deploy new build
echo "Deploying new build..."
mv $NEW_BUILD $CURRENT_BUILD
# Copy uploaded media forward from the previous build
if [ -d "previous-build" ]; then
echo "Copying media files..."
if [ -d "previous-build/media" ]; then
cp -R previous-build/media $CURRENT_BUILD/
else
mkdir -p $CURRENT_BUILD/media
fi
fi
# Set permissions
echo "Setting permissions..."
chmod -R 755 $CURRENT_BUILD
chmod -R 777 $CURRENT_BUILD/media
# Re-restrict the environment file — the recursive chmod above widened it.
if [ -f "$CURRENT_BUILD/.env" ]; then
chmod 600 $CURRENT_BUILD/.env
fi
# Restart application with PM2
echo "Restarting application..."
cd $CURRENT_BUILD
pm2 stop evershop 2>/dev/null || true
pm2 start npm --name "evershop" -- start
echo "Deployment completed successfully!"Make the script executable:
sudo chmod +x deploy.shCreating Your Environment File
EverShop reads its database credentials from environment variables, loaded from a .env file in the project root. This is the same file the evershop install setup wizard writes on a local install — on a server you create it by hand.
Create it in the directory the deployment script treats as the live build:
sudo mkdir -p /var/www/evershop/current
sudo nano /var/www/evershop/current/.envAdd your database connection and the production settings:
DB_HOST="your-rds-endpoint.rds.amazonaws.com"
DB_PORT="5432"
DB_NAME="evershop"
DB_USER="evershop"
DB_PASSWORD="secure_password"
DB_SSLMODE="require"
PORT="3000"
EVERSHOP_HOME_URL="https://yourdomain.com"
TRUST_PROXY_HOPS="1"Lock the file down — it holds your database password:
sudo chmod 600 /var/www/evershop/current/.env:::info
RDS instances present a certificate signed by the Amazon RDS CA. Use DB_SSLMODE="require" for an encrypted connection. If you want full certificate verification, download the RDS root certificate bundle and point DB_SSLROOTCERT at it; use DB_SSLMODE="no-verify" only as a temporary fallback.
:::
:::caution
The config/<env>.json files are still read, but the database connection is not configured there. Putting a system.database block in config/default.json has no effect — EverShop reads DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD and DB_SSLMODE from the environment. Use config/ for non-secret deployment settings such as the active theme and extension wiring.
:::
Setting the Public Base URL
EVERSHOP_HOME_URL overrides the shop.homeUrl configuration key and is the recommended way to set your production base URL.
EVERSHOP_HOME_URL="https://yourdomain.com"Everything EverShop emits as an absolute URL depends on it: links in transactional emails, canonical tags, hreflang alternates, and the <loc> entries plus the Sitemap: line in robots.txt. If it is left unset, EverShop falls back to shop.homeUrl and then to http://localhost: — which means customer emails go out pointing at localhost.
:::danger A malformed value stops the server from bootingEVERSHOP_HOME_URL is validated during startup. It must be an absolute http or https URL. A value like yourdomain.com (no scheme), or one using another protocol, throws during bootstrap and the process exits before it listens — PM2 will show the app repeatedly restarting.
Set the full origin with a scheme and no trailing path. Leaving the variable unset is fine; setting it to something invalid is fatal.
:::
:::tip
Set it to your HTTPS domain once Certbot has issued your certificate (see Securing Your Deployment below). If you set it to http:// and later switch to HTTPS, remember to update it — otherwise emails keep linking to the insecure URL.
:::
Setting the Proxy Hop Count
Nginx sits in front of the Node process and forwards the client address in X-Forwarded-For (that is what the proxy_set_header X-Forwarded-For line in the Nginx config above does). TRUST_PROXY_HOPS tells EverShop how many of those proxies to trust. It drives Express's trust proxy setting, which determines request.ip — and request.ip is what the built-in rate limiter buckets on.
| Value | Use when | What goes wrong otherwise |
|---|---|---|
0 |
The Node process is directly internet-facing with no proxy at all. | Wrong for this guide — with Nginx in front, every request would be attributed to 127.0.0.1. |
1 (default) |
One proxy: the Nginx reverse proxy on the EC2 instance. This is the setup in this guide. | — |
2 or more |
A proxy chain, e.g. an Application Load Balancer or CloudFront in front of Nginx. | Too low and every visitor collapses into one bucket, so a moderate traffic spike triggers mass 429 responses for everyone. Too high and a client can spoof X-Forwarded-For to present a fresh IP per request and bypass the limits entirely. |
An unset, empty or non-numeric value falls back to 1. Count the hops that actually terminate and re-forward the connection, and set the variable to that number. If you put an ALB or CloudFront in front of this instance, raise the value to 2.
Running the Deployment
Execute the deployment script:
cd /var/www/evershop
sudo ./deploy.shAfter deployment completes, your EverShop application should be running. You can access it using your EC2 instance's public DNS or your custom domain (if configured).
The admin panel is available at https://yourdomain.com/admin or http://your-ec2-public-dns/admin.
Securing Your Deployment
Installing SSL Certificate with Let's Encrypt
For production environments, secure your site with HTTPS:
- Install Certbot:
sudo apt install -y certbot python3-certbot-nginx- Obtain and install a certificate:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com- Follow the interactive prompts to complete the process.
Certbot will automatically modify your Nginx configuration to redirect HTTP traffic to HTTPS.
Setting Up Automatic Certificate Renewal
Let's Encrypt certificates expire after 90 days. Set up automatic renewal:
sudo systemctl status certbot.timerThis command should show that the automatic renewal service is active.
Maintenance and Monitoring
Setting Up Log Rotation
Configure log rotation to manage your application logs:
sudo nano /etc/logrotate.d/pm2Add the following configuration:
/var/www/evershop/.pm2/logs/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 ubuntu ubuntu
}Monitoring Your Application
Use PM2 to monitor your application:
pm2 monitFor more advanced monitoring, consider integrating with AWS CloudWatch.
Conclusion
You've successfully deployed EverShop to AWS using EC2 for hosting the application and RDS for managing the PostgreSQL database. This setup provides a robust, scalable foundation for your ecommerce platform.
For production environments, consider implementing additional best practices:
- Setting up automatic backups for your RDS instance
- Implementing AWS CloudWatch for comprehensive monitoring
- Configuring a CDN like CloudFront for improved content delivery
- Setting up an auto-scaling group for handling variable traffic loads
By following this guide, you've created a deployment that balances performance, security, and maintainability for your EverShop store.
File: docs/development/deployment/deploy-evershop-to-azure.md
sidebar_position: 5
keywords:
- Azure deployment
- EverShop cloud hosting
- Microsoft Azure App Service
- Node.js deployment
sidebar_label: Deploy to Azure
title: Deploy EverShop to Microsoft Azure
Deploy EverShop to Microsoft Azure
This comprehensive guide walks you through the process of deploying your EverShop ecommerce platform to Microsoft Azure using Azure App Service and Azure Database for PostgreSQL.
:::tip
Before you go live, run down the Production Checklist — it lists every environment variable EverShop reads at boot, the built-in per-IP rate limits, and what the build and start sequence actually does.
:::
Prerequisites
Before beginning the deployment process, ensure you have:
- An active Microsoft Azure account with subscription access
- An EverShop project installed and running on your local machine
- Git installed on your local machine (for version control and deployment)
Step 1: Create a New Azure Web App
Log in to the Azure portal at https://portal.azure.com.
Click the "+ Create a resource" button in the Azure portal dashboard.
Search for "Web App & Database" and select it from the search results.
Click the "Create" button to begin configuring your web application.
Configure your web application with the following settings:
Configure your database with appropriate settings for your EverShop deployment:
Click "Review + create" and then "Create" to provision your web application and database. Once completed, you'll see your resources in the Azure portal:
Step 2: Configure Deployment Options
Azure supports multiple deployment methods. For this guide, we'll use the local Git deployment approach:
Navigate to your newly created web app in the Azure portal.
In the left menu, select "Deployment Center."
Choose
Local Gitas your deployment option.Click the "Save" button to confirm your deployment configuration.
Step 3: Add Git Remote to Your Local Project
After configuring the deployment options, Azure will generate a Git URL for your repository:
Add this Git remote to your local EverShop project:
git remote add azure <your-git-url>:::caution
If your local project is not already a Git repository, initialize one first by running:
git initThen add and commit your files before proceeding to the next step:
git add .
git commit -m "Initial commit for Azure deployment":::
Step 4: Configure Your Local Project
Configure NPM Scripts
Azure's deployment process automatically runs npm install followed by npm run build. Ensure your package.json file includes these scripts:
{
"name": "evershop",
"version": "1.0.0",
"description": "",
"type": "module",
"main": "index.js",
"scripts": {
"build": "evershop build",
"start": "evershop start",
"user:create": "evershop user:create",
"user:changePassword": "evershop user:changePassword"
},
"workspaces": ["extensions/*"],
"author": "EverShop",
"license": "ISC",
"dependencies": {
"@evershop/evershop": "latest"
}
}:::infoevershop build takes no flags. Asset minification is always on in a production build and cannot be turned off.
:::
Add PM2 Configuration File
Azure App Service uses PM2 as its Node.js process manager. Create a PM2 configuration file in your project root:
module.exports = {
apps: [
{
name: "evershopAzure",
script: "npm",
env: {
NODE_ENV: "production",
},
args: "run start",
},
],
};Create a .gitignore File
Your local project structure should look similar to this:
├── .evershop
├── .log
├── extensions
├── media
├── themes
├── node_modules
├── public
├── .env
├── ecosystem.config.cjs
├── package.json
├── package-lock.json
└── README.mdCreate a .gitignore file to exclude unnecessary files from deployment:
.evershop
.log
node_modules
.envStep 5: Configure Environment Variables
EverShop requires specific environment variables for database connectivity. Configure these in the Azure portal:
Go to your web app in the Azure portal and navigate to the "Configuration" section.
Rename the following default Azure PostgreSQL environment variables to match EverShop's expected names:
AZURE_POSTGRESQL_DBNAME→DB_NAMEAZURE_POSTGRESQL_USERNAME→DB_USERAZURE_POSTGRESQL_PASSWORD→DB_PASSWORDAZURE_POSTGRESQL_HOST→DB_HOSTAZURE_POSTGRESQL_PORT→DB_PORT
Add these additional required environment variables:
DB_SSLMODE: Set torequireto enable SSL connections to the databasePORT: Set to3000to specify the port EverShop will useEVERSHOP_HOME_URL: Your store's public base URL, e.g.https://your-app.azurewebsites.net(or your custom domain once mapped)TRUST_PROXY_HOPS: Set to1— App Service terminates TLS at its own front end and forwards to your container
Set the public base URL
EVERSHOP_HOME_URL overrides the shop.homeUrl configuration key and is the recommended way to set your production base URL — no config file needs to be edited or redeployed to change it.
EVERSHOP_HOME_URL=https://your-app.azurewebsites.netEverything EverShop emits as an absolute URL depends on it: links in transactional emails, canonical tags, hreflang alternates, and the <loc> entries plus the Sitemap: line in robots.txt. If it is left unset, EverShop falls back to shop.homeUrl and then to http://localhost: — which means customer emails go out pointing at localhost.
:::danger A malformed value stops the server from bootingEVERSHOP_HOME_URL is validated during startup. It must be an absolute http or https URL. A value like your-app.azurewebsites.net (no scheme), or one using another protocol, throws during bootstrap and the process exits before it listens.
Set the full origin with a scheme and no trailing path. Leaving the variable unset is fine; setting it to something invalid is fatal.
:::
Set the proxy hop count
TRUST_PROXY_HOPS tells EverShop how many reverse proxies sit in front of the app. It drives Express's trust proxy setting, which determines request.ip — and request.ip is what the built-in rate limiter buckets on.
| Value | Use when | What goes wrong otherwise |
|---|---|---|
0 |
The app is directly internet-facing with no proxy. | Never correct on App Service — every request would be attributed to the front end. |
1 (default) |
One proxy: the App Service front end. This is the normal setting. | — |
2 or more |
A proxy chain, e.g. Azure Front Door or a CDN in front of App Service. | Too low and every visitor collapses into one bucket, so a moderate traffic spike triggers mass 429 responses for everyone. Too high and a client can spoof X-Forwarded-For to present a fresh IP per request and bypass the limits entirely. |
An unset, empty or non-numeric value falls back to 1. Count the hops that actually terminate and re-forward the connection, and set the variable to that number.
- Save your configuration changes.
Step 6: Deploy Your Project
With configuration complete, deploy your EverShop application to Azure:
git push azure master:::caution
During your first deployment, Azure will prompt you for Git credentials. You can find these in the "Deployment Center" section of your web app:
:::
The initial deployment may take several minutes. Once completed, your site will be accessible at the URL provided by Azure:
Step 7: Create an Administrator Account
After deploying your EverShop store, create an administrator account:
Connect to your web app using the SSH feature in the Azure portal.
Navigate to your application directory:
cd /home/site/wwwroot- Create an admin user:
npm run user:create -- --email "[email protected]" --password "securePassword" --name "Admin Name"- Access your admin panel at
https://<your-azure-domain>/adminand log in using the credentials you just created.
Step 8: Configure Custom Domain (Optional)
To use your own domain instead of the default Azure domain:
Navigate to the "Custom domains" section in your web app's settings.
Follow the Azure Custom Domain Configuration Guide to map your domain to your Azure web app.
Troubleshooting
If you encounter issues during deployment or while running your EverShop store on Azure:
- Check the application logs in the Azure portal under "App Service logs"
- Verify your environment variables are configured correctly
- Ensure your database connection is working properly
- Confirm that your PM2 configuration is correct
Performance Optimization
For optimal performance of your EverShop store on Azure:
- Enable Azure CDN for faster content delivery
- Configure auto-scaling based on your expected traffic patterns
- Use the appropriate App Service pricing tier for your needs
- Consider adding Redis Cache for improved performance
File: docs/development/deployment/deploy-evershop-to-digitalocean-app-platform.md
sidebar_position: 12
keywords:
- DigitalOcean deployment
- App Platform hosting
- EverShop cloud deployment
- Node.js PostgreSQL deployment
sidebar_label: Deploy to DigitalOcean App Platform
title: Deploy EverShop to DigitalOcean App Platform
Deploy EverShop to DigitalOcean App Platform
This guide provides detailed instructions for deploying EverShop to DigitalOcean App Platform with a managed PostgreSQL database. Follow these steps to get your ecommerce store running in a production environment.
:::tip
Before you go live, run down the Production Checklist — it lists every environment variable EverShop reads at boot, the built-in per-IP rate limits, and what the build and start sequence actually does.
:::
Prerequisites
Before beginning the deployment process, ensure you have:
- A DigitalOcean account with billing set up
- A registered domain name (for production use)
- A GitHub account
- A GitHub repository containing your EverShop application code
Getting Started
Prepare Your GitHub Repository
While DigitalOcean App Platform supports multiple deployment methods, this tutorial focuses on deployment via GitHub integration. This approach enables continuous deployment whenever you push changes to your repository.
Ensure your package.json file contains the following scripts that DigitalOcean will execute during deployment:
{
"scripts": {
"build": "evershop build",
"start": "evershop start",
"user:create": "evershop user:create",
"user:changePassword": "evershop user:changePassword"
}
}:::infoevershop build takes no flags. Asset minification is always on in a production build and cannot be turned off.
:::
Create a New DigitalOcean App
Log in to your DigitalOcean account and navigate to the App Platform dashboard.
Click the "Create App" button to begin the setup process.
In the "Resources" section, select your GitHub repository containing the EverShop code. If this is your first time connecting GitHub to DigitalOcean, click "Manage Access" to authorize the integration.
Configure your deployment settings. You can typically use the default settings, but verify the branch you want to deploy.
Click "Next" to proceed to the plan selection. Choose the plan that best fits your requirements. For testing or small-scale deployments, the "Basic" plan is often sufficient.
:::warning
Do not add a database to your app at this stage. EverShop requires PostgreSQL 13 or higher, but DigitalOcean App Platform currently offers PostgreSQL 12 through its integrated database option. We'll create a compatible database separately in the next section.
:::
For now, leave the "Environment Variables" section empty. We'll configure these after creating the database.
Complete the "Info" and "Review" sections, then finalize your app creation by clicking "Create Resources."
Create a PostgreSQL Database
EverShop requires PostgreSQL version 13 or higher. Follow these steps to create a compatible managed database:
From the DigitalOcean dashboard, click "Databases" in the main navigation menu.
Click the "Create Database Cluster" button.
Select "PostgreSQL" as the database type and choose version 13 or higher.
Select your preferred region, typically the same region as your App Platform deployment.
Choose an appropriate plan based on your expected traffic and database usage.
After the database is created, navigate to its "Overview" page to find the connection details you'll need for the next steps.
:::warning
For App Platform integration, you must use the "Public Network" connection method, as DigitalOcean App Platform does not currently support private "VPC Network" connections to managed databases. Consider implementing additional security measures such as IP restrictions if your application handles sensitive data.
:::
Configure Your EverShop Application
Set Up Environment Variables
Now that your database is ready, configure your EverShop application with the appropriate connection details:
Return to your App Platform dashboard and select your EverShop application.
Navigate to the "Settings" tab and find the "App-level environment variables" section.
Add the following environment variables, using the values from your database connection details:
DB_HOST: The hostname of your PostgreSQL databaseDB_PORT: The port number (typically 25060 for DigitalOcean managed PostgreSQL)DB_NAME: Your database nameDB_USER: Your database usernameDB_PASSWORD: Your database passwordDB_SSLMODE: Set tono-verify(orrequireif you've configured proper SSL)EVERSHOP_HOME_URL: Your store's public base URL, e.g.https://your-app.ondigitalocean.app(or your custom domain once configured)TRUST_PROXY_HOPS: Set to1— App Platform routes every request through its own load balancer
Set the public base URL
EVERSHOP_HOME_URL overrides the shop.homeUrl configuration key and is the recommended way to set your production base URL — it lives with the rest of your App Platform environment variables, so changing it needs no code change.
EVERSHOP_HOME_URL=https://your-app.ondigitalocean.appEverything EverShop emits as an absolute URL depends on it: links in transactional emails, canonical tags, hreflang alternates, and the <loc> entries plus the Sitemap: line in robots.txt. If it is left unset, EverShop falls back to shop.homeUrl and then to http://localhost: — which means customer emails go out pointing at localhost.
:::danger A malformed value stops the app from bootingEVERSHOP_HOME_URL is validated during startup. It must be an absolute http or https URL. A value like your-app.ondigitalocean.app (no scheme), or one using another protocol, throws during bootstrap and the process exits before it listens — the deployment will fail its health check.
Set the full origin with a scheme and no trailing path. Leaving the variable unset is fine; setting it to something invalid is fatal.
:::
:::tip
Update EVERSHOP_HOME_URL when you attach your custom domain. Leaving it on the *.ondigitalocean.app hostname means every email and canonical URL keeps pointing at the platform domain.
:::
Set the proxy hop count
TRUST_PROXY_HOPS tells EverShop how many reverse proxies sit in front of the app. It drives Express's trust proxy setting, which determines request.ip — and request.ip is what the built-in rate limiter buckets on.
| Value | Use when | What goes wrong otherwise |
|---|---|---|
0 |
The app is directly internet-facing with no proxy. | Never correct on App Platform — every request would be attributed to the platform load balancer. |
1 (default) |
One proxy: the App Platform load balancer. This is the normal setting. | — |
2 or more |
A proxy chain, e.g. Cloudflare or another CDN in front of App Platform. | Too low and every visitor collapses into one bucket, so a moderate traffic spike triggers mass 429 responses for everyone. Too high and a client can spoof X-Forwarded-For to present a fresh IP per request and bypass the limits entirely. |
An unset, empty or non-numeric value falls back to 1. Count the hops that actually terminate and re-forward the connection, and set the variable to that number.
Configure Deployment Commands
To ensure your application builds and starts correctly:
In the "Settings" tab, select your app component (typically named after your GitHub repository).
Navigate to the "Commands" section.
Verify that the build and run commands match the scripts in your package.json file:
Save your changes. DigitalOcean will automatically redeploy your application with the new configuration.
After the deployment process completes, your EverShop application should be up and running on DigitalOcean App Platform. You can verify this by checking the deployment status:
Create an Administrator Account
To access the admin panel and manage your store, create an administrator account:
From your App Platform dashboard, select your EverShop application.
Navigate to the "Console" tab to access a command-line interface.
Run the following command to create an admin user, replacing the placeholder values with your desired credentials:
npm run user:create -- --email "[email protected]" --name "Admin User" --password "SecurePassword123!"- Once the command completes successfully, access your admin panel by visiting
https://<your-app-domain>/adminand log in with your newly created credentials.
Next Steps
Congratulations! You've successfully deployed EverShop to DigitalOcean App Platform. Here are some additional steps to consider:
Configure Custom Domain
For production use, configure your custom domain through the App Platform "Settings" tab under the "Domains" section.
Set Up Continuous Deployment
Your application is already configured for automatic deployment when you push changes to your GitHub repository. You can adjust these settings in the "Settings" tab under the "Components" section.
Implement Monitoring
Consider setting up monitoring and alerts through DigitalOcean's Monitoring service to keep track of your application's health and performance.
Regular Backups
Configure regular database backups through the DigitalOcean Managed Database dashboard to protect your store's data.
Performance Optimization
As your store grows, consider scaling your resources in App Platform and optimizing your EverShop application for improved performance.
File: docs/development/deployment/deploy-evershop-to-heroku.md
sidebar_position: 7
keywords:
- Heroku deployment
- EverShop hosting
- PostgreSQL setup
- Node.js cloud hosting
sidebar_label: Deploy to Heroku
title: Deploy EverShop to Heroku
Deploy EverShop to Heroku
This guide provides detailed instructions for deploying an EverShop application on Heroku's cloud platform using the Heroku Command Line Interface (CLI) and a PostgreSQL database add-on.
:::tip
Before you go live, run down the Production Checklist — it lists every environment variable EverShop reads at boot, the built-in per-IP rate limits, and what the build and start sequence actually does.
:::
Prerequisites
Before beginning the deployment process, ensure you have:
- An active Heroku account
- An EverShop project installed and running on your local machine
- Git installed on your local machine
- Heroku CLI installed
Step 1: Create a New Heroku App
- Log in to the Heroku dashboard.
- Click the "New" button and select "Create new app" from the dropdown menu.
- Enter a unique app name, select your preferred region, and click "Create app".
Step 2: Install and Configure Heroku CLI
- If you haven't already, download and install the Heroku CLI from the official Heroku documentation.
- Open your terminal and authenticate with Heroku:
heroku loginFollow the prompts to complete the authentication process.
Step 3: Set Up a PostgreSQL Database
EverShop requires a PostgreSQL database. Add the Heroku Postgres add-on to your application:
heroku addons:create heroku-postgresql:PLAN_NAME -a YOUR_APP_NAME:::caution
Replace PLAN_NAME with your desired PostgreSQL plan (e.g., hobby-dev for the free tier). See the Heroku Postgres plans for available options.
Replace YOUR_APP_NAME with your Heroku application name.
:::
Configure Database Connection
Heroku provides database credentials as a connection string in the DATABASE_URL environment variable. View this connection string with:
heroku config -a YOUR_APP_NAMEYou'll see output similar to:
DATABASE_URL: postgres://username:password@host:port/database_nameSince EverShop requires individual database configuration parameters rather than a connection string, parse the DATABASE_URL and set the following environment variables:
heroku config:set DB_HOST=your_database_host -a YOUR_APP_NAME
heroku config:set DB_PORT=5432 -a YOUR_APP_NAME
heroku config:set DB_USER=your_database_username -a YOUR_APP_NAME
heroku config:set DB_PASSWORD=your_database_password -a YOUR_APP_NAME
heroku config:set DB_NAME=your_database_name -a YOUR_APP_NAME
heroku config:set DB_SSLMODE=no-verify -a YOUR_APP_NAME:::tip
The values for these environment variables can be extracted from the DATABASE_URL. For example, if your DATABASE_URL is:
postgres://gqdnxqkaxcrbyd:dd3aecf3715167ce8a519c518f637sdfcb9ebb0dda3723d050e8a3b8a7cf19fc789@ec2-52-205-11-146.compute-1.amazonaws.com:5432/d468v1qsdfnb0arsqfThen your environment variables would be:
DB_HOST=ec2-52-205-11-146.compute-1.amazonaws.com
DB_PORT=5432
DB_USER=gqdnxqkaxcrbyd
DB_PASSWORD=dd3aecf3715167ce8a519c518f637sdfcb9ebb0dda3723d050e8a3b8a7cf19fc789
DB_NAME=d468v1qsdfnb0arsqf:::
Step 4: Prepare Your Local Project
Assuming you have an EverShop project ready for deployment, you'll need to make a few configuration changes to ensure compatibility with Heroku.
A typical EverShop project structure looks like:
├── .evershop # Production build files
├── .log # Application logs
├── extensions # Custom extensions
├── media # Uploaded images and media
├── themes # Custom themes
├── node_modules # Node.js dependencies
├── public # Static public files
├── .env # Environment variables (local only)
├── package.json # Project configuration
└── README.md # DocumentationSpecify Node.js and NPM Versions
Add Node.js and NPM version specifications to your package.json:
{
"engines": {
"node": "20.x",
"npm": "9.x"
}
}Configure Scripts for Heroku
Update your package.json with the necessary scripts for Heroku deployment:
{
"scripts": {
"build": "evershop build",
"start": "evershop start",
"user:create": "evershop user:create"
}
}:::infoevershop build takes no flags. Asset minification is always on in a production build and cannot be turned off.
:::
Set the Public Base URL
EVERSHOP_HOME_URL overrides the shop.homeUrl configuration key and is the recommended way to set your production base URL on Heroku — it is a config var like any other, so changing it needs no redeploy of your code.
heroku config:set EVERSHOP_HOME_URL=https://YOUR_APP_NAME.herokuapp.com -a YOUR_APP_NAMEEverything EverShop emits as an absolute URL depends on it: links in transactional emails, canonical tags, hreflang alternates, and the <loc> entries plus the Sitemap: line in robots.txt. If it is left unset, EverShop falls back to shop.homeUrl and then to http://localhost: — which means customer emails go out pointing at localhost.
:::danger A malformed value stops the dyno from bootingEVERSHOP_HOME_URL is validated during startup. It must be an absolute http or https URL. A value like YOUR_APP_NAME.herokuapp.com (no scheme), or one using another protocol, throws during bootstrap and the process exits before it binds to $PORT — Heroku will report an R10 Boot timeout or crash the dyno.
Set the full origin with a scheme and no trailing path. Leaving the variable unset is fine; setting it to something invalid is fatal.
:::
:::tip
Update EVERSHOP_HOME_URL when you attach a custom domain, otherwise every email and canonical URL keeps pointing at herokuapp.com.
:::
Set the Proxy Hop Count
Heroku's router sits in front of every dyno, so EverShop only sees the real client IP through X-Forwarded-For. TRUST_PROXY_HOPS tells EverShop how many proxies to trust. It drives Express's trust proxy setting, which determines request.ip — and request.ip is what the built-in rate limiter buckets on.
heroku config:set TRUST_PROXY_HOPS=1 -a YOUR_APP_NAME| Value | Use when | What goes wrong otherwise |
|---|---|---|
0 |
The app is directly internet-facing with no proxy. | Never correct on Heroku — every request would be attributed to the Heroku router. |
1 (default) |
One proxy: the Heroku router. This is the normal setting. | — |
2 or more |
A proxy chain, e.g. Cloudflare or another CDN in front of the Heroku router. | Too low and every visitor collapses into one bucket, so a moderate traffic spike triggers mass 429 responses for everyone. Too high and a client can spoof X-Forwarded-For to present a fresh IP per request and bypass the limits entirely. |
An unset, empty or non-numeric value falls back to 1. Count the hops that actually terminate and re-forward the connection, and set the variable to that number.
Configure Build Behavior
Control how Heroku builds your application with these environment variables:
# Skip installing devDependencies (recommended for production)
heroku config:set NPM_CONFIG_PRODUCTION=true -a YOUR_APP_NAME
# Use 'npm install' instead of 'npm ci' (if you encounter issues with npm ci)
heroku config:set USE_NPM_INSTALL=true -a YOUR_APP_NAME:::info
Heroku automatically sets NODE_ENV=production for all deployed applications.
:::
Create a .gitignore File
Create a .gitignore file to exclude unnecessary files from deployment:
.evershop
.log
node_modules
.envStep 5: Deploy Your Application
Initialize Git Repository
If your project isn't already in a Git repository:
git initCommit Your Changes
Add and commit all files:
git add .
git commit -m "Prepare EverShop for Heroku deployment"Connect to Heroku Remote
Link your local repository to your Heroku app:
heroku git:remote -a YOUR_APP_NAMEDeploy to Heroku
Push your code to deploy:
git push heroku mainIf your primary branch is named master instead of main, use:
git push heroku masterHeroku will automatically detect your Node.js application, install dependencies, run the build script, and start your application. You can monitor the deployment process in your terminal.
Once deployed, you can access your application at:
https://YOUR_APP_NAME.herokuapp.comStep 6: Create an Administrator Account
After your first deployment, create an administrator account to access the admin panel:
heroku run npm run user:create -- --email "[email protected]" --name "Admin User" --password "SecurePassword123!" -a YOUR_APP_NAME:::caution
Replace the example credentials with a secure email, name, and strong password.
:::
Once created, you can access the admin panel at:
https://YOUR_APP_NAME.herokuapp.com/adminTroubleshooting and Maintenance
Viewing Application Logs
Monitor your application logs with:
heroku logs --tail -a YOUR_APP_NAMEScaling Your Application
As your store grows, you may need to scale your application:
heroku ps:scale web=2 -a YOUR_APP_NAMEManaging Database Backups
Create regular backups of your PostgreSQL database:
heroku pg:backups:capture -a YOUR_APP_NAMEUpdating Your Application
To deploy updates, simply commit your changes and push to Heroku:
git add .
git commit -m "Update application"
git push heroku mainConclusion
Congratulations! You've successfully deployed EverShop to Heroku. Your ecommerce application is now accessible to customers worldwide, backed by Heroku's reliable cloud infrastructure.
File: docs/development/deployment/production-checklist.md
sidebar_position: 2
keywords:
- EverShop production checklist
- environment variables
- rate limiting
- EVERSHOP_HOME_URL
- TRUST_PROXY_HOPS
- production deployment
sidebar_label: Production Checklist
title: Production Checklist
Production Checklist
This page is the pre-launch pass. Whichever platform you deploy to — AWS, Azure, DigitalOcean, Heroku or your own machine — the same set of environment variables governs how the store boots, and the same rate limits govern how it behaves under load.
Read it top to bottom once before your first production start, and again whenever you move the store to a new host or put a CDN in front of it.
Environment variables
EverShop is configured for deployment through environment variables. On a server they normally live in a .env file in the project root (the file evershop install writes) or in your platform's configuration panel — both end up in process.env, which is all EverShop reads.
Two of these have failure modes worth knowing before you set them: EVERSHOP_HOME_URL aborts the boot if it is malformed, and TRUST_PROXY_HOPS silently changes who your rate limits apply to. Both are covered in detail below the table.
| Variable | Default | What it controls, and what happens if it is wrong |
|---|---|---|
| Database | ||
DB_HOST |
— | PostgreSQL hostname. Required. |
DB_PORT |
5432 |
PostgreSQL port. Managed providers often use a non-standard port (DigitalOcean uses 25060). |
DB_NAME |
— | Database name. Required. |
DB_USER |
— | Database user. Needs CREATE, ALTER and DROP — migrations run on every start. |
DB_PASSWORD |
— | Database password. |
DB_SSLMODE |
TLS off | disable turns TLS off explicitly. require, prefer, verify-ca and verify-full all enable TLS with certificate verification. no-verify enables TLS but skips verification — acceptable as a stopgap, not as a permanent setting. |
**Any unset or unrecognized value means no TLS at all.** A typo here does not error; it silently sends your credentials in the clear. || DB_SSLROOTCERT``DB_SSLCERT``DB_SSLKEY | — | Filesystem paths to a CA bundle / client certificate / client key. Read only in the verifying DB_SSLMODE modes. A path that does not exist throws at startup when the connection pool is created. |
| **Server** | | |
| PORT | 3000 | Port the HTTP server binds to. A non-numeric value falls back to 3000 rather than failing — if your platform assigns a port dynamically, make sure it is exported before the process starts. |
| NODE_ENV | set by the CLI | You do not need to set this. evershop start and evershop build force production; evershop dev forces development. A value you export is overwritten by the CLI for those commands. |
| EVERSHOP_HOME_URL | shop.homeUrl, then localhost | **Validated at boot — a malformed value is fatal.** The store's public base URL, used for every absolute URL the store emits. See below. |
| TRUST_PROXY_HOPS | 1 | Number of reverse proxies in front of the app. Determines request.ip, and therefore which bucket the rate limiter counts a request into. See below. |
| **Tokens and secrets** | | |
| JWT_ADMIN_SECRET | — | Signing keys for the token-based REST authentication endpoints (POST /api/user/tokens, /api/user/token/refresh, /api/customer/tokens, /api/customer/token/refresh, /api/user/session/tokens).
There is no default and no boot-time check: the endpoints throw `JWT secret for … is not configured` the first time they are called. If you use the REST API from a headless frontend or a mobile app, set all four to long, independent random strings. Rotating one invalidates every token signed with it.
Cookie-session logins to the admin panel and the storefront do not use these. || JWT_ADMIN_REFRESH_SECRET | — | |
| JWT_CUSTOMER_SECRET | — | |
| JWT_CUSTOMER_REFRESH_SECRET | — | |
| JWT_ISSUER | evershop | The iss claim on issued tokens. |
| JWT_ADMIN_TOKEN_EXPIRY
JWT_ADMIN_REFRESH_TOKEN_EXPIRY
JWT_CUSTOMER_TOKEN_EXPIRY
JWT_CUSTOMER_REFRESH_TOKEN_EXPIRY | 900
54000
1800
108000 | Token lifetimes in seconds (15 min / 15 h / 30 min / 30 h). |
| ORDER_TRACKING_TOKEN_SECRET | — | Signs the anonymous order-tracking links embedded in shipment emails. If unset, those links cannot be generated and the tracking page renders "this link is no longer valid". Set it if guests can place orders. |
| Images | | |
| IMAGE_ALLOWED_HOSTS | empty | Comma-separated allowlist of hosts the /images optimization proxy may fetch from. It is a strict allowlist: it blocks SSRF against internal addresses and stops the endpoint being used as a free image proxy for third-party sites.
**EverShop logs a warning at boot when this is unset** and no cloud storage host can be derived from your configuration. With nothing allowed, only local `media`, `public` and theme images are optimized; external images are refused. When S3, Azure or GCS file storage is configured, that provider's host is allowed automatically — you do not need to repeat it here. || Logging | | |
| LOG_FILE | console | Path to a log file. When set, log output goes to that file instead of the console — check it before you conclude the app is silent. Ignored in debug mode (evershop dev, or --debug), which always logs to the console. |
| LOGGER_LEVEL | warn | Minimum level to record: error, warn, info, http, verbose, debug, silly. The default of warn means informational messages are dropped in production. Debug mode overrides this to silly. |
| Cloud file storage — only needed for the provider you selected in Admin → Settings. See File Storage. | | |
| AWS_BUCKET_NAME
AWS_REGION
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_S3_ENDPOINT
AWS_S3_FORCE_PATH_STYLE
AWS_S3_BASE_URL | — | Amazon S3 and S3-compatible services. Omit the key pair to use the AWS SDK's default credential chain (instance role, IRSA, shared config) — the recommended setup on EC2 and EKS. AWS_S3_ENDPOINT plus AWS_S3_FORCE_PATH_STYLE cover R2, MinIO and Spaces; AWS_S3_BASE_URL serves files through a CDN instead of the bucket host. |
| GCS_BUCKET_NAME
GCS_BASE_URL | — | Google Cloud Storage. Credentials come from Application Default Credentials (an attached service account, or GOOGLE_APPLICATION_CREDENTIALS) unless you paste a service-account key in the admin settings. |
| AZURE_STORAGE_CONNECTION_STRING
AZURE_STORAGE_CONTAINER_NAME
AZURE_STORAGE_BASE_URL | container: images | Azure Blob Storage. The container is created on first use if it does not exist. |
:::info
Anything set through an environment variable or config/<env>.json wins over the matching admin setting, and the admin form shows those fields as read-only. That is deliberate: it lets infrastructure pin credentials that a store operator must not be able to change from the browser.
:::
:::tip Installer variablesevershop install also reads ADMIN_FULLNAME, ADMIN_EMAIL and ADMIN_PASSWORD, and skips the matching prompt when one is present. Useful for scripting an unattended first install. They have no effect on a running store.
:::
`EVERSHOP_HOME_URL` — validated, and fatal when wrong
This is the recommended way to set your store's public base URL. It overrides the shop.homeUrl configuration key, so you can change the domain without editing a config file or rebuilding.
EVERSHOP_HOME_URL="https://your-store.com"Everything the store emits as an absolute URL is derived from it:
- links in transactional emails (order confirmations, shipment notifications, password resets)
<loc>entries insitemap.xmland the absoluteSitemap:line in the generatedrobots.txt- canonical tags and
hreflangalternates on multi-language stores
Left unset, EverShop falls back to shop.homeUrl and then to http://localhost:$PORT. That fallback is the classic production bug: the store works fine in a browser, and every order confirmation emails a localhost link to the customer.
:::danger A malformed value stops the boot
The value is validated during module bootstrap. It must parse as a URL and use the http or https protocol. your-store.com (no scheme) and ftp://your-store.com both throw, and the process exits before it starts listening.
An unset or empty value is allowed — it just falls back. Only a set but invalid value is fatal. If a deploy suddenly fails to come up right after you touched this variable, that is where to look.
:::
Set the full origin, with the scheme and without a trailing slash or path. Update it when you move from a platform-assigned hostname to your custom domain, and again if you switch from http to https.
`TRUST_PROXY_HOPS` — who the rate limiter sees
Every deployment in this section runs behind at least one proxy. EverShop only learns the real client address from the X-Forwarded-For header, and it only trusts as much of that header as you tell it to.
TRUST_PROXY_HOPS sets Express's trust proxy to a hop count. That determines request.ip, and request.ip is the key the rate limiter buckets on.
| Value | Topology | Failure mode when this is the wrong choice |
|---|---|---|
0 |
The Node process is directly internet-facing. X-Forwarded-For is not trusted at all. |
Correct only with no proxy. Behind one, every request is attributed to the proxy's address — one bucket for the entire internet. |
1 (default) |
One reverse proxy or load balancer: nginx, an ALB, the Heroku router, the App Service front end, the App Platform load balancer. | The common case, and the reason this is the default. |
2 or more |
A chain: CDN → load balancer → app. Cloudflare or CloudFront in front of nginx is two. | Set too low and the address you see is the last proxy's, not the visitor's — all traffic collapses into a single bucket and a normal traffic spike returns 429 to everybody at once. |
**Set too high** and the app trusts an entry in `X-Forwarded-For` that the client supplied. A client can then present a fresh fake IP on every request and never hit a limit at all. |Count the hops that actually terminate and re-forward the connection, and set the variable to exactly that number. An unset, empty, negative or non-integer value falls back to 1.
Built-in rate limits
EverShop ships a per-client-IP rate limiter, mounted early in the middleware stack — before session lookup and before any database work — so a flood is shed before it consumes a connection from the pool.
| Tier | Applies to | Limit |
|---|---|---|
| Pages | Everything not matched by another tier — storefront and admin pages. | 300 requests per minute per IP (~5/second) |
| API | /api and everything under /api/. |
120 requests per minute per IP |
| Auth | POST to these paths only: |
`/customer/login`
`/admin/user/login`
`/api/customers` (registration)
`/api/customers/reset-password`
`/api/customers/password` | **8 requests per 15 minutes** per IP |A leading locale prefix is stripped before matching, so /fr/customer/login lands in the auth tier just like /customer/login.
Exempt paths
These never count against any limit:
- Static assets — any path ending in a known asset extension:
.js,.mjs,.cjs,.css,.map,.png,.jpg,.jpeg,.gif,.svg,.webp,.avif,.ico,.woff,.woff2,.ttf,.eot,.txt,.xml,.json. (This is why/robots.txtand/sitemap.xmlare also exempt.) - Hot module replacement —
/__webpack_hmr…and any.hot-updatepath. /backend/— the admin development bundle./healthand/healthz— reserved for uptime probes, so a monitor polling every few seconds never exhausts a bucket.
:::caution/health and /healthz are exempt from rate limiting, but EverShop core does not define them as routes — a request to either returns a 404 unless you add the route yourself in an extension. If your platform requires a health-check endpoint, point it at one of these two paths and implement it, so the probe traffic stays exempt.
:::
The 429 contract
When a limit is exceeded, the response is 429 Too Many Requests with:
- a
Retry-Afterheader, in seconds —60for the page and API tiers,900for the auth tier - standard
RateLimit-*headers on every response, so a well-behaved client can back off before it is throttled (the legacyX-RateLimit-*headers are not sent) - a JSON body for
/api/**paths, matching EverShop's normal error envelope:
{
"error": {
"status": 429,
"message": "Too many requests. Please slow down and try again later."
}
}- a plain-text body for page requests
What you cannot change
These limits are hardcoded and not operator-configurable. There is no config key and no environment variable for the windows, the thresholds or the tiers. They are deliberately generous — sized so that an office or a mobile carrier sharing one NAT address still browses comfortably — and exist as a capacity safety net rather than as a tunable policy.
The limiter is skipped when NODE_ENV=test, which keeps integration suites deterministic. That is not an escape hatch for production: evershop start forces NODE_ENV=production, so a running store always has the limiter active.
:::warning A per-IP limit is not DDoS protection
Per-IP limits stop a single abusive source. A distributed flood arrives from thousands of addresses and each one stays under the threshold. If that is in your threat model, put a WAF or CDN with edge rate limiting in front of the store — and remember to raise TRUST_PROXY_HOPS when you do.
:::
Build and start
Two commands, in this order:
npm run build
npm run startevershop build compiles the production bundles for every route. It takes no flags — asset minification is always on for a production build and cannot be disabled. Run it on every deploy, after npm install, and re-run it whenever you change a theme, add an extension or upgrade EverShop.
evershop start boots the server in production mode. It sets NODE_ENV=production itself, loads .env, runs module bootstrap, and binds to $PORT (default 3000).
Migrations run on start
Database migrations are applied automatically during startup, after bootstrap and before the server accepts requests. There is no separate migrate command to run and nothing to schedule.
The practical consequences:
- The database user in
DB_USERneeds DDL privileges —CREATE,ALTER,DROP— permanently, not just at install time. - Take a backup before starting a new version. Migrations are not reversible; rolling the application back does not roll the schema back.
- On a multi-instance deployment, be aware that every instance runs migrations at boot. Roll instances one at a time rather than restarting the whole fleet at once.
- The very first start on an empty database also creates the schema, so a fresh deploy has a longer cold start than subsequent ones.
Creating the first administrator is a separate, explicit step — evershop install does it locally, and on a server you run:
npm run user:create -- --email "[email protected]" --name "Admin" --password "a-strong-password"Final pass before launch
-
EVERSHOP_HOME_URLis set to the real public origin, withhttps://and no trailing slash — and a test email actually links to it. -
TRUST_PROXY_HOPSmatches the real number of proxies. Confirm it by checking that different clients produce different addresses in your logs. -
DB_SSLMODEis one of the verifying modes, not left unset. - The four JWT secrets are set to independent random strings if you use the REST API;
ORDER_TRACKING_TOKEN_SECRETis set if guests can order. -
IMAGE_ALLOWED_HOSTSis set, or cloud file storage is configured — check the boot log for the warning. -
LOG_FILEpoints somewhere you will actually look, and log rotation is configured for it. - Uploaded media is on cloud storage, or on a volume that survives a redeploy. See File Storage.
- Automated database backups are on, and you have restored one at least once.
- No hand-written
public/robots.txtorpublic/sitemap.xmlis shadowing the generated ones. See Static File Serving. - Store settings — currency, timezone, units, languages, tax — are set in Admin → Settings. See Store Settings.
- Demo seed data has not been run against the production database.
-
npm run buildcompleted without errors on the exact commit you are deploying.
File: docs/development/getting-started/installation-guide.md
sidebar_position: 10
keywords:
- EverShop installation
sidebar_label: Installation Guide
title: EverShop Installation Guide
Installation guide
The following installation guides will guide you step-by-step to create a new EverShop project and get it started.
:::info
Please check this document for the system requirements list.
:::
Install EverShop Using `create-evershop-app` command
npx create-evershop-app my-evershop-appThe create-evershop-app command will create a new folder named my-evershop-app and install all of the dependencies for you.
Install EverShop Using Docker
You can get started with EverShop in minutes by using the Docker image. The Docker image is a great way to get started with EverShop without having to worry about installing dependencies or configuring your environment.
curl -sSL https://raw.githubusercontent.com/evershopcommerce/evershop/main/docker-compose.yml > docker-compose.yml
docker-compose up -dThe Docker image will start a fresh EverShop installation with the default configuration. You can access the site at http://localhost:3000 and the admin panel at http://localhost:3000/admin.
:::info
To create a new admin user, terminal into the Docker app container and run the following command:
npm run user:create -- --email "your email" --password "your password" --name "your name":::
:::caution
The public Docker image is for installing EverShop in your local environment only. If you are looking for a development solution, please check the development section below.
:::
Install EverShop manually using Npm
Step 1: Install The @evershop/evershop Npm Package
@evershop/evershop is the core of the EverShop platform. It contains all of the core modules like catalog, checkout, order.
npm init --init-type=module -y
npm install @evershop/evershop:::info
EverShop is an ES-modules (ESM) project and requires "type": "module" in your project's package.json. npm 11 and newer write "type": "commonjs" into the package.json generated by npm init -y — with that value, npm run build fails on every page with Module parse failed: 'import' and 'export' may appear only with 'sourceType: module'. The npm pkg set type=module command sets the correct module type. Projects created with create-evershop-app already have it.
:::
Step 2: Install the core Npm scripts
Open the package.json file and add the following scripts:
{
"scripts": {
"setup": "evershop install",
"build": "evershop build",
"start": "evershop start",
"dev": "evershop dev",
"seed": "evershop seed",
"start:debug": "evershop start --debug",
"user:create": "evershop user:create",
"user:changePassword": "evershop user:changePassword",
"theme:create": "evershop theme:create",
"theme:active": "evershop theme:active",
"theme:twizz": "evershop theme:twizz",
"theme:status": "evershop theme:status",
"theme:uninstall": "evershop theme:uninstall",
"theme:export-content": "evershop theme:export-content"
}
}:::info
The six theme:* scripts are required if you plan to work with themes — creating one, activating it, or overriding its components. The theme documentation refers to them by their npm run names. See the Command-Line Interface reference for what each one does and which flags it accepts.
When you pass flags through npm run, separate them with --, e.g. npm run theme:active -- boutique --dry-run.
:::
Step 3: Run the installation script
Before running this script, make sure that you have an empty Postgres database ready for EverShop.
:::info
Please check this document for the system requirements list.
:::
The interactive setup wizard will prompt you for:
- Database connection details — host, port, database name, user, and password
- Admin user details — full name, email, and password (minimum 8 characters, must contain at least one letter and one digit)
Once you provide this information, the setup script will:
- Create a
.envfile in your project root with the database connection settings - Create the
mediaandpublicdirectories - Set up the database schema (create all required tables)
- Run all database migrations
- Create your administrator account
npm run setup:::info
The .env file created by the setup script contains your database credentials:
DB_HOST="localhost"
DB_PORT="5432"
DB_NAME="evershop"
DB_USER="postgres"
DB_PASSWORD="your_password"
DB_SSLMODE="disable"You can edit this file later to change your database connection settings. See the Database documentation for more options including SSL configuration.
:::
Step 4: Folder permission
EverShop needs to write some files to the disk. So you need to make sure that the following folders have the write permission:
public/.evershop.logmedia
Step 5: Run the `build` command to build the site
npm run buildStep 6: Run the `start` command to start your store in production mode
npm run startYour site will start at http://localhost:3000.
Admin panel can be accessed at http://localhost:3000/admin.
:::info
By default EverShop will start at port 3000. You can change the port by setting the PORT environment variable.
:::
Configuring Your Store
Most of what a merchant changes day to day is not a config.json key. Currency, timezone, weight and length units, store languages, branding (logo, favicon), catalog display options, tax rounding and the cloud file-storage provider are all stored in the database and edited from Admin → Settings. They take effect without a restart and without a rebuild.
config/<env>.json stays for deployment concerns: database credentials, the active theme, session secrets and extension wiring — things a developer sets and a redeploy changes.
See Store Settings for the full split, the getSetting / getSettingSync API, and how to migrate extension code that still reads a config key.
Upgrade EverShop
To upgrade EverShop to the latest version:
npm install @evershop/evershop@latestnpm run buildnpm run startDatabase migrations are run automatically when the application starts, so any schema changes in the new version are applied for you.
:::warning Major upgrades can break themes and extensions
A minor or patch upgrade is normally a drop-in replacement. A major upgrade is not: the core React version, component APIs and public import paths can change, and your custom theme and extensions run against them.
Before upgrading a live store, read the release notes for the version you are moving to, upgrade a staging copy first, and take a database backup — migrations are applied automatically on the first start and are not reversible.
The most recent example is EverShop 2.2, which moved the framework from React 17 to React 19. Most of those changes fail silently at runtime rather than at build time. See Upgrading To React 19 for the full list of breaking changes and the fix for each one.
:::
Demo Data Seeding
After installation, you can populate your store with demo data using the seed command. This is useful for development and testing.
npm run seedThis command will populate your store with sample products, categories, and other data.
:::info
Demo data seeding is intended for development and testing environments only. Do not use it in production.
:::
For Developers
If you are a developer and want to start the project in development mode, there are some additional steps you need to follow.
Install Additional Dev Dependencies
To run the project in development mode, you need to install additional dependencies. These are not required for production but are essential for development:
npm install --save-dev @types/node typescript @parcel/watcher @types/config @types/express @types/pg @types/react execaAdding the workspace configuration
Open the package.json and add the following configuration:
{
"workspaces": ["extensions/*", "themes/*"]
}This allows each extension and theme to function as an independent package with its own dependencies.
Start the project in development mode
npm run devYour store will start at http://localhost:3000 with hot-reloading enabled. Changes to React components update instantly in the browser.
The debug mode
The start:debug script (already added in Step 2) starts the production server with debug logging enabled:
npm run start:debugThis outputs detailed middleware execution timings and request processing information.
:::info
Debug mode is enabled by default when you run the dev command, so you don't need to use start:debug during development.
:::
Dockerize Your Project
Dockerizing your EverShop project allows you to create a portable, self-contained environment for your application. Below is a guide to creating your own Docker image and managing persistent data like media files.
Handling Persistent Media Files
When you dockerize your application, it's crucial to handle the media directory correctly. This directory contains user-uploaded files (like product images) and should not be copied directly into the Docker image. If you do, any uploaded files will be lost whenever the container is rebuilt.
There are two primary approaches for managing media files:
Docker Volumes: This approach mounts a directory from your host machine into the container. Files are stored on the host, so they persist even if the container is removed. This is suitable for single-server deployments or local development.
Cloud Storage: This approach uses a cloud storage service like Amazon S3, Azure Blob Storage or Google Cloud Storage. This decouples file storage from your application container entirely, which is essential for auto-scaling environments and provides better durability and performance.
:::caution Production Recommendation
For any production environment, we strongly recommend using cloud storage. This approach is more scalable, reliable, and secure than using local volumes. It prevents data loss, simplifies backups, and is essential if you ever plan to run your application across multiple servers.
Amazon S3, Azure Blob Storage and Google Cloud Storage are built into EverShop core — there is no extension to install. Pick the provider in Admin → Settings → File Storage and fill in its credentials there, or pin them from config/<env>.json or environment variables when your infrastructure manages them. The S3 provider also drives any S3-compatible service (Cloudflare R2, MinIO, DigitalOcean Spaces) through a custom endpoint.
See File Storage for the provider settings, the credential precedence rules, and how to register a provider of your own.
:::
1. Building Your Docker Image
Here is an example Dockerfile. Note that we have removed the COPY media ./media line.
FROM node:20-alpine
WORKDIR /app
RUN npm install -g npm@10
COPY package*.json .
# Copy your custom theme.
COPY themes ./themes
# Copy your custom extensions.
COPY extensions ./extensions
# Copy your config.
COPY config ./config
# DO NOT copy the media folder. It will be handled by a volume.
# Copy your public files.
COPY public ./public
# We must copy translations to the image as they are required for the build.
COPY translations ./translations
# Run npm install.
RUN npm install
# Build assets.
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "start"]2. Using Docker Compose with a Volume
This docker-compose.yml file demonstrates how to build the image and attach a volume for the media directory.
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
restart: always
volumes:
- ./media:/app/media # Mounts the host's media folder to the container
environment:
DB_HOST: database
DB_PORT: 5432
DB_PASSWORD: postgres
DB_USER: postgres
DB_NAME: postgres
networks:
- myevershop
depends_on:
- database
ports:
- 3000:3000
#The postgres database:
database:
image: postgres:16
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: postgres
ports:
- "5432:5432"
networks:
- myevershop
networks:
myevershop:
name: MyEverShop
driver: bridge
volumes:
postgres-data:With this configuration, any files uploaded to your application will be saved in the media folder on your host machine, ensuring they are not lost.
import Sponsors from '@site/src/components/Sponsor';
File: docs/development/getting-started/introduction.md
sidebar_position: 1
keywords:
- EverShop introduction
- Node ecommerce
- React ecommerce
- GraphQL ecommerce
- TypeScript ecommerce
- open-source ecommerce
- e-commerce platform
sidebar_label: Introduction
title: 'EverShop: Open-Source Node.js & TypeScript Ecommerce Platform'
EverShop: An Open-Source TypeScript Ecommerce Platform
Welcome to EverShop, a modern, open-source Node.js ecommerce platform built with TypeScript, React, and GraphQL.
EverShop is designed to help developers create powerful and flexible ecommerce stores. It features a modular architecture that allows for extensive customization of both the admin panel and the front store, giving you complete control over your application.
EverShop is free and open-source. The source code is available on GitHub.
Core Philosophy
- Developer-Centric: Built by developers, for developers. EverShop provides a flexible and powerful foundation to build upon.
- Open Source: We believe in the power of community. EverShop is free, open-source, and ready for you to make it your own.
- Performance: With server-side rendering and a modern stack, EverShop is designed for speed and a great user experience.
Key Features
- Modern Tech Stack: Built with Node.js, TypeScript, React, and GraphQL for a powerful and scalable foundation.
- Modular Architecture: Extend and customize your store with a flexible module system. Add new features without touching the core code.
- Extensible Admin Panel: A fully-featured admin panel, inspired by leading platforms, that you can easily customize.
- Customizable Storefront: The frontend is designed for deep customization through a powerful theme system.
- GraphQL API: A flexible and powerful GraphQL API for both admin and storefront operations.
What's Included
EverShop ships with a complete set of core modules out of the box:
| Module | Description |
|---|---|
| Catalog | Products, categories, attributes, collections, product search, and recommendations |
| Checkout | Shopping cart, shipping providers and the built-in Core provider, multi-country shipping zones, shipping methods and rates, package/parcel packing, payment method registration |
| Customer | Customer accounts, addresses, authentication (JWT) |
| OMS | Order management, multi-shipment fulfillment, carriers and labels, payment/shipment status tracking |
| Promotion | Coupon codes, discount rules, and landing pages |
| CMS | Custom pages, file storage, and widget definitions and storage |
| Page Builder | The visual editing experience for widgets — placements, drafts, changesets, and scheduled rollouts. Widgets are stored by CMS; they are edited here. |
| Blog | Blog posts, categories, tags, comments, and reactions |
| Tax | Tax classes, rates, and calculation |
| Setting | Store configuration management (the database-backed admin settings) |
| Auth | Admin login, JWT and session handling, and route access control |
| Base | Cross-cutting infrastructure — metafields, sitemap, URL redirects, rate limiting, and the shared error/response middleware |
| GraphQL | Schema assembly, query building, and the storefront and admin GraphQL endpoints |
Payment gateways (Stripe, PayPal, Cash on Delivery) are included as built-in modules and can be enabled through configuration.
Technology Stack
EverShop's highly modular structure is a result of several open source technologies embedded into a stack. These open source technologies are composed of the following components:
TypeScript
EverShop is written in TypeScript, a statically typed superset of JavaScript. This provides better tooling, scalability, and code quality.
Node.js
Node.js is an open-source server side runtime environment built on Chrome's V8 JavaScript engine. It provides an event driven, non-blocking (asynchronous) I/O and cross-platform runtime environment for building highly scalable server-side application using JavaScript.
PostgreSQL
PostgreSQL is a powerful, open source object-relational database system. It has more than 15 years of active development and a proven architecture that has earned it a strong reputation for reliability, data integrity, and correctness. EverShop requires PostgreSQL 13 or higher.
React
React is a free and open-source frontend JavaScript library for building user interfaces based on UI components. EverShop implements server-side rendering of React components with hydration to provide a fast, performant experience and SEO optimization.
GraphQL
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. EverShop uses GraphQL and React to build a flexible and extensible frontend.
:::info
Check this document for more detail about system requirement of EverShop.
:::
Extensible Admin Panel
Inspired by leading ecommerce platforms, EverShop includes a fully-featured admin panel built with React. It provides all the tools you need to manage your products, orders, and customers.
The admin panel is also designed to be extensible and customizable. Developers can easily add new features to the admin panel without modifying the core source code.
Customizable Storefront
The EverShop frontend is designed to optimize storefront customization, with highly extensible themes being the central customization mechanism.
:::info
Check out the theme documentation here.
:::
Demo Store
Explore our demo store.
import Link from '@docusaurus/Link';
Admin Panel Front Store:::info
The demo admin account email is [email protected] and the password is 123456
:::
Next Steps
Ready to get started? Here's the recommended path:
- System Requirements — Ensure your system is ready
- Installation Guide — Install and run your store
- Architecture Overview — Understand how EverShop works
- Extension Development — Build your first extension
- Theme Overview — Customize the storefront
Community
We invite you to join our Discord channel for project discussion. Thank you!
import Sponsors from '@site/src/components/Sponsor';
File: docs/development/getting-started/system-requirements.md
sidebar_position: 5
keywords:
- EverShop system requirements
- Node.js requirements
- PostgreSQL requirements
- Server requirements
sidebar_label: System Requirements
title: System Requirements
System Requirements
Before installing EverShop, ensure your system meets the following requirements. This guide covers the necessary software, hardware, and environment specifications for optimal performance.
Overview
EverShop is a modern ecommerce platform built on Node.js and PostgreSQL. It requires specific system configurations to run efficiently and securely.
Operating System
EverShop is cross-platform compatible and can run on:
- Linux (Ubuntu 20.04 LTS or later, CentOS 8+, Debian 10+)
- macOS (10.15 Catalina or later)
- Windows (10 or later, Windows Server 2019+)
:::tip Recommended
For production environments, we recommend using Linux-based systems (Ubuntu Server or CentOS) for better performance and stability.
:::
Software Requirements
Node.js
Required Version: Node.js 20.9 or higher
- Download from nodejs.org
- We recommend using the LTS (Long Term Support) version
- Verify installation:
node --version - EverShop 2.2.2 and later need Node.js 20.9 or newer, because the image library it uses (
sharp0.35) requires it
:::info Tested versions
EverShop's continuous integration builds and runs the test suite on Node.js 20 and Node.js 22. Both are supported; either one is a safe choice for production. Newer majors may work but are not covered by CI.
:::
:::caution Version Compatibility
Node.js versions below 20.9 are not supported and may cause compatibility issues.
:::
NPM (Node Package Manager)
Required Version: NPM 9.x or higher
- NPM is included with Node.js installation
- Verify installation:
npm --version - Update NPM if needed:
npm install npm@latest
PostgreSQL Database
Required Version: PostgreSQL 13 or higher
- Download from postgresql.org
- Recommended: PostgreSQL 15+ for improved performance
- Verify installation:
psql --version
Database Configuration Requirements:
- An empty PostgreSQL database for EverShop
- Database user with full privileges (CREATE, ALTER, DROP, INSERT, UPDATE, DELETE)
- UTF-8 character encoding
- Recommended extensions:
pg_trgmfor full-text search
:::info Database Setup
During installation, EverShop will automatically create the necessary database schema and tables. Ensure your database user has sufficient privileges.
:::
Hardware Requirements
Minimum Requirements
Suitable for development and testing environments:
- CPU: 2 cores
- RAM: 2 GB
- Storage: 10 GB available disk space
- Network: Stable internet connection for package installation
Recommended Requirements
For production environments and optimal performance:
- CPU: 4+ cores
- RAM: 4 GB or more
- Storage: 20 GB+ SSD storage
- Network: High-speed internet connection with low latency
:::tip Performance
For stores with high traffic or large product catalogs (10,000+ products), consider scaling up to 8 GB RAM and using SSD storage for better database performance.
:::
Development Environment (Optional)
For developers working on EverShop customization or extension development:
Required Tools
- Git (version 2.x or higher) - for version control
- Code Editor - VS Code, Sublime Text, or similar
- Docker (optional) - for containerized development
Additional Dependencies
When running EverShop in development mode, you'll need to install additional dev dependencies manually:
npm install --save-dev @types/node typescript @parcel/watcher @types/config @types/express @types/pg @types/react execa:::warning @types/react must be version 19
EverShop runs on React 19. @types/react must resolve to 19.x — if npm hoists an older @types/react@18 (usually pulled in by another dependency), your theme's tsc build fails with JSX type errors such as "JSX element type ... is not a constructor function for JSX elements" or "'Component' cannot be used as a JSX component".
Pin it explicitly, and check what actually resolved:
npm install --save-dev @types/react@19 @types/react-dom@19
npm ls @types/reactIf npm ls still reports an 18.x copy somewhere in the tree, add an overrides entry to your package.json:
{
"overrides": {
"@types/react": "^19.1.2",
"@types/react-dom": "^19.0.0"
}
}:::
See the Installation Guide for the complete development setup steps.
Network and Security Requirements
Firewall Configuration
Ensure the following ports are accessible:
- Port 3000 - Default EverShop application port (configurable)
- Port 5432 - PostgreSQL database port (if remote database)
- Port 443 - HTTPS (for production with SSL)
- Port 80 - HTTP (redirects to HTTPS)
SSL/TLS Certificate
For production environments:
- Valid SSL/TLS certificate (Let's Encrypt, commercial CA, or similar)
- Configured reverse proxy (Nginx, Apache, or similar)
Browser Requirements
Admin Panel
The EverShop admin panel supports:
- Chrome/Edge (latest 2 versions)
- Firefox (latest 2 versions)
- Safari (latest 2 versions)
Storefront
The customer-facing store supports:
- Modern browsers with ES6+ support
- JavaScript enabled
- Cookies enabled
Docker Requirements (Optional)
If using Docker for deployment:
- Docker: 20.10 or higher
- Docker Compose: 2.0 or higher
- Sufficient resources allocated to Docker containers (2GB+ RAM)
Cloud Platform Compatibility
EverShop can be deployed on various cloud platforms:
- AWS (EC2, ECS, Elastic Beanstalk)
- Google Cloud Platform (Compute Engine, Cloud Run)
- Microsoft Azure (Virtual Machines, App Service)
- DigitalOcean (Droplets, App Platform)
- Heroku
- Railway
:::info
EverShop requires a persistent Node.js server process with PostgreSQL access. It is not compatible with serverless platforms (e.g., Vercel, Netlify Functions) that don't support long-running processes or sessions.
:::
Checking Your System
Before installing EverShop, verify your system meets the requirements:
Check Node.js Version
node --version
# Should output: v20.x.x or higherCheck NPM Version
npm --version
# Should output: 9.x.x or higherCheck PostgreSQL Version
psql --version
# Should output: psql (PostgreSQL) 13.x or higherCheck Available Disk Space
Linux/macOS:
df -hWindows:
wmic logicaldisk get size,freespace,captionNext Steps
Once your system meets all requirements:
- Proceed to the Installation Guide
- Set up your PostgreSQL database
- Install EverShop using your preferred method
Troubleshooting
Common Issues
Node.js version mismatch:
- Use nvm (Node Version Manager) to manage multiple Node.js versions
- Switch to Node.js 20:
nvm install 20 && nvm use 20
PostgreSQL connection issues:
- Verify PostgreSQL service is running:
systemctl status postgresql(Linux) - Check database connection settings in EverShop configuration
- Ensure firewall allows PostgreSQL connections
Insufficient permissions:
- On Linux/macOS, you may need to use
sudofor global npm installations - Consider using nvm to avoid permission issues
Support
If you encounter issues with system requirements:
- Check our GitHub Issues
- Join our Discord Community
- Review the Documentation
File: docs/development/knowledge-base/api-routes.md
sidebar_position: 28
keywords:
- RESTful API
- API endpoints
- API authentication
- Request validation
sidebar_label: RESTful API Routes
title: RESTful API Routes
RESTful API Routes
EverShop provides a comprehensive system for building RESTful API endpoints that power your store's backend functionality. This document explains how to create, structure, and secure these endpoints.
:::info
Before diving into API routes, it's recommended to familiarize yourself with the extension structure to understand how extensions are organized in EverShop.
:::
API Organization
EverShop's RESTful APIs are located in the api folder of each module. Below is an example of the API structure in the catalog module:
catalog
├── api
│ ├── createProduct
│ │ ├── ... // Middleware functions
│ │ └── route.json
│ ├── updateProduct
│ │ ├── ... // Middleware functions
│ │ └── route.json
│ ├── createCategory
│ │ ├── ... // Middleware functions
│ │ └── route.json
│ ├── updateCategory
│ │ ├── ... // Middleware functions
│ │ └── route.json
│ ├── createAttribute
│ │ ├── ... // Middleware functions
│ │ └── route.json
│ └── updateAttribute
│ ├── ... // Middleware functions
│ └── route.jsonAPI Folder Structure
The api folder contains the following organization:
global- Contains middleware functions that apply to all API endpoints. For example, theauthmiddleware that handles authentication for all secured endpoints.<apiID>- Individual folders representing specific API endpoints. For example, thecreateProductfolder contains all the components needed for the product creation API.
Single API Endpoint Structure
Each API endpoint folder contains middleware functions and a route definition file. Here's an example structure:
├── createProduct
│ ├── [context]bodyParser[auth].ts
│ ├── saveProduct.ts
│ └── route.jsonThe middleware functions handle the request processing, while the route.json file defines how the endpoint is accessed.
API Route Definition
Every API endpoint requires a route.json file that specifies the HTTP methods, path, and access restrictions. For example, here's the route definition for a product deletion API:
{
"methods": ["DELETE"],
"path": "/products/:id",
"access": "private"
}:::warning
The folder name is used as the routeId, so ensure each folder name is unique and contains only URL-safe characters. The routeId is important for correctly routing and identifying API endpoints.
:::
:::info
For more detailed information on routing patterns and options, refer to the routing system documentation.
:::
API Authentication
The access property in the route.json file controls endpoint security:
public- The endpoint can be accessed without authenticationprivate- The endpoint requires authentication (admin user credentials)
:::warning
For security best practices, endpoints without an explicitly defined access property are treated as private by default, requiring authentication to access.
:::
API Request Data Validation
EverShop allows you to define validation schemas for API request data using a payloadSchema.json file in the API endpoint folder. This ensures that incoming data meets your requirements before processing.
Here's an illustrative schema — it is not the schema EverShop ships for createProduct, which declares only description, metafields and duplicate_of and has no required array. It does demonstrate the two conventions the real file uses: skipEscape: true and per-property errorMessage.
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"short_description": {
"type": "string"
},
"url_key": {
"type": "string",
"pattern": "^\\S+$"
},
"meta_title": {
"type": "string"
},
"meta_description": {
"type": "string"
},
"meta_keywords": {
"type": "string"
},
"status": {
"type": ["integer", "string"],
"enum": [0, 1, "0", "1"]
},
"sku": {
"type": "string"
},
"price": {
"type": ["string", "number"],
"pattern": "^\\d+(\\.\\d{1,2})?$"
},
"weight": {
"type": ["string", "number"],
"pattern": "^[0-9]+(\\.[0-9]{1,2})?$"
},
"qty": {
"type": ["string", "number"],
"pattern": "^[0-9]+$"
},
"manage_stock": {
"type": ["string", "number"],
"enum": [0, 1, "0", "1"]
},
"stock_availability": {
"type": ["string", "number"],
"enum": [0, 1, "0", "1"]
},
"group_id": {
"type": ["string", "integer"],
"pattern": "^[0-9]+$"
},
"visibility": {
"type": ["integer", "string"],
"enum": [0, 1, "0", "1"]
},
"images": {
"type": "array",
"items": {
"type": "string"
}
},
"attributes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"attribute_code": {
"type": "string"
},
"value": {
"type": ["string", "array"],
"items": {
"type": "string"
}
}
}
}
},
"categories": {
"type": "array",
"items": {
"type": ["string", "integer"],
"pattern": "^[0-9]+$"
}
},
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"option_name": {
"type": "string"
},
"option_type": {
"type": "string",
"enum": ["select", "multiselect"]
},
"is_required": {
"type": ["string", "integer"],
"enum": [0, 1, "0", "1"],
"default": 0
},
"values": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "string"
},
"extra_price": {
"type": ["string", "number"],
"pattern": "^\\d+(\\.\\d{1,2})?$"
}
}
}
}
},
"required": ["option_name", "option_type", "values"],
"additionalProperties": true
}
}
},
"required": [
"name",
"meta_title",
"url_key",
"status",
"sku",
"qty",
"price",
"weight",
"group_id",
"visibility"
],
"additionalProperties": true
}When a request is made to the API endpoint, the request data is automatically validated against this schema. If validation fails, an appropriate error response is sent to the client with details about the validation issues.
:::info
EverShop uses Ajv JSON schema validator for request payload validation, providing robust and flexible validation capabilities.
:::
API Middleware Functions
EverShop's API endpoints are powered by middleware functions that process requests sequentially. You can create as many middleware functions as needed for each API endpoint, allowing for modular and maintainable code.
:::info
For more details on how middleware functions work in EverShop, refer to the middleware system documentation.
:::
Sharing Middleware Across Endpoints
For efficiency, you may need to reuse middleware functions across multiple API endpoints. EverShop provides a convenient way to share middleware:
Endpoint-Specific Sharing: Create a special folder named after the endpoints that should share middleware (e.g.,
createProduct+updateProduct) in theapidirectory. Place shared middleware functions in this folder, and they'll be executed for both endpoints.Global Sharing: For middleware that should run for all API endpoints, place it in the
api/globalfolder.
These special shared middleware folders should contain only middleware functions without a route.json file, as they don't define endpoints themselves but enhance existing ones.
Rate Limiting
Every API endpoint sits behind a per-client-IP rate limiter that is mounted globally, before any module middleware runs. You do not opt in, and you cannot opt out from a route.json or a middleware.
| Scope | Limit (per IP) |
|---|---|
/api and /api/** |
120 requests per minute |
Credential endpoints — POST to /api/customers, /api/customers/reset-password, /api/customers/password, /customer/login, /admin/user/login |
8 requests per 15 minutes |
| Page routes (storefront and admin) | 300 requests per minute |
Requests for static assets, HMR, /backend/, /health and /healthz are exempt. The limiter is disabled when NODE_ENV=test.
The 429 Contract
When a limit is exceeded, an API request gets 429 Too Many Requests with EverShop's standard error envelope:
{
"error": {
"status": 429,
"message": "Too many requests. Please slow down and try again later."
}
}Two response headers matter to clients:
Retry-After— seconds until the window resets. Honor it rather than retrying immediately.RateLimit-*— the standard limit/remaining/reset triple, so a well-behaved client can back off before it gets rejected.
Page routes get the same status with a plain-text body instead of JSON.
:::warning
Behind a reverse proxy, the limiter only sees the true client IP if Express trust proxy matches your topology. Set the TRUST_PROXY_HOPS environment variable to the number of proxy layers in front of the app (default 1); otherwise all traffic looks like one IP and a single visitor can lock everyone out.
:::
Best Practices for API Development
When developing APIs for EverShop, consider these best practices:
- Proper Validation: Always define comprehensive validation schemas for request data
- Access Control: Set appropriate
accesslevels for your endpoints - Modular Design: Break complex operations into multiple middleware functions
- Error Handling: Provide clear error responses with appropriate HTTP status codes
- Handle 429s: Clients should respect
Retry-Afterand theRateLimit-*headers rather than retrying in a tight loop - Documentation: Comment your code and keep API contracts consistent
File: docs/development/knowledge-base/architecture-overview.md
sidebar_position: 10
keywords:
- EverShop architecture
- Node.js architecture
- TypeScript ecommerce
- Modular monolith
- Project structure
- Module system
- Directory organization
sidebar_label: Architecture Overview
title: 'Architecture Deep Dive: The EverShop Platform'
Architecture Deep Dive
Welcome to the architectural overview of EverShop. This document provides a deep dive into the core concepts, design patterns, and structure that make EverShop a flexible and powerful ecommerce platform.
EverShop is designed as a modular monolith. It combines the simplicity of a single, deployable application with the flexibility and separation of concerns of a modular system. Built with Node.js, TypeScript, React, and GraphQL, it provides a robust foundation for developers.
Core Architectural Concepts
EverShop's architecture is built on a few key principles:
- Modular Monolith: EverShop is a single application, but its internal structure is composed of decoupled modules. This design simplifies development and deployment while still allowing for clean separation of features.
- API-Driven: All communication, whether from the frontend to the backend or between internal components, occurs through well-defined APIs. EverShop provides both GraphQL and RESTful endpoints.
- Everything is a Module: All business logic in EverShop is organized into modules. This includes core features like
catalogandcheckout, as well as custom extensions you create. - Extensibility by Default: The system is designed to be extended. You can add new features, override existing components, and tap into the event system without modifying core files.
The Module System
The module is the fundamental building block of EverShop. Each module is a self-contained package that encapsulates a specific business feature. It includes all the necessary components for that feature, such as API endpoints, services, and UI components.
This modular approach ensures that features are decoupled, making the system easier to maintain, scale, and extend.
Request Lifecycle
Understanding the request lifecycle is key to understanding how EverShop works. When a request enters the system, it flows through a series of layers before a response is sent back.
- HTTP Request: A request is received by the web server.
- Middleware Pipeline: The request passes through a pipeline of middleware functions. This includes core middleware (for sessions, authentication, etc.) and middleware added by modules.
- Routing: The router directs the request to the appropriate controller or GraphQL resolver within a specific module based on the URL and HTTP method.
- Controller/Resolver: The controller or resolver processes the request, validates input, and calls the necessary services.
- Service Layer: Services contain the core business logic. They interact with the database and other resources to perform operations.
- Database: Data is persisted and retrieved from the PostgreSQL database.
- Response: The controller or resolver generates a response. This can be a JSON response for an API request or an HTML page rendered using React for a web page request.
Folder Structure
It's important to distinguish between the source code structure (how EverShop itself is organized) and the project structure (the layout of a deployed EverShop instance).
Project Structure (Your Store)
When you create a new EverShop project, it will have a simplified structure designed for you to build upon. This is the structure you will work with for your own store.
├── .env # Database credentials and other environment variables
├── .evershop # Built files and cache
├── config
│ ├ default.json # Your custom configuration
├── extensions # Your custom modules
├── media # Uploaded files (images, etc.)
├── public # Static assets (CSS, JS, images)
├── node_modules # Project dependencies
├── themes # Your custom themes
├── translations # Locale dictionaries (one folder per locale, CSV files)
└── package.json.envFile: Holds environment variables — most importantly the PostgreSQL connection (DB_HOST,DB_USER, …), plusPORT,EVERSHOP_HOME_URLandTRUST_PROXY_HOPS. It is read directly by the code; it is not merged into thenode-configtree..evershopDirectory: Contains built files optimized for production, including compiled React components and other assets. This directory is generated by thebuildcommand.configDirectory: Houses your custom configuration files. You can override default settings here. See the Configuration Guide for more details.extensionsDirectory: This is where you place your custom modules to extend EverShop's functionality.mediaDirectory: Stores all uploaded files, such as product images and other media assets.publicDirectory: Contains static assets like CSS, JavaScript, and images that are served directly to clients.node_modulesDirectory: Contains all project dependencies, including the coreevershoppackage.themesDirectory: This directory holds your custom themes, which control the look and feel of your storefront. See the Theme Overview for more information.translationsDirectory: One folder per locale (17 ship with EverShop:de,el,es,fa,fr,hu,it,mn,nb,ne,nl,pt,ru,sr,ta,vi,zh), each holding*.csvdictionaries that are read from disk at boot and held in memory. Add or override translations here. See Translation.
:::info
Most store settings no longer live in config/default.json. Currency, units, store identity, catalog behavior, tax rounding, and the storefront logo are all database-backed admin settings (the setting table), edited under Settings in the admin panel; the config file only provides fallbacks. See Store Settings.
:::
Managing Dependencies with NPM Workspaces
For advanced development, both the extensions and themes directories can be managed as NPM workspaces. This allows each extension or theme to have its own package.json file and manage its own dependencies.
To enable this, add the workspaces property to your root package.json:
{
"name": "my-evershop-store",
"version": "1.0.0",
"workspaces": [
"extensions/*",
"themes/*"
]
}With this configuration, you can structure your extensions like this:
├── extensions
│ ├── my-custom-extension
│ │ ├── src/
│ │ │ └── bootstrap.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── another-extension
│ ├── src/
│ │ └── bootstrap.ts
│ ├── package.json
│ └── tsconfig.jsonWhen you run npm install in the project root, NPM will install the dependencies for all your extensions and themes and symlink them in the root node_modules directory. This is a powerful way to manage complex projects with multiple custom modules.
Bootstrap Lifecycle
Understanding the startup sequence is essential for knowing when and where to register your extension points. Here is the complete lifecycle:
- Create Express App — The HTTP application is initialized.
- Load Modules — Core modules (catalog, checkout, customer, etc.) and enabled extensions are discovered.
- Scan Routes & Middleware — Route definitions (
route.json) and middleware files are scanned from all modules and extensions. - Execute Bootstrap Scripts — Each module's
bootstrap.tsis executed sequentially. This is where modules register processors, hooks, widgets, and cron jobs. - Lock Hooks, Registry & Carrier Registry — After all bootstrap scripts run, the hook system, the registry and the carrier registry are locked (
lockCarrierRegistry()). No new hooks, processors or carriers can be added after this point. This ensures the extension pipeline is stable during request handling. - Validate Configuration — The configuration is validated against the merged JSON schema.
- Run Migrations — Pending database migrations from all modules are executed within transactions.
- Provision Active Theme Metafields —
provisionActiveThemeMetafields()re-applies the active theme'smetafieldDefinitionsfromtheme.json. It is idempotent and runs in the main web process only. - Start HTTP Server — The Express app begins listening for requests.
- Start Child Processes — Two separate child processes are spawned after the server is listening:
- Cron Process — Handles scheduled jobs.
- Event Process — Handles asynchronous event subscribers.
Extension Points
EverShop provides four primary extension mechanisms, all registered during the bootstrap phase:
| Mechanism | Purpose | Documentation |
|---|---|---|
| Registry & Processors | Transform data values across modules (cart fields, email services, config schemas) | Registry and Processors |
| Hooks | Run code before or after specific function calls | hookable |
| Events & Subscribers | React to actions asynchronously (product created, order placed) | Events and Subscribers |
| Middleware | Add request-processing logic to routes | Middleware System |
TypeScript and the Build Process
EverShop source code is written in TypeScript. All .ts and .tsx files in the src/ directory are compiled to JavaScript in the dist/ directory before the application runs. In development mode, EverShop handles TypeScript compilation automatically. In production, you must compile your extensions and themes before deployment.
This means:
- You write code in
src/as TypeScript. - The runtime loads compiled JavaScript from
dist/. - Core modules are published as pre-compiled JavaScript in
node_modules/@evershop/evershop/dist/.
File: docs/development/knowledge-base/blog.md
sidebar_position: 55
keywords:
- blog
- blog post
- blog category
- blog tag
- blog comment
- reactions
- rss
sidebar_label: Blog
title: Blog Module
Blog Module
The blog module is a core EverShop module (packages/evershop/src/modules/blog) that adds posts, categories, tags, comments, and reactions to the storefront. It is enabled by default — there is nothing to install. Like every other core module it owns its own migration, GraphQL types, admin pages, storefront pages, REST endpoints, and a bootstrap.ts that registers a widget, sitemap collectors, URN link loaders, and collection filters.
This page describes how the module is wired. For the HTTP surface, see the Blog REST API.
Storefront routes and friendly URLs
The module declares five storefront routes. Two of them are addressable directly; the other three are only ever reached through a url_rewrite row.
| Route ID | Declared path | Friendly URL | Editable |
|---|---|---|---|
blogHome |
/blog |
/blog |
Yes |
blogRss |
/blog/rss.xml |
/blog/rss.xml |
No |
blogPostView |
/blogPost/:uuid |
/blog/<slug> |
Yes |
blogCategoryView |
/blogCategory/:uuid |
/blog/category/<slug> |
Yes |
blogTagView |
/blogTag/:uuid |
/blog/tag/<slug> |
Yes |
editable: true means the page participates in the page builder, so widgets can be placed on it.
How the friendly URLs are built
Post, category, and tag URLs are not hardcoded in the router. They come from rows in the shared url_rewrite table, written by event subscribers when the entity is created or updated and removed when it is deleted:
| Event | Subscriber | Effect |
|---|---|---|
blog_post_created / blog_post_updated |
subscribers/blog_post_*/buildUrlRewrite.ts |
Upserts /blog/{url_key} → /blogPost/{uuid} |
blog_category_created / blog_category_updated |
subscribers/blog_category_*/buildUrlRewrite.ts |
Upserts /blog/category/{url_key} → /blogCategory/{uuid} |
blog_tag_created / blog_tag_updated |
subscribers/blog_tag_*/buildUrlRewrite.ts |
Upserts /blog/tag/{url_key} → /blogTag/{uuid} |
blog_post_deleted / blog_category_deleted / blog_tag_deleted |
subscribers/*_deleted/deleteUrlRewrite.ts |
Deletes the row by entity_uuid + entity_type |
The upsert uses entity_uuid as the conflict key, so renaming a post replaces its request_path rather than accumulating stale rewrites. Every subscriber is a no-op when url_key is missing, and every one swallows its own errors through error() so a rewrite failure never rolls back the entity write — the entity is still reachable at its internal /blogPost/ path.
The entity_type values written are blog_post, blog_category, and blog_tag.
`/blog` is a reserved slug
assertUrlKeyAvailable (modules/base/services/assertUrlKeyAvailable.ts) refuses any url_key that matches a single-segment, non-admin, non-API storefront route. It builds that list dynamically from the live route table rather than from a hardcoded array:
const reserved = getRoutes()
.filter((r) => !r.isAdmin && !r.isApi && /^\/[a-zA-Z0-9-]+$/.test(r.path))
.map((r) => r.path);Because blogHome declares /blog, creating a CMS page or a landing page with url_key: "blog" throws:
URL key "blog" is reserved by a system route and would be unreachable.This is not cosmetic — the route matcher runs before the url_rewrite fallback, so a CMS page at /blog would be permanently shadowed by the blog home page. Note that the other blog routes are not reserved: /blogPost/:uuid, /blogCategory/:uuid, and /blogTag/:uuid contain a parameter and /blog/rss.xml has two segments, so none of them match the single-segment pattern.
The RSS feed
blogRss is a plain middleware page with no React component. It selects the 20 most recent published posts, renders RSS 2.0 by hand, sets Content-Type: application/rss+xml; charset=utf-8 plus Cache-Control: public, max-age=3600, and calls response.send() without calling next() — which short-circuits the React render pipeline. Item links are built from the pretty path (/blog/).
Page context values
The storefront middlewares publish context so resolvers can find the current entity:
| Context key | Set by | Used by |
|---|---|---|
currentBlogPostId |
blogPostView |
currentBlogPost query |
currentBlogCategoryId |
blogCategoryView |
currentBlogCategory query |
currentBlogTagId |
blogTagView |
currentBlogTag query |
blogVisitor |
blogPostView |
BlogComment.liked |
blogPostReactedType |
blogPostView |
BlogPost.reactions[].reacted |
filtersFromUrl |
blogHome, blogCategoryView, blogTagView |
Collection paging / filtering |
All three view pages also set request.locals.pageBuilderEntityUrn to the entity's URN so page-builder content can be scoped per entity, and call setPageMetaInfo with breadcrumbs. blogPostView additionally emits Open Graph article metadata (publishedTime, authors, tags, image).
Each view page returns a 404 when the entity is missing or unpublished — blogPostView and blogCategoryView both add status = 1 to their lookup.
Admin routes
| Route ID | Path (auto-prefixed with /admin) |
|---|---|
blogPostGrid |
/blog/posts |
blogPostNew |
/blog/posts/new |
blogPostEdit |
/blog/posts/edit/:id |
blogCategoryGrid |
/blog/categories |
blogCategoryNew |
/blog/categories/new |
blogCategoryEdit |
/blog/categories/edit/:id |
blogTagGrid |
/blog/tags |
blogTagNew |
/blog/tags/new |
blogTagEdit |
/blog/tags/edit/:id |
blogCommentGrid |
/blog/comments |
Data model
Everything is created by a single migration, modules/blog/migration/Version-1.0.0.ts, in FK-dependency order.
| Table | Purpose | Notable columns |
|---|---|---|
blog_category |
Category record | uuid, status (default 1), comment_policy (default moderated), position, meta_data (JSONB) |
blog_category_description |
Translatable category fields | name, short_description, url_key (UNIQUE), meta_title, meta_description |
blog_tag |
Tag record (single table, no description split) | uuid, name, url_key (UNIQUE), meta_title, meta_description, created_at |
blog_post |
Post record | uuid, status (default 0), category_id, author_id, thumbnail, reaction_counts (JSONB), comment_count, reading_time, published_at, meta_data (JSONB) |
blog_post_description |
Translatable post fields | name, short_description, description (text — JSON blocks), url_key (UNIQUE), meta_title, meta_description |
blog_post_tag |
Many-to-many pivot | post_id, tag_id, UNIQUE on the pair |
blog_comment |
Comment, self-referencing for threads | uuid, post_id, parent_id, customer_id, name, email, comment, status (default pending), like_count |
blog_reaction |
Post reactions and comment likes | entity_type, entity_id, reaction_type, fingerprint, UNIQUE on (entity_type, entity_id, fingerprint) |
Two deliberate design decisions in the schema are worth knowing:
blog_post.author_idandblog_comment.customer_idcarry no database foreign key. Module migration order across modules is not guaranteed, so a hard FK toadmin_user/customercould run before those tables exist. The resolvers tolerate a missing or orphaned id and returnnull.blog_reactionis UNIQUE on(entity_type, entity_id, fingerprint)— the reaction type is deliberately excluded. A visitor therefore holds at most one reaction per post; picking a different type switches it rather than adding a second.
`url_key` triggers
The migration installs a blog-specific build_blog_url_key() PL/pgSQL function and attaches it as a BEFORE INSERT OR UPDATE trigger on blog_post_description, blog_category_description, and blog_tag. It slugifies NEW.name when url_key is NULL, and rejects an explicit url_key containing /, \ or #.
Unlike catalog's build_url_key, it does not append a random numeric suffix — blog slugs stay clean. The consequence is that two posts with the same name collide on the url_key UNIQUE constraint; the author must set an explicit url_key to disambiguate.
Denormalized counters
blog_post.comment_count, blog_post.reaction_counts, and blog_comment.like_count are caches. Every write path that can change them recomputes them from the source rows inside the same transaction — for example submitBlogComment, moderateBlogComment, and deleteBlogComment all re-run SELECT COUNT(*) ... WHERE status='approved', and reactToBlogPost rebuilds reaction_counts with jsonb_object_agg over blog_reaction.
Services, hooks, and events
The write services live in modules/blog/services/ and are re-exported from services/index.ts. Each follows the standard EverShop shape: a hookable-wrapped implementation, hookBefore* / hookAfter* helpers, a registry key for the data payload, and an event emitted after commit.
| Service | Registry key | Event emitted |
|---|---|---|
createBlogPost |
blogPostDataBeforeCreate |
blog_post_created |
updateBlogPost |
blogPostDataBeforeUpdate |
blog_post_updated |
deleteBlogPost |
— | blog_post_deleted |
createBlogCategory |
blogCategoryDataBeforeCreate |
blog_category_created |
updateBlogCategory |
blogCategoryDataBeforeUpdate |
blog_category_updated |
deleteBlogCategory |
— | blog_category_deleted |
createBlogTag |
blogTagDataBeforeCreate |
blog_tag_created |
updateBlogTag |
blogTagDataBeforeUpdate |
blog_tag_updated |
deleteBlogTag |
— | blog_tag_deleted |
Each create/update service also runs its AJV data schema through a registry key so extensions can widen it: createBlogPostDataJsonSchema, updateBlogPostDataJsonSchema, and the equivalent ...BlogCategory... / ...BlogTag... keys.
:::note
Unlike catalog, cms, oms, and the other core modules, the blog module has no @evershop/evershop/blog/services subpath in package.json as of v2.2.1. The hookBefore* / hookAfter* helpers exist and are re-exported from modules/blog/services/index.ts, but they are not reachable from an extension through a published import path. Extend blog writes through the registry keys and events above instead — both are public API.
:::
Adding a field to the post payload and reacting to the write, from an extension bootstrap.ts:
import { addProcessor } from '@evershop/evershop/lib/util/registry';
export default function bootstrap() {
addProcessor('blogPostDataBeforeCreate', async function (data) {
// `this` is the processor context — do not use an arrow function here.
return data;
});
addProcessor('createBlogPostDataJsonSchema', function (schema) {
schema.properties.reviewed_by = { type: ['string', 'null'] };
return schema;
});
}Both must be registered from bootstrap.ts — the registry locks once bootstrap finishes.
Two behaviours are shared by createBlogPost and updateBlogPost:
- Empty foreign keys are coerced. The admin form sends
''for an unselected category or author; both services rewrite''tonullbefore the insert, because the integer columns reject the empty string. published_atis stamped on first publish. Whenstatusis1andpublished_atis not already set, the service stamps the current ISO timestamp. There is no scheduler — a post is visible on the storefront whenstatus = 1, full stop.
updateBlogPost and updateBlogCategory tolerate a payload that touches only the base table: the description update is wrapped in a try/catch that swallows the query builder's No data was provided error.
Reading time
services/readingTime.ts exports a pure, side-effect-free readingTime(content, options). It walks the Editor.js document shape (rows → columns → data.blocks) that components/common/Editor.tsx renders and counts words per block type:
| Block type | Counted as |
|---|---|
paragraph, header, quote |
Words in data.text plus data.caption |
list |
Words across data.items |
raw |
Words in data.html (tags stripped) |
image |
An image, not words |
productList |
12 words per product |
Words are divided by wpm (default 200). Images add decaying time — the first image costs secondsPerImage (default 10), each subsequent image one second less, with a floor of 3 seconds. The result is Math.ceil of the total minutes, minimum 1.
Because it is pure, it runs inside the create/update transaction and its output is cached in blog_post.reading_time. The GraphQL resolver prefers the cached value and recomputes from description only when the cache is absent or non-positive:
readingTime: ({ readingTime: cached, description }) =>
typeof cached === 'number' && cached > 0 ? cached : readingTime(description)updateBlogPost recomputes reading_time only when description is supplied — an update that changes just the title leaves the cached value alone.
Comments and moderation
A comment's fate is decided by the category's comment_policy, not the post's:
comment_policy |
Result of submitBlogComment |
|---|---|
open |
Stored with status = 'approved' and immediately visible |
moderated (default) |
Stored with status = 'pending', hidden until an admin approves |
closed |
CommentsClosedError → the API responds 403 |
A post with no category falls back to moderated.
services/comment/submitBlogComment.ts treats the input as hostile, since comments are untrusted text rendered back to other visitors:
nameandcommentgo throughsanitizeHtmlwith no allowed tags or attributes, then whitespace is collapsed and the value is truncated (120 characters forname, 5000 forcomment). This is deliberately notsanitizeRawHtml, which is Editor.js-specific and permissive.- A hidden
websitefield acts as a honeypot. Humans never see it; a bot that fills it getsstatus = 'spam'. - A comment containing more than three
http(s)://occurrences is also auto-markedspam.
Spam comments are still stored — they are simply never shown. Only status = 'approved' comments reach the storefront.
moderateBlogComment(uuid, status) accepts exactly pending, approved, or spam and recomputes the post's comment_count in the same transaction. deleteBlogComment(uuid) deletes the comment (replies cascade via the parent_id FK), purges the comment's blog_reaction rows, and recomputes comment_count.
The BlogComment storefront resolver loads all approved comments for a post in one query and assembles the reply tree in JavaScript, avoiding N+1 recursion. Note that email is declared only on BlogComment.admin.graphql — it is write-only and never exposed on the storefront.
Reactions and reactor resolution
Reactions are visitor-scoped, not customer-scoped. The identity used is a fingerprint produced by services/reaction/resolveReactor.ts:
export function resolveReactor(
request: EvershopRequest,
response?: EvershopResponse,
issue = false
): string | null;The fingerprint is the value of a signed, httpOnly, sameSite: 'lax' cookie named blog_visitor with a one-year maxAge. There is no IP or user-agent hashing.
The issue flag is what separates the read path from the write path:
- Write path (
POST .../react,POST .../like) callsresolveReactor(request, response, true). If the cookie is absent, a freshrandomUUID()is generated and set on the response. - Read path (
blogPostViewGET) callsresolveReactor(request)withissuedefaulting tofalse, so a GET never sets a cookie. It returnsnullwhen no cookie exists, and the resolvers simply reportreacted: false/liked: false.
Reaction types are fixed lists in services/reaction/reactionTypes.ts:
export const REACTION_TYPES = ['like', 'love', 'clap', 'insightful'] as const;Posts accept all four. Comments only ever use 'like' — likeBlogComment is the degenerate single-type case and takes no type argument.
Toggle semantics differ slightly between the two:
reactToBlogPost(postId, type, fingerprint)— same type again removes the reaction (reacted: null); a different type updates the existing row in place (a visitor never holds two reactions on one post); no row yet inserts one. Returns{ counts, reacted }.likeBlogComment(commentId, fingerprint)— a pure toggle. Returns{ likeCount, liked }.
Both recompute the denormalized counter from blog_reaction inside the transaction.
GraphQL surface
The module ships storefront types plus .admin.graphql extensions. Remember that the two schemas build separately: a type defined in an .admin.graphql file is invisible to the storefront schema.
Queries
| Query | Returns | Notes |
|---|---|---|
blogPost(id: ID) |
BlogPost |
id is the post uuid |
blogPosts(filters: [FilterInput]) |
BlogPostCollection |
Non-admin callers are restricted to status = 1 |
currentBlogPost |
BlogPost |
Returns null unless the current route is blogPostView |
blogCategory(id: ID) |
BlogCategory |
|
blogCategories(filters: [FilterInput]) |
BlogCategoryCollection |
|
currentBlogCategory |
BlogCategory |
Bound to blogCategoryView |
blogTag(id: ID) |
BlogTag |
|
blogTags(filters: [FilterInput]) |
BlogTagCollection |
|
currentBlogTag |
BlogTag |
Bound to blogTagView |
blogComments(filters: [FilterInput]) |
BlogCommentCollection |
Admin only — declared in BlogComment.admin.graphql, powers the moderation grid |
featuredBlogsWidget(...) |
FeaturedBlogsWidget |
Backs the featured_blogs widget |
`BlogPost`
type BlogPost {
blogPostId: Int
uuid: String!
status: Int!
name: String!
urlKey: String!
shortDescription: String
description: JSON
thumbnail: String
publishedAt: String
readingTime: Int!
commentCount: Int!
reactions: [BlogReactionCount!]!
url: String!
category: BlogCategory
author: BlogAuthor
tags: [BlogTag!]!
related(limit: Int = 3): [BlogPost!]!
metaTitle: String
metaDescription: String
comments: [BlogComment!]!
}Resolver behaviour worth knowing:
urllooks up theurl_rewriterow and falls back to/blog/, then runs the result throughlocalizeUrlso the locale prefix is preserved.descriptionisJSON, not HTML. It is the Editor.js block array; the resolver parses the stored string and returns[]on malformed JSON.reactionsalways returns one entry per type inREACTION_TYPES, withcount: 0for types nobody has used, andreactedtrue for the type matchingblogPostReactedTypein context.related(limit)returns published posts in the same category, excluding the post itself, newest first.
The admin schema adds metaData: JSON, authorId, editUrl, updateApi, and deleteApi to BlogPost; editUrl / updateApi / deleteApi to BlogCategory and BlogTag; and moderateApi / deleteApi to BlogComment.
Collection filters
blogPosts supports these filter keys, plus the shared pagination filters (page, limit, od):
| Key | Operations | Effect |
|---|---|---|
name |
eq, like |
Exact match or ILIKE %value% on the post name |
keyword |
eq |
ILIKE %value% on the post name |
status |
eq |
Filters blog_post.status |
category |
eq, in |
in takes a comma-separated list of category ids |
tag |
eq |
Joins blog_post_tag and filters by tag id |
ob |
eq |
Order by name, published_at, or created_at |
blogCategories supports name and status; blogTags supports name; blogComments supports status and keyword. All four filter sets are registered from bootstrap.ts via addProcessor on blogPostCollectionFilters, blogCategoryCollectionFilters, blogTagCollectionFilters, and blogCommentCollectionFilters — so an extension can add its own filter the same way.
The `featured_blogs` widget
bootstrap.ts registers one widget:
registerWidget({
type: 'featured_blogs',
name: 'Featured blogs',
description: 'A list of featured blog posts',
category: 'content',
icon: 'Newspaper',
enabled: true,
defaultSettings: {
eyebrow: '',
heading: '',
subText: '',
postUuids: [],
count: 3,
columns: 3
}
});Its three components live in the module: components/admin/FeaturedBlogsSetting.tsx, components/admin/FeaturedBlogsPreview.tsx, and components/frontStore/FeaturedBlogs.tsx.
The settings schema constrains count to 1–24 and columns to one of [1, 2, 3, 4]. postUuids is an explicitly curated, ordered list — the featuredBlogsWidget resolver fetches the matching published posts and re-orders them to match the pick order before slicing to count:
const ordered = uuids.map((u) => byUuid.get(u)).filter(Boolean);With no posts picked, the resolver shows the newest published posts instead, up to count (newest first, by published_at). That is what a Journal teaser usually is, and it means a theme can ship one: a manifest cannot name a store's posts (their uuids belong to the store), but it does not have to. Posts that were picked and have all since been unpublished still show nothing, because that is a choice rather than the absence of one.
Like every widget, registerWidget must be called from bootstrap.ts; the registry is locked afterwards.
Blog URNs and link loaders
Blog entities are addressable by URN in the form urn:evershop:blog:<type>:, with three registered types:
urn:evershop:blog:post:urn:evershop:blog:category:urn:evershop:blog:tag:
modules/blog/lib/BlogUrn.ts is a thin re-export — the schemas themselves are registered centrally in lib/urn/index.ts alongside the catalog and CMS schemas so they are available on both client and server:
export const BlogUrn = {
post: (uuid: string) => UrnService.build('blog', 'post', uuid),
category: (uuid: string) => UrnService.build('blog', 'category', uuid),
tag: (uuid: string) => UrnService.build('blog', 'tag', uuid)
};bootstrap.ts registers a link loader for each type so a URN can be resolved to a live URL at request time. Each loader is built with linkLoaderFromBatch, batching the uuids into one url_rewrite lookup and falling back to the internal route when no rewrite exists:
registerLinkLoader('blog', 'post', blogLinkLoader('blog_post', 'blogPostView'));
registerLinkLoader('blog', 'category', blogLinkLoader('blog_category', 'blogCategoryView'));
registerLinkLoader('blog', 'tag', blogLinkLoader('blog_tag', 'blogTagView'));This is what lets a page-builder link or a rich-text mention point at a post without hardcoding its slug — rename the post and the URN still resolves.
Metafields
Blog posts and blog categories are metafield owners. The owner types are:
| Owner type | Value storage |
|---|---|
blog_post |
blog_post.meta_data |
blog_category |
blog_category.meta_data |
Blog tags are not metafield owners — blog_tag has no meta_data column.
bootstrap.ts wires the write path by registering a folder on the same registry keys the services already run:
function makeMetafieldFolder(ownerType: string) {
return async function foldMetafields(data) {
if (data && data.metafields !== undefined) {
data.meta_data = await validateMetafields(ownerType, data.metafields);
}
return data;
};
}
addProcessor('blogPostDataBeforeCreate', makeMetafieldFolder('blog_post'));
addProcessor('blogPostDataBeforeUpdate', makeMetafieldFolder('blog_post'));The folder runs only when metafields is explicitly present in the payload, so an ordinary API update that omits the key leaves meta_data untouched rather than wiping it.
On the read side, BlogPostMetafields.graphql and BlogCategoryMetafields.graphql extend both types with metafields(namespace: String) and metafield(namespace: String!, key: String!).
Deleting a metafield definition is cleaned up by two subscribers on metafield_definition_deleted — pruneBlogPost.ts and pruneBlogCategory.ts — which strip the key from every row's meta_data with the JSONB #- operator. Both are idempotent and no-op when the deleted definition belongs to a different owner type.
See Metafields for definition management, field types, and validation.
Sitemap
Blog registers its own sitemap collectors from bootstrap.ts — nothing in modules/base knows about blog:
registerSitemapCollector(
createEntityCollector({
name: 'blog-posts',
table: 'blog_post',
entityType: 'blog_post',
where: 'e.status = 1',
changefreq: 'weekly',
priority: 0.5
})
);| Collector | Table | Filter | changefreq | priority |
|---|---|---|---|---|
blog-posts |
blog_post |
e.status = 1 |
weekly | 0.5 |
blog-categories |
blog_category |
e.status = 1 |
weekly | 0.4 |
blog-tags |
blog_tag |
none | monthly | 0.3 |
The tag collector passes updatedAtColumn: 'created_at' because blog_tag has no updated_at column. Each collector is served at /sitemap-<name>.xml — so /sitemap-blog-posts.xml, /sitemap-blog-categories.xml, and /sitemap-blog-tags.xml — and referenced from the /sitemap.xml index. Each inherits multi-language hreflang, 50,000-per-file chunking, and change detection for free.
The /blog landing page itself is not an entity, so include it through the sitemap's staticPaths configuration if you want it listed. See Sitemap.
REST API
Fourteen endpoints, eleven admin-only and three unauthenticated public write surfaces (POST /api/blog/comments, POST /api/blog/comments/:id/like, POST /api/blog/posts/:id/react). See the Blog REST API reference for payloads and responses.
See also
- Metafields — custom fields on
blog_postandblog_category - Sitemap — how blog collectors plug into
/sitemap.xml - Events and Subscribers — the
blog_*_created/updated/deletedevents - Registry and Processors — the
blog*DataBefore*andblog*CollectionFilterskeys - Routing System —
url_rewriteresolution and route matching order
import Sponsors from '@site/src/components/Sponsor';