Saleor's GraphQL API is the only door, and its Python pin is 3.12
Saleor Core: the high performance, composable, headless commerce API.
At a glance
- What is it?
- Saleor Core is a headless commerce API that offers no interface except GraphQL: the dashboard and storefront live in two other repositories, and the backend is extended through webhooks and apps instead of plugins. The dependency manifest shows what that costs, with Graphene 2, one supported Python minor, and a container image that ships the PostgreSQL client and nothing comparable for anything else.
- Who is it for?
- Saleor fits a team that already runs services, deploys daily, and needs multi-channel pricing, split payments and a GraphQL contract to build a storefront against. It does not fit a single developer with a small catalogue and no uptime requirement, which the README says outright when it weighs itself against WordPress and Magento.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- saleor/saleor23,571 ★ · +215
| Repository | 2026-09-21 | 2026-11-13 |
|---|---|---|
| saleor/saleor | 23.4k | 23.6k |
215 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Graphene 2 and graphql-core 2 are the floor under the whole API
The README insists the API surface is GraphQL only, described as native rather than an afterthought, with no fragmentation across API styles. The dependency list shows how old that promise is.
"graphene<3.0",
"graphql-core>=2.3.2,<3",
"graphql-relay>=2.0.1,<3",That is the Graphene 2 generation with graphql-core 2.x and the older relay helper, sitting alongside django-filter~=26.1, promise~=2.3 and Rx>=1.6.3 as the pieces that turn a resolver into a field. Django itself is pinned to the 5.2 line.
The consequence is concrete for anyone learning this codebase. Most current Graphene and graphql-core material on the web describes the 3.x line, so examples and Stack Overflow answers will not match what is in this tree. If you are extending the schema, check the installed major version before copying anything.
The extension story matches the same philosophy. Rather than a plugin architecture, the README names webhooks, attributes, metadata, apps, subscription queries, synchronous API extensions and dashboard iframes as the ways to extend the backend.
requires-python excludes 3.13, and petl is frozen at one release
The manifest states the supported interpreter range as >=3.12,<3.13. That is a single minor version, which is unusual for a library of this size and means a host on 3.13 fails to resolve rather than degrading.
Most dependencies use bounded ranges. babel>=2.18.0,<2.19, django[bcrypt]~=5.2.18, lxml>=6.1.1,<7, and stripe>=3.0.0 with its own upper bound. One does not.
"petl==1.7.20",petl is pinned to an exact release with no range at all, so it can never float upward on its own. If anything in the dependency tree later requires a different petl, resolution stops until a human bumps that line.
The Dockerfile agrees with the manifest, building on python:3.12 and finishing on python:3.12-slim, so the image is built and tested against exactly the version the manifest allows.
The image installs libpq5 and no other database client
The final stage of the Dockerfile is a python:3.12-slim image with a curated apt-get list, and the list is a description of what Saleor integrates with out of the box.
libpq5 is there for PostgreSQL, and no MySQL client library appears anywhere in it. Pillow's stack is present, so are libxslt1-dev with an inline comment saying libxslt is needed by lxml from PyPI, libmagic1 and media-types with a comment about identifying file types on upload, and libcurl4 with a comment attributing it to celery[sqs] using pycurl for AWS SQS support.
The process identity is deliberate too. The image runs groupadd -r saleor followed by useradd -r -g saleor saleor, then chowns /app to that user, and the service is exposed on port 8000.
The consequence for an operator: .env.example points the cache and the Celery broker at Redis, and the only database client library the image installs is libpq5 for PostgreSQL.
uv sync --locked, with uv itself pinned by digest
Dependency installation in the build stage is reproducible to the byte level of the installer. The Dockerfile copies uv and uvx out of a pinned image reference.
FROM python:3.12 AS build-python
RUN apt-get -y update \
&& apt-get install -y gettext \
# Cleanup apt cache
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*The copy step pins both a tag, ghcr.io/astral-sh/uv:0.13.5, and a sha256 digest, then sets UV_COMPILE_BYTECODE, UV_SYSTEM_PYTHON and UV_PROJECT_ENVIRONMENT=/usr/local before running uv sync --locked --no-install-project --no-editable with a cache mount for the uv cache and bind mounts for uv.lock and pyproject.toml.
The --locked flag is the part with teeth for contributors. uv.lock at the repository root has to agree with pyproject.toml or the build fails, so a dependency bump and the lock update ship in the same commit. The lock file is part of the build contract, not a convenience artefact.
collectstatic runs at image build time with SECRET_KEY=dummy
Toward the end of the Dockerfile, static files are collected during the build rather than at container start.
ARG STATIC_URL
ENV STATIC_URL=${STATIC_URL:-/static/}
RUN SECRET_KEY=dummy STATIC_URL=${STATIC_URL} python3 manage.py collectstatic --no-input
EXPOSE 8000
ENV PYTHONUNBUFFERED=1A placeholder secret is supplied for that single command, with the static URL overridable through a build argument. If any template or static view needs a real secret at collection time, the build fails here, which is a useful place to find out rather than at first request.
The development defaults in .env.example make the same point about configuration: SECRET_KEY=changeme, [email protected], EMAIL_URL=smtp://localhost:1025 pointing at a local mail catcher, and DASHBOARD_URL=http://localhost:9000/ which matches the dashboard running as a separate application. HTTP_IP_FILTER_ALLOW_LOOPBACK_IPS=True is also on by default, which is a development convenience rather than a deployment setting.
main is 3.24.0-a.0 while the newest release tag is 3.23.37
Both manifests agree on the working version. pyproject.toml and package.json both declare 3.24.0-a.0, which is an alpha, while the newest entry in the release list is 3.23.45 dated 2026-10-28. So the branch you get by default is one minor version ahead of the stable line, and the alpha suffix is there on purpose.
The README says the same thing in prose, warning that the main branch is the development version and may be unstable, and pointing at the Releases page or a release tag for the stable one. It adds that the current production-ready version is 3.x and that all three components should be on it.
Three components means three repositories. The API is here, the dashboard is a decoupled project at saleor-dashboard, and the storefront example is react-storefront, each with its own releases page. That is the practical cost of the headless decision: nothing guarantees a dashboard release exists for the alpha you just installed. Version bumps themselves are automated, with release-it and @release-it/bumper as the only devDependencies in package.json.
The README states its own tradeoff and names WordPress and Magento
Most project READMEs stop at the pitch. This one has a section asking what are the tradeoffs, and it answers directly: if you are a single developer working with a small business that does not have high traffic or a critical need for 24/7 availability, a service-oriented approach might feel more complex compared to the traditional WordPress or Magento approach that provides a language-specific framework, runtime, database schema, aspect-oriented programming and other tools to a quick start.
The difference in approach is the whole sentence. The traditional option arrives as one integrated stack with a runtime and schema included. Saleor arrives as an API and nothing else, so the dashboard and storefront are separate applications, the schema is exposed only over GraphQL, and the operations behind it, cache, broker, search and storage, are services you configure through environment variables.
The same section says who is in the right place: if you deploy daily, if reliability and uptime are critical, if you collaborate with other developers, or if you have non-trivial requirements. That is the project's own filter, and it is a reasonable one to apply before reading further.
Extensibility is webhooks and apps, so a plugin has nowhere to go
The README describes an extension surface defined entirely in the API: webhooks including subscription queries and synchronous events, attributes and metadata for custom data, apps, and iframes that extend the dashboard with any web stack.
The README's argument for this design is a list of consequences rather than a claim about elegance. There is less downtime because apps are deployed independently. Custom logic is separated from the core. Upgrade paths get simpler because extensions do not collide with the monolith. Debugging narrows to a service. And extensions can be scaled independently.
Two repository-level details point the same way. socket.yml at the root is configuration for dependency analysis, and a .semgrep/ directory holds static analysis rules, so the supply chain of a platform this size is treated as a tracked concern rather than a build-time accident.
Editorial conclusion
Saleor fits a team that already runs services, deploys daily, and needs multi-channel pricing, split payments and a GraphQL contract to build a storefront against. It does not fit a single developer with a small catalogue and no uptime requirement, which the README says outright when it weighs itself against WordPress and Magento. Verify first the Python version on your host, because requires-python is >=3.12,<3.13, and then confirm someone can release all three repositories, since the dashboard and the storefront ship separately from the API.
Frequently asked questions
How do you install Saleor?
The README sends you to the Docker Compose setup page at docs.saleor.io/setup/docker-compose for step by step instructions, and to CONTRIBUTING.md for local development without Docker. For the hosted option it documents the CLI: npm i -g @saleor/cli, then saleor register, then saleor storefront create --url {your-saleor-graphql-endpoint}.
What is Saleor?
Saleor Core is a headless commerce API built with Python, Django and GraphQL, licensed BSD-3-Clause. It is API-only by design: the dashboard and the storefront are separate projects, and the backend is extended through webhooks and apps rather than plugins.
How does Saleor compare to Shopify?
The comparison the README makes is with traditional WordPress and Magento style platforms, which it describes as shipping a language-specific framework, runtime and database schema, versus an API-only design where the dashboard and storefront are separate applications you build or replace.
What are the alternatives to Saleor?
The README names WordPress and Magento as the traditional monolithic alternative and describes them as a language-specific framework, runtime and database schema with a quick start. It also points at Saleor Cloud for a hosted path and at react-storefront as a storefront example.
Official sources
Where this project is recommended
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/saleor-saleor)Star history chart for your README
[](https://hysenlabs.com/projects/saleor-saleor)Was this useful?