## 1. Project Overview & Quickstart (evershopcommerce/translation)

# Technical Documentation: evershopcommerce/translation

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/evershopcommerce/translation)

# 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](./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](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html) 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:

```bash
sudo apt update
sudo apt install -y curl
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
```

Verify the installation:

```bash
node -v  # Should show v20.x.x or higher
npm -v   # Should show 9.x.x or higher
```

#### 2. Install PM2 Process Manager

PM2 will ensure your EverShop application runs continuously and restarts automatically if the server reboots:

```bash
sudo npm install -g pm2
```

Verify the installation:

```bash
pm2 -v
```

Configure PM2 to start automatically on server boot:

```bash
pm2 startup systemd
```

Follow 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:

```bash
sudo apt install -y nginx
```

Create a configuration file for your EverShop application:

```bash
sudo nano /etc/nginx/sites-available/evershop.conf
```

Add the following configuration, replacing `yourdomain.com` with your actual domain name (or use the EC2 public DNS temporarily):

```nginx
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:

```bash
sudo ln -s /etc/nginx/sites-available/evershop.conf /etc/nginx/sites-enabled/
sudo unlink /etc/nginx/sites-enabled/default
```

Test the configuration and restart Nginx:

```bash
sudo nginx -t
sudo systemctl restart nginx
```

## Database 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](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_CreateDBInstance.html).
:::

1. Navigate to the RDS dashboard in the AWS console
2. Click "Create database"
3. Select "Standard create" and choose PostgreSQL
4. Select version 13 or higher
5. Choose the appropriate instance size (db.t3.micro is free tier eligible)
6. Configure storage, connectivity, and security settings
7. Set your master username and password
8. Complete the creation process

### Configuring Security Groups

For your EC2 instance to access the RDS database:

1. Identify the security group of your RDS instance
2. 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](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_VPC.WorkingWithRDSInstanceinaVPC.html).
:::

### Creating the Database and User

Connect to your RDS instance using a PostgreSQL client like pgAdmin or psql:

```bash
psql -h your-rds-endpoint.rds.amazonaws.com -U master_username -d postgres
```

Create a dedicated database and user for EverShop:

```sql
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:

