← Back to posts
comparison guide self-hosted · · 14 min read

Best Self-Hosted Headless E-Commerce Platforms: Medusa vs Saleor vs Vendure 2026

Compare the top 3 open-source headless e-commerce platforms you can self-host in 2026. Complete setup guides, feature comparisons, and recommendations for Medusa, Saleor, and Vendure.

OS
Editorial Team

Building an online store without surrendering control of your data, customer information, and revenue streams to a SaaS platform is one of the most impactful infrastructure decisions a business can make. Headless e-commerce platforms separate the backend — products, orders, payments, inventory — from the frontend presentation layer, giving you full flexibility over the customer experience while keeping the commerce engine under your control.

In 2026, three open-source platforms dominate this space: Medusa, Saleor, and Vendure. Each takes a different architectural approach, supports different tech stacks, and targets different types of merchants. This guide compares them side by side and provides complete self-hosting instructions for each.

Why Self-Host Your E-Commerce Platform

Running your own e-commerce backend offers advantages that go far beyond cost savings:

  • Zero platform fees. Shopify charges 0.5–2% transaction fees on top of payment processor costs. Self-hosted platforms eliminate this entirely.
  • Full data ownership. Customer data, purchase history, and behavioral analytics stay on your infrastructure. No third-party data sharing, no vendor lock-in, no surprise policy changes.
  • Unlimited customization. Headless architecture means your frontend can be anything — a React SPA, a static Hugo site, a mobile app, or a custom native experience — with no template restrictions.
  • No API rate limits. SaaS platforms throttle API calls on lower-tier plans. Self-hosted, you control the infrastructure and can scale as needed.
  • Regulatory compliance. When you host the data yourself, you control exactly where it lives and how it’s processed — critical for GDPR, CCPA, and industry-specific requirements.
  • Portability. Your store is not tied to a vendor’s ecosystem. You can change hosting providers, swap payment gateways, or modify any component without platform approval.

The trade-off is operational responsibility: you manage deployment, scaling, backups, and updates. For teams with even basic DevOps experience, the returns on investment are substantial.

Platform Overview

Medusa — The Modular JavaScript Platform

Medusa is a Node.js-based headless commerce platform designed around a plugin architecture. It describes itself as “the open-source Shopify alternative” and provides a comprehensive set of commerce primitives out of the box — products, pricing, cart, checkout, orders, returns, and customer management — with the ability to extend virtually any behavior through plugins and modules.

Architecture: Monolithic Node.js backend with pluggable modules. REST API by default, with optional GraphQL through community plugins.

Database: PostgreSQL (required), Redis (recommended for caching and job queues).

Admin dashboard: Built-in React-based admin panel, shipped separately from the core server.

Best for: Developers who want Shopify-level features with complete code-level customization and a large plugin ecosystem.

Saleor — The Python-Powered GraphQL-First Platform

Saleor is a Python (Django + Graphene) headless commerce platform that is GraphQL-first from the ground up. Every operation — product queries, cart mutations, checkout flows — is exposed through a single GraphQL endpoint. Saleor’s architecture is designed for high-performance storefronts and supports multi-channel, multi-currency operations natively.

Architecture: Python/Django core with a GraphQL API layer. Separate dashboard application (React/TypeScript).

Database: PostgreSQL (required), Redis (required for caching and async tasks).

Admin dashboard: React-based dashboard included, with a rich product management interface.

Best for: Teams that want GraphQL-first APIs, strong internationalization (multi-currency, multi-language), and a polished admin experience.

Vendure — The TypeScript GraphQL Platform

Vendure is a headless commerce framework built entirely in TypeScript using NestJS and GraphQL. It emphasizes developer experience with strong typing, a clean plugin API, and a focus on B2B and complex catalog scenarios. Vendure’s architecture is designed to be extended rather than modified — you build on top of its core rather than patching internals.

Architecture: NestJS backend with GraphQL API. Admin UI and storefront starter kits provided separately.

Database: PostgreSQL and MySQL (both supported), with Redis for caching.

Admin dashboard: Angular-based admin UI, fully featured with product, order, customer, and settings management.

Best for: TypeScript/NestJS teams, B2B commerce scenarios, and projects where strong typing and developer experience are priorities.

