Both platforms tick the same three boxes — GraphQL, headless, open source — so ticking them tells you nothing about which one to run. The real split shows up one layer down: how a Channel scopes currency, how an extension attaches to the runtime, and where each platform draws the line between free and paid. Every claim below is checked against saleor-core 3.23.22 and vendure-core 3.7.1, targeting Docker 28.1.1, Ubuntu 24.04 LTS and PostgreSQL 17.5.
Saleor Apps run out-of-process, Vendure plugins run in the same server
Saleor’s docs describe an App as a “technology-agnostic client that can communicate with Saleor using API and webhooks” (opens in a new tab), and state that new integrations should be built this way rather than as legacy in-process plugins. That single sentence explains most of the operational difference between the two platforms. An App is a separate service; a Vendure Plugin is not.
A Saleor App runs as its own process, hosted wherever you put it, written in whatever language you want. Saleor talks to it over HTTPS webhooks and a manifest that declares the App’s name, the permissions it needs, and the URLs Saleor should call. Nothing about the App shares memory, a database connection or a deploy pipeline with Saleor Core.
A Vendure Plugin (opens in a new tab) is, in the docs’ own words, “an enhanced version of a NestJS Module” that runs inside the same process as the core server. From one package, a Plugin can extend the GraphQL schema through shopApiExtensions and adminApiExtensions, add TypeORM entities or custom fields, subscribe to the core event bus, and register REST controllers (opens in a new tab) — all without a network hop.
The two models fail differently, and that’s by design, not accident. A Saleor App that goes unreachable, or ships a manifest with the wrong scopes, stops receiving events silently — Saleor logs it, but nothing blocks the order from completing. A Vendure Plugin whose TypeScript, NestJS or TypeORM version has drifted from the host server after a core upgrade fails to compile or load, which is loud and blocks a deploy.
If you want polyglot services and network isolation between core and integrations, Apps fit that shape. If you want direct schema and database access without deploying a second service, Plugins do. Neither is strictly safer — they move the failure to a different point in the pipeline.
sequenceDiagram
participant Core as Saleor Core
participant App as Saleor App (external service)
Core->>App: POST webhook (ORDER_CREATED)
Note over App: Endpoint unreachable, or manifest scope mismatch
App--xCore: No response / 4xx
Note over Core: Delivery marked failed in the dashboard
Core->>Core: Order still completes -- App's side effect never runs
A minimal Saleor App manifest scopes exactly what the App can see and where events get delivered:
{
"id": "app.example.b2b-pricing",
"version": "1.0.0",
"name": "B2B Pricing Sync",
"about": "Syncs negotiated B2B prices into Saleor order lines.",
"permissions": [
"MANAGE_ORDERS",
"MANAGE_PRODUCTS"
],
"appUrl": "https://b2b-pricing.example.com/app",
"tokenTargetUrl": "https://b2b-pricing.example.com/api/register",
"webhooks": [
{
"name": "Order created",
"targetUrl": "https://b2b-pricing.example.com/webhooks/order-created",
"asyncEvents": ["ORDER_CREATED"],
"query": "subscription { event { ... on OrderCreated { order { id } } } }"
}
]
}A minimal Vendure Plugin, by contrast, ships as TypeScript compiled directly into the server process:
import { PluginCommonModule, VendurePlugin } from '@vendure/core';
import gql from 'graphql-tag';
const shopApiExtensions = gql`
extend type Query {
negotiatedPrice(productVariantId: ID!): Money
}
`;
@VendurePlugin({
imports: [PluginCommonModule],
shopApiExtensions: {
schema: shopApiExtensions,
resolvers: [],
},
})
export class B2BPricingPlugin {}A Saleor channel holds one currency, a Vendure channel holds many
A Saleor Channel (opens in a new tab) belongs to exactly one currency, fixed at creation as a single currencyCode field. There is no list of supported currencies to configure — the docs state it plainly: a channel belongs to a single currency. If you need three currencies in one region, you create three channels, each with its own prices, stock allocation and sync logic.
A Vendure Channel is built differently. It carries a defaultCurrencyCode plus an availableCurrencyCodes set, so one channel supports several currencies natively. A product variant has a one-to-many relation to ProductVariantPrice, meaning a single UK channel can hold a GBP price and a USD price on the same variant — something a single Saleor channel structurally cannot express.
This isn’t a configuration gap you can work around with a flag. It’s a schema-level decision each platform made early, and it shapes how you’ll model regions later. Get it wrong up front and the fix isn’t a setting — it’s more channels.
Pitfall: a team builds a storefront around one Saleor channel named eu, expecting it to serve EUR, GBP and SEK. It starts returning EUR prices to GBP and SEK shoppers, because the channel was never multi-currency — it just happened to launch with EUR configured. The fix is splitting eu into three channels, not adding currencies to one.
flowchart LR
subgraph Saleor["Saleor: one channel = one currency"]
SC1["Channel: de-eur
currencyCode: EUR"]
SC2["Channel: uk-gbp
currencyCode: GBP"]
SC3["Channel: us-usd
currencyCode: USD"]
end
subgraph Vendure["Vendure: one channel = many currencies"]
VC["Channel: europe
defaultCurrencyCode: EUR
availableCurrencyCodes: EUR, GBP, USD"]
VV["ProductVariantPrice
one row per currency"]
VC --> VV
end
Creating a Saleor channel commits to one currency at the mutation level:
mutation CreateChannel {
channelCreate(
input: {
name: "Germany EUR"
slug: "de-eur"
currencyCode: "EUR"
defaultCountry: DE
}
) {
channel {
id
slug
currencyCode
}
errors {
field
message
}
}
}A Vendure channel query returns a currency list, and a variant can carry a price for each entry in it:
query ChannelDetails {
activeChannel {
id
code
defaultCurrencyCode
availableCurrencyCodes
}
}
query VariantPricesInChannel {
product(id: "42") {
variants {
id
sku
prices {
currencyCode
price
}
}
}
}Saleor stays BSD-3-Clause, Vendure moved to GPLv3 in July 2024
Saleor Core is licensed BSD-3-Clause, confirmed in the repository’s LICENSE file (opens in a new tab), with no field-of-use restriction. You can fork it, modify it, and ship a closed-source product built on it without an obligation to release your changes.
Vendure Core’s licence is not the same one it shipped with earlier. It changed from MIT to GPLv3 with the v3.0 release on 17 July 2024 (opens in a new tab). Vendure’s own licensing page (opens in a new tab) confirms Vendure Core is “free under GPLv3 for developers building and operating a custom ecommerce application,” with a stated exception that lets plugins built against Vendure be released under any licence of your choosing.
GPLv3 is copyleft. If you distribute a modified version of Vendure Core itself, that modified version must also be released under GPLv3 — an obligation BSD-3-Clause never imposes on Saleor. Running Vendure as an internal service and never distributing the modified core is a materially different situation from shipping a modified Vendure Core to customers, and the two trigger different obligations under the licence.
This section states the licence facts only. It is not legal advice on how GPLv3’s copyleft terms apply to any specific deployment, and if the distinction between “running” and “distributing” matters for your situation, that’s a question for counsel, not a blog post.
Saleor’s B2B tools are free, Vendure’s B2B suite costs 40,000 EUR a year
Saleor ships no dedicated B2B module. Its documented pattern composes two primitives that already exist for other reasons: map B2B and B2C customer types to separate Channels for exclusive pricing, then use draft orders (opens in a new tab) to “generate draft orders with special pricing or configurations for B2B sales or negotiated deals, and confirm them once approved.” Everything here ships in Saleor Core, under BSD-3-Clause, at no extra cost.
Vendure’s B2B feature set (opens in a new tab) is purpose-built: company accounts with roles and permissions, quote-to-order (CPQ) workflows with approval steps, contract and customer-group pricing, credit limits and payment terms, and a production-ready B2B storefront (opens in a new tab) on Next.js. It’s a materially deeper feature set than Saleor’s composed-from-primitives approach.
None of it ships in the free, GPLv3 @vendure/core package. All of it sits inside the commercial Vendure Platform (opens in a new tab), priced from €40,000 per year as a flat fee, which the pricing page describes as carrying “no GMV, order, user, or capability-based charges.” That’s a five-figure annual commitment before a single order flows through it.
Pitfall: a team scopes a quote-and-approval workflow against Vendure’s B2B documentation, installs @vendure/core, and can’t find the company-account or quote APIs the docs described. CustomerGroups is the free building block Core actually ships; the finished company-account and CPQ workflow is Platform-only. Confirm which package a specific B2B feature lives in before it goes on a project plan.
flowchart TB
subgraph SaleorFree["Saleor -- all free, BSD-3-Clause"]
S1["Channels mapped to customer type"]
S2["Draft orders with special pricing"]
end
subgraph VendureCore["Vendure Core -- free, GPLv3"]
V1["CustomerGroups"]
V2["Custom fields and custom plugins"]
end
subgraph VendurePlatform["Vendure Platform -- EUR40,000 per year"]
P1["Company accounts, roles, permissions"]
P2["Quote-to-order (CPQ) with approvals"]
P3["Contract and customer-group pricing"]
P4["Credit limits, payment terms"]
P5["B2B storefront"]
end
VendureCore -. requires upgrade for .-> VendurePlatform
Saleor Cloud has four priced tiers, Vendure Cloud has none yet
Saleor Cloud (opens in a new tab) is live and generally available, with four published tiers as of the research date behind this comparison, 2026-07-28:
| Tier | Price | Monthly GMV ceiling | Overage |
|---|---|---|---|
| Sandbox | Free | — | — |
| Select | $1,599/month | $200,000 | 0.8% |
| Volume | $3,999/month | $1,000,000 | 0.4% |
| Enterprise | Custom-quoted | — | negotiable |
Vendure Cloud (opens in a new tab) is not there yet. Vendure’s own pricing page lists capabilities such as multi-region routing, regional data residency and bring-your-own-cloud (BYOC) as “At GA” — meaning shipped once Cloud reaches general availability, not shipped now — and its FAQ carries an unanswered entry: “How is Vendure Cloud priced?”
Pitfall: a budget spreadsheet lists “managed Vendure hosting” at a price matched to Saleor Cloud’s Select or Volume tier. There’s no equivalent Vendure list price to copy that number from — Vendure Cloud has no public price yet, so treat Vendure as self-hosted-only for cost modelling until that changes.
If managed hosting has to be a contractable, priced line item today, that fact alone favours Saleor Cloud, independent of which platform otherwise fits the rest of the stack better.
Line up Saleor and Vendure on the same editions, dates and units
Every figure below is pinned to the same core versions, the same research date (2026-07-28) and the same unit, so you can compare rows directly instead of re-deriving them from separate pages later.
| Dimension | Saleor | Vendure | Value type |
|---|---|---|---|
| Core version | 3.23.22 | 3.7.1 | Official version number |
| Licence | BSD-3-Clause | GPLv3 (since v3.0, 17 July 2024) | Official licence |
| Runtime range | Python >=3.12,<3.13 | Node ^20.19.0 || >=22.12.0 | Official requirement |
| Channel currency model | Single currencyCode per channel | defaultCurrencyCode + availableCurrencyCodes list | Interpretive characterisation |
| Extension model | Apps, out-of-process, webhook-driven | Plugins, in-process NestJS module | Interpretive characterisation |
| B2B availability | Free primitives (Channels + draft orders) | €40,000/year Vendure Platform | Official pricing, interpretive framing |
| Managed cloud status | GA, four published tiers | ”At GA”, unpriced | Official status |
The licence, version and pricing rows are figures pulled directly from each platform’s own documentation or repository. The currency-model and extension-model rows are this article’s characterisation of how those documented mechanics behave in practice — read them as framing, not as a quoted spec.
Pick Saleor or Vendure based on channels, extensions and B2B budget
Saleor fits when a region prices in one currency — or you’re fine splitting channels per currency — when integrations are written as separate services in any language, when a live priced managed-hosting option matters today, and when B2B needs fit inside Channels plus draft orders without company hierarchies or approval chains.
Avoid Saleor when a single storefront region has to price in several currencies at once inside one channel, or when B2B requirements include company account hierarchies, multi-step approvals, or CPQ workflows that draft orders can’t express on their own.
Vendure fits when a channel genuinely needs multiple currencies at once, when extensions are written in TypeScript with direct database and schema access preferred over a network hop, and when the B2B budget can absorb a €40,000-a-year Platform licence for company accounts, quotes and approvals.
Avoid Vendure when there’s no budget for the commercial Platform tier and B2B needs exceed what CustomerGroups alone covers, or when managed hosting has to be a priced, contractable line item today rather than something to revisit once Vendure Cloud reaches GA.
Answer four questions in order, and most teams will diverge on at least one of them before reaching the last:
flowchart TD
Q1{"More than one currency
inside a single channel?"}
Q1 -->|Yes| V1["Lean Vendure"]
Q1 -->|No| Q2{"Extensions written in TypeScript
with direct DB access preferred?"}
Q2 -->|Yes| V2["Lean Vendure"]
Q2 -->|No| Q3{"B2B needs exceed
draft orders / CustomerGroups?"}
Q3 -->|Yes, budget covers EUR40k/yr| V3["Lean Vendure Platform"]
Q3 -->|Yes, no budget for it| S1["Lean Saleor"]
Q3 -->|No| Q4{"Managed hosting must be
a priced line item today?"}
Q4 -->|Yes| S2["Lean Saleor Cloud"]
Q4 -->|No| E1["Either fits -- decide on team language and ops preference"]
What this Saleor vs Vendure comparison did not test
This comparison did not deploy either platform against Docker 28.1.1, Ubuntu 24.04 LTS and PostgreSQL 17.5 to measure real query latency, cold-start time, or webhook delivery reliability under load. Every version and configuration number above is written against the documented target, not a measured one — treat the deployment steps as pending until someone runs them.
No side-by-side benchmark of GraphQL schema completeness or query performance was run. “GraphQL is the similarity, not the decision” is a claim about API paradigm and extension architecture, not about measured throughput on either schema.
Vendure Cloud’s eventual pricing wasn’t available to compare against Saleor Cloud’s published tiers, because it doesn’t exist yet — this comparison needs a re-check once Vendure Cloud reaches general availability. No migration path between the two platforms was tested, and no official migration tool is known to exist for moving a live store from one to the other.
No legal assessment of GPLv3’s copyleft obligations for any specific commercial deployment was performed. That determination depends on how you distribute or run a modified Vendure Core, and it needs your own counsel, not a general statement in an engineering comparison.