1. Fork the [EverShop project template](https://github.com/evershopcommerce/project-template) to your GitHub account
2. 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:

```bash
sudo mkdir -p /var/www/evershop
cd /var/www/evershop
sudo nano deploy.sh
```

Add the following script, replacing the placeholder values with your actual GitHub information:

```bash
#!/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:

```bash
sudo chmod +x deploy.sh
```

### Creating 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:

```bash
sudo mkdir -p /var/www/evershop/current
sudo nano /var/www/evershop/current/.env
```

Add your database connection and the production settings:

```bash title=".env"
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:

```bash
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.

```bash
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 booting
`EVERSHOP_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](#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:

```bash
cd /var/www/evershop
sudo ./deploy.sh
```

After 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:

1. Install Certbot:

```bash
sudo apt install -y certbot python3-certbot-nginx
```

2. Obtain and install a certificate:

```bash
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
```

3. 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:

```bash
sudo systemctl status certbot.timer
```

This 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:

```bash
sudo nano /etc/logrotate.d/pm2
```

Add 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:

```bash
pm2 monit
```

For 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](./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:

1. An active Microsoft Azure account with subscription access
2. An EverShop project installed and running on your local machine
3. Git installed on your local machine (for version control and deployment)

## Step 1: Create a New Azure Web App

1. Log in to the Azure portal at [https://portal.azure.com](https://portal.azure.com).


2. Click the "+ Create a resource" button in the Azure portal dashboard.

3. Search for "Web App & Database" and select it from the search results.
   

4. Click the "Create" button to begin configuring your web application.

5. Configure your web application with the following settings:



6. Configure your database with appropriate settings for your EverShop deployment:



7. 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:

1. Navigate to your newly created web app in the Azure portal.

2. In the left menu, select "Deployment Center."

3. Choose `Local Git` as your deployment option.

4. 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:

```bash
git remote add azure <your-git-url>
```

:::caution
If your local project is not already a Git repository, initialize one first by running:

```bash
git init
```

Then add and commit your files before proceeding to the next step:

```bash
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:

```json title="package.json"
{
  "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"
  }
}
```

:::info
`evershop 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:

```javascript title="ecosystem.config.cjs"
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:

```bash
├── .evershop
├── .log
├── extensions
├── media
├── themes
├── node_modules
├── public
├── .env
├── ecosystem.config.cjs
├── package.json
├── package-lock.json
└── README.md
```

Create a `.gitignore` file to exclude unnecessary files from deployment:

```bash title=".gitignore"
.evershop
.log
node_modules
.env
```

## Step 5: Configure Environment Variables

EverShop requires specific environment variables for database connectivity. Configure these in the Azure portal:

1. Go to your web app in the Azure portal and navigate to the "Configuration" section.



2. Rename the following default Azure PostgreSQL environment variables to match EverShop's expected names:

   - `AZURE_POSTGRESQL_DBNAME` → `DB_NAME`
   - `AZURE_POSTGRESQL_USERNAME` → `DB_USER`
   - `AZURE_POSTGRESQL_PASSWORD` → `DB_PASSWORD`
   - `AZURE_POSTGRESQL_HOST` → `DB_HOST`
   - `AZURE_POSTGRESQL_PORT` → `DB_PORT`

3. Add these additional required environment variables:

   - `DB_SSLMODE`: Set to `require` to enable SSL connections to the database
   - `PORT`: Set to `3000` to specify the port EverShop will use
   - `EVERSHOP_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 to `1` — 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.

```bash
EVERSHOP_HOME_URL=https://your-app.azurewebsites.net
```

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 booting
`EVERSHOP_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.





4. Save your configuration changes.

## Step 6: Deploy Your Project

With configuration complete, deploy your EverShop application to Azure:

```bash
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:

1. Connect to your web app using the SSH feature in the Azure portal.



2. Navigate to your application directory:

```bash
cd /home/site/wwwroot
```

3. Create an admin user:

```bash
npm run user:create -- --email "admin@example.com" --password "securePassword" --name "Admin Name"
```

4. Access your admin panel at `https://<your-azure-domain>/admin` and 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:

1. Navigate to the "Custom domains" section in your web app's settings.

2. Follow the [Azure Custom Domain Configuration Guide](https://learn.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-custom-domain) to map your domain to your Azure web app.

## Troubleshooting

If you encounter issues during deployment or while running your EverShop store on Azure:

1. Check the application logs in the Azure portal under "App Service logs"
2. Verify your environment variables are configured correctly
3. Ensure your database connection is working properly
4. Confirm that your PM2 configuration is correct

## Performance Optimization

For optimal performance of your EverShop store on Azure:

1. Enable Azure CDN for faster content delivery
2. Configure auto-scaling based on your expected traffic patterns
3. Use the appropriate App Service pricing tier for your needs
4. 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](./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:

```json
{
  "scripts": {
    "build": "evershop build",
    "start": "evershop start",
    "user:create": "evershop user:create",
    "user:changePassword": "evershop user:changePassword"
  }
}
```

:::info
`evershop build` takes no flags. Asset minification is always on in a production build and cannot be turned off.
:::

### Create a New DigitalOcean App

1. Log in to your DigitalOcean account and navigate to the App Platform dashboard.

2. Click the "Create App" button to begin the setup process.



3. 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.

4. Configure your deployment settings. You can typically use the default settings, but verify the branch you want to deploy.



5. 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.
:::

6. For now, leave the "Environment Variables" section empty. We'll configure these after creating the database.



7. 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:

1. From the DigitalOcean dashboard, click "Databases" in the main navigation menu.

2. Click the "Create Database Cluster" button.

3. Select "PostgreSQL" as the database type and choose version 13 or higher.

4. Select your preferred region, typically the same region as your App Platform deployment.

5. Choose an appropriate plan based on your expected traffic and database usage.



6. 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:

1. Return to your App Platform dashboard and select your EverShop application.

2. Navigate to the "Settings" tab and find the "App-level environment variables" section.

3. Add the following environment variables, using the values from your database connection details:



- `DB_HOST`: The hostname of your PostgreSQL database
- `DB_PORT`: The port number (typically 25060 for DigitalOcean managed PostgreSQL)
- `DB_NAME`: Your database name
- `DB_USER`: Your database username
- `DB_PASSWORD`: Your database password
- `DB_SSLMODE`: Set to `no-verify` (or `require` if 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 to `1` — 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.

```bash
EVERSHOP_HOME_URL=https://your-app.ondigitalocean.app
```

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 app from booting
`EVERSHOP_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:

1. In the "Settings" tab, select your app component (typically named after your GitHub repository).

2. Navigate to the "Commands" section.



3. Verify that the build and run commands match the scripts in your package.json file:



4. 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:

1. From your App Platform dashboard, select your EverShop application.

2. Navigate to the "Console" tab to access a command-line interface.

3. Run the following command to create an admin user, replacing the placeholder values with your desired credentials:

```bash
npm run user:create -- --email "admin@example.com" --name "Admin User" --password "SecurePassword123!"
```

4. Once the command completes successfully, access your admin panel by visiting `https://<your-app-domain>/admin` and 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](./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:

1. An active Heroku account
2. An EverShop project installed and running on your local machine
3. Git installed on your local machine
4. [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) installed

## Step 1: Create a New Heroku App

1. Log in to the [Heroku dashboard](https://dashboard.heroku.com).
2. Click the "New" button and select "Create new app" from the dropdown menu.
3. Enter a unique app name, select your preferred region, and click "Create app".



## Step 2: Install and Configure Heroku CLI

1. If you haven't already, download and install the Heroku CLI from the [official Heroku documentation](https://devcenter.heroku.com/articles/heroku-cli).
2. Open your terminal and authenticate with Heroku:

```bash
heroku login
```

Follow 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:

```bash
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](https://devcenter.heroku.com/articles/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:

```bash
heroku config -a YOUR_APP_NAME
```

You'll see output similar to:

```
DATABASE_URL: postgres://username:password@host:port/database_name
```

Since EverShop requires individual database configuration parameters rather than a connection string, parse the `DATABASE_URL` and set the following environment variables:

```bash
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/d468v1qsdfnb0arsqf
```

Then 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     # Documentation
```

### Specify Node.js and NPM Versions

Add Node.js and NPM version specifications to your `package.json`:

```json title="package.json"
{
  "engines": {
    "node": "20.x",
    "npm": "9.x"
  }
}
```

### Configure Scripts for Heroku

Update your `package.json` with the necessary scripts for Heroku deployment:

```json title="package.json"
{
  "scripts": {
    "build": "evershop build",
    "start": "evershop start",
    "user:create": "evershop user:create"
  }
}
```

:::info
`evershop 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.

```bash
heroku config:set EVERSHOP_HOME_URL=https://YOUR_APP_NAME.herokuapp.com -a YOUR_APP_NAME
```

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 dyno from booting
`EVERSHOP_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.

```bash
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:

```bash
# 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:

```bash title=".gitignore"
.evershop
.log
node_modules
.env
```

## Step 5: Deploy Your Application

### Initialize Git Repository

If your project isn't already in a Git repository:

```bash
git init
```

### Commit Your Changes

Add and commit all files:

```bash
git add .
git commit -m "Prepare EverShop for Heroku deployment"
```

### Connect to Heroku Remote

Link your local repository to your Heroku app:

```bash
heroku git:remote -a YOUR_APP_NAME
```

### Deploy to Heroku

Push your code to deploy:

```bash
git push heroku main
```

If your primary branch is named `master` instead of `main`, use:

```bash
git push heroku master
```

Heroku 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.com
```



## Step 6: Create an Administrator Account

After your first deployment, create an administrator account to access the admin panel:

```bash
heroku run npm run user:create -- --email "admin@example.com" --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/admin
```

## Troubleshooting and Maintenance

### Viewing Application Logs

Monitor your application logs with:

```bash
heroku logs --tail -a YOUR_APP_NAME
```

### Scaling Your Application

As your store grows, you may need to scale your application:

```bash
heroku ps:scale web=2 -a YOUR_APP_NAME
```

### Managing Database Backups

Create regular backups of your PostgreSQL database:

```bash
heroku pg:backups:capture -a YOUR_APP_NAME
```

### Updating Your Application

To deploy updates, simply commit your changes and push to Heroku:

```bash
git add .
git commit -m "Update application"
git push heroku main
```

## Conclusion

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](./deploy-evershop-to-aws), [Azure](./deploy-evershop-to-azure), [DigitalOcean](./deploy-evershop-to-digitalocean-app-platform), [Heroku](./deploy-evershop-to-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](/docs/development/knowledge-base/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 variables
`evershop 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.

```bash
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 in `sitemap.xml` and the absolute `Sitemap:` line in the generated `robots.txt`
- canonical tags and `hreflang` alternates 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](#built-in-rate-limits) 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.txt` and `/sitemap.xml` are also exempt.)
- **Hot module replacement** — `/__webpack_hmr…` and any `.hot-update` path.
- **`/backend/`** — the admin development bundle.
- **`/health` and `/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-After`** header, in seconds — `60` for the page and API tiers, `900` for the auth tier
- standard **`RateLimit-*`** headers on every response, so a well-behaved client can back off before it is throttled (the legacy `X-RateLimit-*` headers are not sent)
- a JSON body for `/api/**` paths, matching EverShop's normal error envelope:

```json
{
  "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:

```bash
npm run build
npm run start
```

`evershop 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_USER` needs 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:

```bash
npm run user:create -- --email "admin@example.com" --name "Admin" --password "a-strong-password"
```

## Final pass before launch

- [ ] `EVERSHOP_HOME_URL` is set to the real public origin, with `https://` and no trailing slash — and a test email actually links to it.
- [ ] `TRUST_PROXY_HOPS` matches the real number of proxies. Confirm it by checking that different clients produce different addresses in your logs.
- [ ] `DB_SSLMODE` is 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_SECRET` is set if guests can order.
- [ ] `IMAGE_ALLOWED_HOSTS` is set, or cloud file storage is configured — check the boot log for the warning.
- [ ] `LOG_FILE` points 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](/docs/development/knowledge-base/file-storage).
- [ ] Automated database backups are on, and you have restored one at least once.
- [ ] No hand-written `public/robots.txt` or `public/sitemap.xml` is shadowing the generated ones. See [Static File Serving](/docs/development/knowledge-base/static-file-serving).
- [ ] Store settings — currency, timezone, units, languages, tax — are set in Admin → Settings. See [Store Settings](/docs/development/knowledge-base/store-settings).
- [ ] Demo seed data has **not** been run against the production database.
- [ ] `npm run build` completed 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](/docs/development/getting-started/system-requirements) for the system requirements list.

:::

## Install EverShop Using `create-evershop-app` command

```bash
npx create-evershop-app my-evershop-app
```

The `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.

```bash
curl -sSL https://raw.githubusercontent.com/evershopcommerce/evershop/main/docker-compose.yml > docker-compose.yml
docker-compose up -d
```

The 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:

```bash
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`.

```bash title="Install the @evershop/evershop Npm package"
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:

```json title="Add the core npm scripts to package.json"
{
  "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](/docs/development/knowledge-base/command-lines) 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](/docs/development/getting-started/system-requirements) for the system requirements list.
:::

The interactive setup wizard will prompt you for:

1. **Database connection details** — host, port, database name, user, and password
2. **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 `.env` file in your project root with the database connection settings
- Create the `media` and `public` directories
- Set up the database schema (create all required tables)
- Run all database migrations
- Create your administrator account

```bash title="Installation script"
npm run setup
```

:::info
The `.env` file created by the setup script contains your database credentials:

```bash
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](/docs/development/knowledge-base/database) 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`
- `.log`
- `media`

### Step 5: Run the `build` command to build the site

```bash title="Build the site"
npm run build
```

### Step 6: Run the `start` command to start your store in production mode

```bash title="Start the site"
npm run start
```

Your 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](../knowledge-base/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:

```bash title="Step 1: Install the latest version"
npm install @evershop/evershop@latest
```

```bash title="Step 2: Rebuild your store"
npm run build
```

```bash title="Step 3: Restart your store"
npm run start
```

Database 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](/blog/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.

```bash
npm run seed
```

This 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:

```bash
npm install --save-dev @types/node typescript @parcel/watcher @types/config @types/express @types/pg @types/react execa
```

### Adding the workspace configuration

Open the package.json and add the following configuration:

```json title="Adding the workspace 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

```bash title="Start the site in development mode"
npm run dev
```

Your 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:

```bash title="Start the site in debug mode"
npm run start:debug
```

This 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:

1.  **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.

2.  **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](../knowledge-base/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.

```dockerfile title="Dockerfile"
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.

```yml title="docker-compose.yml"
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';

<Sponsors/>

---

## 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](https://github.com/evershopcommerce/evershop).

## 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](https://www.typescriptlang.org/), a statically typed superset of JavaScript. This provides better tooling, scalability, and code quality.

### Node.js

[Node.js](https://nodejs.org/en/) 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](https://www.postgresql.org/) 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](https://reactjs.org/) 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](https://graphql.org/) 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](/docs/development/getting-started/system-requirements) 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](/docs/development/theme/theme-overview).
:::

## Demo Store

Explore our demo store.

import Link from '@docusaurus/Link';


<Link
            className="button button--primary button--md"
            to="https://demo.evershop.io/admin/">
            Admin Panel
          </Link>
<Link
            className="button button--primary button--md"
            to="https://demo.evershop.io/">
            Front Store
          </Link>



:::info
The demo admin account email is `demo@evershop.io` and the password is `123456`
:::

## Next Steps

Ready to get started? Here's the recommended path:

1. **[System Requirements](/docs/development/getting-started/system-requirements)** — Ensure your system is ready
2. **[Installation Guide](/docs/development/getting-started/installation-guide)** — Install and run your store
3. **[Architecture Overview](/docs/development/knowledge-base/architecture-overview)** — Understand how EverShop works
4. **[Extension Development](/docs/development/module/extension-development)** — Build your first extension
5. **[Theme Overview](/docs/development/theme/theme-overview)** — Customize the storefront

## Community

We invite you to join our [Discord channel](https://discord.com/invite/GSzt7dt7RM) for project discussion. Thank you!

import Sponsors from '@site/src/components/Sponsor';

<Sponsors/>

---

## 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](https://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 (`sharp` 0.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](https://www.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_trgm` for 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:

```bash
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:

```bash
npm install --save-dev @types/react@19 @types/react-dom@19
npm ls @types/react
```

If `npm ls` still reports an 18.x copy somewhere in the tree, add an `overrides` entry to your `package.json`:

```json
{
  "overrides": {
    "@types/react": "^19.1.2",
    "@types/react-dom": "^19.0.0"
  }
}
```
:::

See the [Installation Guide](/docs/development/getting-started/installation-guide#for-developers) 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

```bash
node --version
# Should output: v20.x.x or higher
```

### Check NPM Version

```bash
npm --version
# Should output: 9.x.x or higher
```

### Check PostgreSQL Version

```bash
psql --version
# Should output: psql (PostgreSQL) 13.x or higher
```

### Check Available Disk Space

**Linux/macOS:**
```bash
df -h
```

**Windows:**
```bash
wmic logicaldisk get size,freespace,caption
```

## Next Steps

Once your system meets all requirements:

1. Proceed to the [Installation Guide](/docs/development/getting-started/installation-guide)
2. Set up your PostgreSQL database
3. Install EverShop using your preferred method

## Troubleshooting

### Common Issues

**Node.js version mismatch:**
- Use [nvm](https://github.com/nvm-sh/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 `sudo` for global npm installations
- Consider using nvm to avoid permission issues

## Support

If you encounter issues with system requirements:

- Check our [GitHub Issues](https://github.com/evershopcommerce/evershop/issues)
- Join our [Discord Community](https://discord.gg/GSzt7dt7RM)
- Review the [Documentation](/docs/development/getting-started/installation-guide)

---

## 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](/docs/development/module/extension-overview) 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:

```bash
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.json

```

## API Folder Structure

The `api` folder contains the following organization:

- `global` - Contains middleware functions that apply to all API endpoints. For example, the `auth` middleware that handles authentication for all secured endpoints.

- `<apiID>` - Individual folders representing specific API endpoints. For example, the `createProduct` folder 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:

```bash
├── createProduct
│   ├── [context]bodyParser[auth].ts
│   ├── saveProduct.ts
│   └── route.json
```

The 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:

```json
{
  "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](/docs/development/knowledge-base/routing-system).
:::

## API Authentication

The `access` property in the `route.json` file controls endpoint security:

- `public` - The endpoint can be accessed without authentication
- `private` - 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`.

```json title="payloadSchema.json"
{
  "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](https://ajv.js.org/) 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](/docs/development/knowledge-base/middleware-system).
:::

### 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:

1. **Endpoint-Specific Sharing**: Create a special folder named after the endpoints that should share middleware (e.g., `createProduct+updateProduct`) in the `api` directory. Place shared middleware functions in this folder, and they'll be executed for both endpoints.

2. **Global Sharing**: For middleware that should run for all API endpoints, place it in the `api/global` folder.

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:

```json
{
  "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:

1. **Proper Validation**: Always define comprehensive validation schemas for request data
2. **Access Control**: Set appropriate `access` levels for your endpoints
3. **Modular Design**: Break complex operations into multiple middleware functions
4. **Error Handling**: Provide clear error responses with appropriate HTTP status codes
5. **Handle 429s**: Clients should respect `Retry-After` and the `RateLimit-*` headers rather than retrying in a tight loop
6. **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 `catalog` and `checkout`, 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.



1.  **HTTP Request**: A request is received by the web server.
2.  **Middleware Pipeline**: The request passes through a pipeline of middleware functions. This includes core middleware (for sessions, authentication, etc.) and middleware added by modules.
3.  **Routing**: The router directs the request to the appropriate controller or GraphQL resolver within a specific module based on the URL and HTTP method.
4.  **Controller/Resolver**: The controller or resolver processes the request, validates input, and calls the necessary services.
5.  **Service Layer**: Services contain the core business logic. They interact with the database and other resources to perform operations.
6.  **Database**: Data is persisted and retrieved from the PostgreSQL database.
7.  **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.

```bash
├── .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
```

-   **`.env` File**: Holds environment variables — most importantly the PostgreSQL connection (`DB_HOST`, `DB_USER`, …), plus `PORT`, `EVERSHOP_HOME_URL` and `TRUST_PROXY_HOPS`. It is read directly by the code; it is **not** merged into the `node-config` tree.
-   **`.evershop` Directory**: Contains built files optimized for production, including compiled React components and other assets. This directory is generated by the `build` command.
-   **`config` Directory**: Houses your custom configuration files. You can override default settings here. See the [Configuration Guide](./configuration-guide) for more details.
-   **`extensions` Directory**: This is where you place your custom modules to extend EverShop's functionality.
-   **`media` Directory**: Stores all uploaded files, such as product images and other media assets.
-   **`public` Directory**: Contains static assets like CSS, JavaScript, and images that are served directly to clients.
-   **`node_modules` Directory**: Contains all project dependencies, including the core `evershop` package.
-   **`themes` Directory**: This directory holds your custom themes, which control the look and feel of your storefront. See the [Theme Overview](../theme/theme-overview) for more information.
-   **`translations` Directory**: 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 `*.csv` dictionaries that are read from disk **at boot** and held in memory. Add or override translations here. See [Translation](./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](./store-settings).
:::

### Managing Dependencies with NPM Workspaces

For advanced development, both the `extensions` and `themes` directories can be managed as [NPM workspaces](https://docs.npmjs.com/cli/v7/using-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`:

```json
{
  "name": "my-evershop-store",
  "version": "1.0.0",
  "workspaces": [
    "extensions/*",
    "themes/*"
  ]
}
```

With this configuration, you can structure your extensions like this:

```bash
├── extensions
│   ├── my-custom-extension
│   │   ├── src/
│   │   │   └── bootstrap.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── another-extension
│       ├── src/
│       │   └── bootstrap.ts
│       ├── package.json
│       └── tsconfig.json
```

When 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:

1.  **Create Express App** — The HTTP application is initialized.
2.  **Load Modules** — Core modules (catalog, checkout, customer, etc.) and enabled extensions are discovered.
3.  **Scan Routes & Middleware** — Route definitions (`route.json`) and middleware files are scanned from all modules and extensions.
4.  **Execute Bootstrap Scripts** — Each module's `bootstrap.ts` is executed sequentially. This is where modules register [processors](/docs/development/knowledge-base/registry-and-processors), [hooks](/docs/development/module/functions/hookable), [widgets](/docs/development/module/widget-development), and [cron jobs](/docs/development/knowledge-base/cron-jobs).
5.  **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.
6.  **Validate Configuration** — The configuration is validated against the merged JSON schema.
7.  **Run Migrations** — Pending database migrations from all modules are executed within transactions.
8.  **Provision Active Theme Metafields** — `provisionActiveThemeMetafields()` re-applies the active theme's `metafieldDefinitions` from `theme.json`. It is idempotent and runs in the main web process only.
9.  **Start HTTP Server** — The Express app begins listening for requests.
10. **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 &amp; Processors** | Transform data values across modules (cart fields, email services, config schemas) | [Registry and Processors](/docs/development/knowledge-base/registry-and-processors) |
| **Hooks** | Run code before or after specific function calls | [hookable](/docs/development/module/functions/hookable) |
| **Events &amp; Subscribers** | React to actions asynchronously (product created, order placed) | [Events and Subscribers](/docs/development/knowledge-base/events-and-subscribers) |
| **Middleware** | Add request-processing logic to routes | [Middleware System](/docs/development/knowledge-base/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](/docs/api/blog).

## 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/&lt;slug&gt;` | Yes |
| `blogCategoryView` | `/blogCategory/:uuid` | `/blog/category/&lt;slug&gt;` | Yes |
| `blogTagView` | `/blogTag/:uuid` | `/blog/tag/&lt;slug&gt;` | 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/&#123;url_key&#125;` → `/blogPost/&#123;uuid&#125;` |
| `blog_category_created` / `blog_category_updated` | `subscribers/blog_category_*/buildUrlRewrite.ts` | Upserts `/blog/category/&#123;url_key&#125;` → `/blogCategory/&#123;uuid&#125;` |
| `blog_tag_created` / `blog_tag_updated` | `subscribers/blog_tag_*/buildUrlRewrite.ts` | Upserts `/blog/tag/&#123;url_key&#125;` → `/blogTag/&#123;uuid&#125;` |
| `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:

```ts
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_id` and `blog_comment.customer_id` carry no database foreign key.** Module migration order across modules is not guaranteed, so a hard FK to `admin_user` / `customer` could run before those tables exist. The resolvers tolerate a missing or orphaned id and return `null`.
- **`blog_reaction` is 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`:

```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 `''` to `null` before the insert, because the integer columns reject the empty string.
- **`published_at` is stamped on first publish.** When `status` is `1` and `published_at` is not already set, the service stamps the current ISO timestamp. There is no scheduler — a post is visible on the storefront when `status = 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:

```ts
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:

- `name` and `comment` go through `sanitizeHtml` with **no allowed tags or attributes**, then whitespace is collapsed and the value is truncated (120 characters for `name`, 5000 for `comment`). This is deliberately *not* `sanitizeRawHtml`, which is Editor.js-specific and permissive.
- A hidden `website` field acts as a **honeypot**. Humans never see it; a bot that fills it gets `status = 'spam'`.
- A comment containing more than three `http(s)://` occurrences is also auto-marked `spam`.

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`:

```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`) calls `resolveReactor(request, response, true)`. If the cookie is absent, a fresh `randomUUID()` is generated and set on the response.
- **Read path** (`blogPostView` GET) calls `resolveReactor(request)` with `issue` defaulting to `false`, so a GET never sets a cookie. It returns `null` when no cookie exists, and the resolvers simply report `reacted: false` / `liked: false`.

Reaction types are fixed lists in `services/reaction/reactionTypes.ts`:

```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`

```graphql
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:

- **`url`** looks up the `url_rewrite` row and falls back to `/blog/`, then runs the result through `localizeUrl` so the locale prefix is preserved.
- **`description`** is `JSON`, not HTML. It is the Editor.js block array; the resolver parses the stored string and returns `[]` on malformed JSON.
- **`reactions`** always returns one entry per type in `REACTION_TYPES`, with `count: 0` for types nobody has used, and `reacted` true for the type matching `blogPostReactedType` in 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:

```ts
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`:

```ts
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:

```ts
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:

```ts
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:

```ts
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](./metafields.md) for definition management, field types, and validation.

## Sitemap

Blog registers its own sitemap collectors from `bootstrap.ts` — nothing in `modules/base` knows about blog:

```ts
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](./sitemap.md).

## 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](/docs/api/blog) for payloads and responses.

## See also

- [Metafields](./metafields.md) — custom fields on `blog_post` and `blog_category`
- [Sitemap](./sitemap.md) — how blog collectors plug into `/sitemap.xml`
- [Events and Subscribers](./events-and-subscribers.md) — the `blog_*_created` / `updated` / `deleted` events
- [Registry and Processors](./registry-and-processors.md) — the `blog*DataBefore*` and `blog*CollectionFilters` keys
- [Routing System](./routing-system.md) — `url_rewrite` resolution and route matching order

import Sponsors from '@site/src/components/Sponsor';

<Sponsors/>