Feature Comparison

FeatureMedusaSaleorVendure
Backend languageNode.js (JavaScript/TypeScript)Python (Django)TypeScript (NestJS)
Primary APIRESTGraphQLGraphQL
Secondary APIGraphQL (community plugins)REST (limited)
DatabasePostgreSQLPostgreSQLPostgreSQL, MySQL
Admin dashboardReact (separate package)React (included)Angular (included)
Multi-currencyYes (plugin)Yes (native)Yes (native)
Multi-languageYes (plugin)Yes (native)Yes (native)
Multi-channelLimitedYes (native)Yes (native)
Product variantsYesYesYes
Digital productsYes (plugin)YesYes
SubscriptionsYes (plugin)Yes (plugin)Yes (plugin)
B2B featuresLimitedLimitedYes (strong)
Inventory managementYesYesYes
Tax calculationPlugin-basedPlugin-basedPlugin-based
Promotions/DiscountsYesYesYes
Gift cardsYesYesYes
WebhooksYesYesYes
Job queueYes (Redis/Bull)Yes (Celery)Yes (built-in)
Plugin systemModules & pluginsPlugins & webhooksPlugins (NestJS providers)
Storefront startersNext.js, GatsbyReact Storefront, PWANext.js, Astro, React
LicenseMITBSD 3-ClauseMIT
GitHub stars25,000+docker0+5,000+
Docker supportYesYesYes

Self-Hosting Guide: Medusa

Prerequisites

  • Node.js 20+ installed
  • PostgreSQL 15+ running
  • Redis 7+ running
  • npm or pnpm package manager

Step 1: Install the Medusa CLI

1
npm install -g @medusajs/medusa-cli

Step 2: Create a New Medusa Project

1
2
medusa new my-store --skip-db --skip-migrations
cd my-store

Step 3: Configure the Database

Create a PostgreSQL database:

1
2
3
psql -U postgres -c "CREATE DATABASE medusa_store;"
psql -U postgres -c "CREATE USER medusa_user WITH PASSWORD 'your_secure_password';"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE medusa_store TO medusa_user;"

Edit .env in your project root:

1
2
3
4
5
6
7
8
DATABASE_URL=postgresql://medusa_user:your_secure_password@localhost:5432/medusa_store
REDIS_URL=redis://localhost:6379
JWT_SECRET=your_jwt_secret_here_64_chars_minimum
COOKIE_SECRET=your_cookie_secret_here_64_chars
NPM_CONFIG_LOGLEVEL=error
STORE_CORS=http://localhost:8000,https://your-storefront.com
ADMIN_CORS=http://localhost:7000,https://your-admin.com
AUTH_CORS=http://localhost:7000,https://your-admin.com

Step 4: Run Migrations and Seed Data

1
2
medusa migrations run
medusa seed

Step 5: Start the Medusa Server and Admin

1
2
3
4
5
6
7
# Terminal 1: Start the backend
medusa develop

# Terminal 2: Start the admin dashboard
cd ../admin
npm install
npm run start

The backend runs on http://localhost:9000 and the admin on http://localhost:7001. Create an admin user at /app/settings/manage-your-store.

Docker Compose Deployment

For production deployment, use Docker Compose:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
version: "3.8"

services:
  medusa:
    image: node:20-alpine
    working_dir: /app
    command: sh -c "npm install && medusa migrations run && medusa develop"
    ports:
      - "9000:9000"
    environment:
      DATABASE_URL: postgresql://medusa:medusa@postgres:5432/medusa
      REDIS_URL: redis://redis:6379
      JWT_SECRET: ${JWT_SECRET}
      COOKIE_SECRET: ${COOKIE_SECRET}
      STORE_CORS: "https://your-store.com"
      ADMIN_CORS: "https://admin.your-store.com"
    volumes:
      - .:/app
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: medusa
      POSTGRES_PASSWORD: medusa
      POSTGRES_DB: medusa
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U medusa"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  admin:
    image: node:20-alpine
    working_dir: /app/admin
    command: sh -c "npm install && npm run build"
    build:
      context: .
      dockerfile: admin/Dockerfile
    ports:
      - "7001:80"
    depends_on:
      - medusa
    restart: unless-stopped

volumes:
  pgdata:

Key Medusa plugins to consider for a production store:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Payment processing
npm install @medusajs/medusa-payment-stripe

# Search functionality
npm install medusa-plugin-meilisearch

# Email notifications
npm install @medusajs/medusa-plugin-sendgrid

# File storage (S3)
npm install @medusajs/medusa-file-s3

Self-Hosting Guide: Saleor

Prerequisites

  • Docker and Docker Compose
  • No local Python or PostgreSQL installation needed (all containerized)

Step 1: Clone the Saleor Platform Repository

Saleor provides a ready-made Docker Compose setup that runs all components:

1
2
git clone https://github.com/saleor/saleor-platform.git
cd saleor-platform

Step 2: Configure Environment Variables

Copy and edit the environment file:

1
cp .env.example .env

Key variables to set:

1
2
3
4
5
6
7
8
POSTGRES_PASSWORD=your_postgres_password
POSTGRES_USER=saleor
POSTGRES_DB=saleor
REDIS_URL=redis://redis:6379/0
JWT_SECRET_KEY=your_jwt_secret_key_minimum_32_chars
ALLOWED_CLIENT_HOSTS=localhost,127.0.0.1,your-store.com
ALLOWED_GRAPHQL_ORIGINS=https://your-store.com
CELERY_BROKER_URL=redis://redis:6379/1

Step 3: Start the Platform

1
docker compose up -d

This starts the following services:

  • saleor-api — Core GraphQL API on port 8000
  • saleor-dashboard — Admin dashboard on port 9000
  • saleor-storefront — Demo storefront on port 3000
  • postgres — PostgreSQL database
  • redis — Redis cache and message broker
  • jaeger — Distributed tracing (optional)

Step 4: Run Database Migrations and Create Admin User

1
2
docker compose exec api python manage.py migrate
docker compose exec api python manage.py createsuperuser

Follow the prompts to create your admin account. Then access the dashboard at http://localhost:9000.

Step 5: Configure Your Store

Log into the dashboard and configure:

  1. Channels — Define your sales channels (web store, mobile app, wholesale)
  2. Warehouses — Set up inventory locations
  3. Payment gateways — Configure Stripe, Adyen, or Braintree
  4. Shipping methods — Define zones, rates, and weight-based rules
  5. Products and collections — Import or create your catalog

Production Docker Compose

For production, modify the compose file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
version: "3.9"

services:
  api:
    image: saleor/saleor:3.20
    ports:
      - "8000:8000"
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_DB: ${POSTGRES_DB}
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      REDIS_URL: redis://redis:6379/0
      JWT_SECRET_KEY: ${JWT_SECRET_KEY}
      ALLOWED_CLIENT_HOSTS: ${ALLOWED_CLIENT_HOSTS}
      ALLOWED_GRAPHQL_ORIGINS: ${ALLOWED_GRAPHQL_ORIGINS}
      CELERY_BROKER_URL: redis://redis:6379/1
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  dashboard:
    image: saleor/dashboard:3.20
    ports:
      - "9000:80"
    environment:
      API_URI: https://api.your-store.com/graphql/
      APP_MOUNT_URI: /dashboard/
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - saleor-pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  saleor-pgdata:

Saleor-specific considerations for production:

  • nginxe TLS termination at the reverse proxy level (Nginx, Caddy, or Traefik)
  • Configure ALLOWED_GRAPHQL_ORIGINS strictly for your frontend domains
  • Use Saleor’s webhook system to sync orders with external ERP or accounting systems
  • The GraphQL Playground at /graphql/ is invaluable for testing queries during development

Self-Hosting Guide: Vendure

Prerequisites

  • Node.js 20+ and npm
  • PostgreSQL 15+ or MySQL 8+
  • Redis 7+ (recommended for caching)

Step 1: Install the Vendure CLI

1
npm install -g @vendure/cli

Step 2: Create a New Vendure Project

1
2
vendure create my-shop
cd my-shop

The CLI scaffolds a complete Vendure project with TypeScript configuration, including a vendure-config.ts file that controls database, authentication, payment, and plugin settings.

Step 3: Configure the Database

Edit vendure-config.ts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { VendureConfig } from '@vendure/core';
import { defaultJobQueuePlugin } from '@vendure/job-queue-plugin';
import {
  DefaultLogger,
  LogLevel,
  defaultConfig,
} from '@vendure/core';

export const config: VendureConfig = {
  apiOptions: {
    port: 3000,
    adminApiPath: 'admin-api',
    shopApiPath: 'shop-api',
    cors: {
      origin: ['https://your-store.com'],
      credentials: true,
    },
  },
  authOptions: {
    tokenMethod: ['bearer', 'cookie'],
    superadminCredentials: {
      identifier: 'superadmin@your-store.com',
      password: 'change_this_immediately',
    },
    cookieOptions: {
      secret: process.env.COOKIE_SECRET || 'change-me',
    },
  },
  dbConnectionOptions: {
    type: 'postgres',
    synchronize: false,
    logging: false,
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT || '5432', 10),
    username: process.env.DB_USERNAME || 'vendure',
    password: process.env.DB_PASSWORD || 'vendure_pass',
    database: process.env.DB_NAME || 'vendure_shop',
  },
  plugins: [
    defaultJobQueuePlugin,
    // Add payment, search, and email plugins here
  ],
};

Step 4: Set Environment Variables

Create a .env file:

1
2
3
4
5
6
7
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=vendure
DB_PASSWORD=vendure_pass
DB_NAME=vendure_shop
COOKIE_SECRET=your_random_32_char_cookie_secret
VENDURE_SECRET=your_random_32_char_vendure_secret

Create the database:

1
2
3
psql -U postgres -c "CREATE DATABASE vendure_shop;"
psql -U postgres -c "CREATE USER vendure WITH PASSWORD 'vendure_pass';"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE vendure_shop TO vendure;"

Step 5: Start the Development Server

1
npm run dev

Vendure automatically runs database migrations on first start and creates the superadmin user from your config. Access the admin UI at http://localhost:3000/admin.

Docker Compose Deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
version: "3.8"

services:
  vendure:
    image: node:20-alpine
    working_dir: /app
    command: sh -c "npm ci && npm run build && node dist/server/main.js"
    ports:
      - "3000:3000"
    environment:
      DB_HOST: postgres
      DB_PORT: "5432"
      DB_USERNAME: vendure
      DB_PASSWORD: vendure_pass
      DB_NAME: vendure_shop
      COOKIE_SECRET: ${COOKIE_SECRET}
      VENDURE_SECRET: ${VENDURE_SECRET}
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  admin-ui:
    image: node:20-alpine
    working_dir: /app/admin-ui
    command: sh -c "npm ci && npm run build"
    build:
      context: .
      dockerfile: admin-ui/Dockerfile
    ports:
      - "80:80"
    depends_on:
      - vendure
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: vendure
      POSTGRES_PASSWORD: vendure_pass
      POSTGRES_DB: vendure_shop
    volumes:
      - vendure-pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U vendure"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    restart: unless-stopped

volumes:
  vendure-pgdata:

Essential Vendure plugins for production:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Stripe payments
npm install @vendure/payments-plugin

# Asset management with S3
npm install @vendure/asset-server-plugin

# Email handling
npm install @vendure/email-plugin

# Full-text search with Elasticsearch
npm install @vendure/elasticsearch-plugin

# Or use the built-in search:
# DefaultSearchPlugin is included in @vendure/core

Vendure B2B Considerations

Vendure shines in B2B scenarios with features that the other platforms handle less elegantly:

  • Customer groups with custom pricing rules
  • Quote management for negotiated pricing
  • Custom fields on any entity (products, orders, customers) without schema migrations
  • Channel-based permissions for multi-tenant storefronts

To add a custom field to the Product entity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Product, CustomFields } from '@vendure/core';

export const config: VendureConfig = {
  customFields: {
    Product: [
      { name: 'sku', type: 'string', label: [{ languageCode: 'en', value: 'SKU' }] },
      { name: 'manufacturer', type: 'string' },
      { name: 'weight', type: 'float' },
      { name: 'isDigital', type: 'boolean', defaultValue: false },
    ],
  },
};

Performance and Scaling

Medusa Performance

Medusa’s Node.js architecture handles moderate traffic well. For high-traffic stores:

  • Use Redis for session storage and job queue processing
  • Enable CDN caching for product images and static assets
  • Use a reverse proxy (Nginx, Caddy) for SSL termination and load balancing
  • Consider horizontal scaling with multiple Medusa instances behind a load balancer

Typical resource requirements:

  • Small store (< 100 products, < 10 orders/day): 1 CPU, 1GB RAM
  • Medium store (100–1000 products, 10–100 orders/day): 2 CPU, 2GB RAM
  • Large store (1000+ products, 100+ orders/day): 4+ CPU, 4GB+ RAM, separate database server

Saleor Performance

Saleor’s Django-based architecture is robust for high-traffic stores:

  • GraphQL query complexity analysis prevents expensive queries
  • Celery workers handle async tasks (emails, webhooks, reports)
  • Redis caching significantly reduces database load for product queries
  • Jaeger tracing helps identify slow GraphQL resolvers in production

Typical resource requirements:

  • Small store: 2 CPU, 2GB RAM (Django + PostgreSQL + Redis)
  • Medium store: 4 CPU, 4GB RAM, with separate PostgreSQL
  • Large store: 4+ CPU, 8GB+ RAM, separate PostgreSQL and Redis, multiple Celery workers

Vendure Performance

Vendure’s NestJS architecture provides solid performance with TypeScript type safety:

  • Built-in job queue handles background tasks without external dependencies
  • GraphQL DataLoader prevents N+1 query problems
  • MySQL support can be advantageous for teams with existing MySQL infrastructure
  • TypeORM connection pooling handles concurrent requests efficiently

Typical resource requirements:

  • Small store: 1 CPU, 1GB RAM
  • Medium store: 2 CPU, 2GB RAM
  • Large store: 4 CPU, 4GB RAM, separate database

Choosing the Right Platform

Choose Medusa if:

  • You want the closest open-source equivalent to Shopify’s feature set
  • Your team is comfortable with JavaScript/Node.js
  • You value a large plugin ecosystem and community
  • You prefer REST APIs over GraphQL
  • You need rapid prototyping with ready-made storefront starters

Choose Saleor if:

  • GraphQL is your primary API preference
  • You need strong internationalization (multi-currency, multi-language, multi-channel)
  • You want a polished, production-ready admin dashboard
  • Your store serves multiple regions with different pricing and tax rules
  • You have Python expertise on your team

Choose Vendure if:

  • Your team works primarily with TypeScript and NestJS
  • You are building a B2B store with complex pricing, customer groups, or custom fields
  • You need MySQL support alongside PostgreSQL
  • Developer experience and type safety are top priorities
  • You want a clean plugin architecture that does not require modifying core code

Conclusion

All three platforms are production-ready, well-maintained, and genuinely open source. The decision ultimately comes down to your team’s technical stack and your store’s specific requirements.

For a solo developer or small team building a standard B2C store quickly, Medusa offers the gentlest learning curve and the richest plugin ecosystem. For a growing international brand that needs multi-channel sales and GraphQL APIs, Saleor provides the most polished experience. For a B2B-focused operation or a TypeScript-heavy team, Vendure delivers the strongest developer experience and extensibility.

Regardless of which platform you choose, self-hosting your e-commerce backend gives you complete control over your data, your customer relationships, and your technology stack — without paying platform fees or accepting vendor-imposed limitations.

Frequently Asked Questions (FAQ)

Which one should I choose in 2026?

The best choice depends on your specific requirements:

  • For beginners: Start with the simplest option that covers your core use case
  • For production: Choose the solution with the most active community and documentation
  • For teams: Look for collaboration features and user management
  • For privacy: Prefer fully open-source, self-hosted options with no telemetry

Refer to the comparison table above for detailed feature breakdowns.

Can I migrate between these tools?

Most tools support data import/export. Always:

  1. Backup your current data
  2. Test the migration on a staging environment
  3. Check official migration guides in the documentation

Are there free versions available?

All tools in this guide offer free, open-source editions. Some also provide paid plans with additional features, priority support, or managed hosting.

How do I get started?

  1. Review the comparison table to identify your requirements
  2. Visit the official documentation (links provided above)
  3. Start with a Docker Compose setup for easy testing
  4. Join the community forums for troubleshooting
Advertise here