Self-hosted service
saleor/saleor-dashboard avatar
saleor/saleor-dashboard

saleor-dashboard: the React front end that talks GraphQL to a Saleor API

A GraphQL-powered, single-page dashboard application for Saleor.

1,045 stars1,234 forksTypeScriptBSD-3-Clause

At a glance

What is it?
saleor-dashboard is the single-page admin application for Saleor, a GraphQL commerce API, and it is developed in the open as its own repository with a two-generation build, a self-contained end to end suite, and a Docker image that serves static assets from nginx. This looks at what the dashboard is and how it is built.
Who is it for?
saleor-dashboard fits a team running Saleor as its commerce backend and wanting an admin interface that is developed in the open alongside the API, and it fits anyone who wants to run a Saleor instance locally without a cloud account. It does not fit a team that wants a general purpose admin panel for a different backend, and it is the wrong choice if you need a single build that talks to both a stable and an unreleased API, because the target is fixed at build time.
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 last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One repository, one half of a two-part system

saleor-dashboard is described as a GraphQL-powered, single-page dashboard application for Saleor, and the repository holds the front end only. The Saleor API itself lives in the separate saleor/saleor repository, and the readme links to it in the prerequisites as a running instance of Saleor.

That separation is worth stating plainly, because it decides what this repository is for. It is not an admin template you can point at your own data. It is the operator interface for one specific commerce platform, written against that platform's GraphQL schema, and it is versioned in lockstep with the API it talks to.

The stack is visible in the manifest. The package is `saleor-dashboard`, the main entry is `src/index.tsx`, and the tooling names the framework choices: `vite` for the build and the dev server, `storybook` for component work, `jest.config.js` and the Jest based test scripts, `eslint.config.mjs` and `graphql-lint.config.js` for linting, `react-intl` and a `locale/` directory for translations, `apollo.config.js` and `graphql.config.ts` for the GraphQL layer, `lighthouserc.json` for performance budgets, `codecov.yml` for coverage, and `knip.json` for unused code detection. There is a `dependency-cruiser` configuration as well, with a `dep-status` script that runs it over `src`, which is how the project keeps the module boundaries between the source, the queries and the generated code from decaying.

The licence is BSD-3-Clause, the author is Saleor Commerce, and the default branch is `main`.

The local development loop, and the CORS trap

The prerequisites are short and specific: Node.js v24, a running instance of Saleor, the PNPM package manager, preferably installed via corepack, and Docker to run the E2E suite, since it starts its own Saleor.

The development sequence is five steps.

bash
git clone https://github.com/saleor/saleor-dashboard.git
bash
cd saleor-dashboard
bash
pnpm install

Then configure the env vars as described in `docs/configuration.md`, and start the dev server with:

bash
pnpm run dev

The `predev` script runs `pnpm run generate` first, which is the code generation step, and the `dev` script itself is `vite --host`. There is also a `dev:no-strict` variant that sets `VITE_DISABLE_STRICT_MODE=true`, which tells you something about the codebase: there is a strict mode and there is a way out of it.

One warning in the readme is worth internalising before you lose an hour. If you see CORS errors, check the CORS configuration of your Saleor instance, or the CORS settings in the Cloud Console. The dashboard is a browser application calling a GraphQL API on another origin, and the browser will block it unless the API says otherwise. That is the single most common first-run problem, and it is a configuration matter on the API side rather than a bug in the dashboard.

There is also an optional nicety for people running several checkouts at once. portless serves the dev server on a stable `https://dashboard.localhost` instead of `http://localhost:9000`, which the readme says is useful when you run several checkouts or worktrees at once, since each one gets its own URL instead of fighting over port 9000. The repository is already configured with `portless.json`, but portless is not a dependency, so it has to be installed globally with `npm install -g portless` and then run in place of `pnpm run dev`.

Two API generations means two bundles and two backends

The most consequential design decision in the repository is that the dashboard supports both Saleor generations at once, and which one a given build speaks to is decided at build time. Each target is therefore a separate Saleor and a separate bundle.

The build scripts show it. There is `codegen-main.ts` and `codegen-staging.ts`, and the schema files at the root are `schema-main.graphql`, `schema-staging.graphql` and `schema.graphql`, with a `schema-directives.graphql` alongside them. The container build selects one explicitly, with `RUN pnpm run generate:main` and a single `schema-main.graphql` copied into the image.

The consequence for a team is straightforward but easy to miss. A deployment is not one artefact, it is one artefact per API generation, and the frontend has to be rebuilt when the schema changes. That is the cost of a codegen-driven front end: the types are generated from the schema, so the schema is a build input rather than a runtime contract, and a mismatch between the deployed bundle and the running API is a build-time problem rather than a runtime surprise.

The documentation has a page for exactly this, Developing with stable and staging Saleor graphql.schema, and the `docs/multi-schema.md` path in the docs list points at it. The `build-flags` script and the `.featureFlags/` directory suggest the other half of the story, which is that the two targets are not only different schemas but also different feature sets.

The version number tracks the API. The manifest is at 3.23.39, which corresponds to the 3.23 line of Saleor, and the E2E script for the stable target is described as running against the latest stable Saleor, 3.23.

The E2E suite brings up its own Saleor and resets the database

The end to end suite is the part of this repository that most distinguishes a serious project from a demo, and the readme is unusually clear about what it does.

The suite brings up its own Saleor in Docker, seeds it, builds the dashboard from your working tree and drives it in a browser, with no Saleor Cloud, no credentials and nothing to configure.

bash
pnpm e2e         # against the latest stable Saleor (3.23)
pnpm e2e:main    # against Saleor's unreleased main branch
pnpm e2e:all     # both
pnpm e2e --ui    # ...and anything else Playwright understands
pnpm e2e:fresh   # start over from an empty database
pnpm e2e:down    # stop the stacks when you are done

Two details make this usable in practice. The first run pulls an image, migrates and seeds, which takes a few minutes, and afterwards a run reaches the first test in seconds. And the database is restored before every test, so a failing test reproduces on its own rather than only failing in the order it happened to run.

The `e2e/README.md` holds the seeds, the actors and how to port a spec, which is the documentation a contributor needs.

There is also a previous suite, `e2e-legacy/`, which runs against a provisioned Saleor Cloud instance with `pnpm run e2e:legacy`. It is frozen, and the readme says new tests belong in `e2e/`. That is a sensible migration path: the old suite tested against a real cloud instance, the new one tests against a container it controls, and the reason to move is that a suite you can run without credentials is a suite contributors will actually run.

The container build ends in nginx, not in a Node server

The Dockerfile is worth reading because it shows a deployment decision that a lot of single-page applications make and a few do not.

The builder stage is `node:24-alpine`, with corepack enabled and pnpm 11.13.1 prepared and activated, and the dependency install uses `pnpm install --frozen-lockfile` with `CI=1` set. The source is copied in layer by layer, the schema, the feature flags, the locales and the assets before the code, which is ordinary cache-friendly ordering.

The build itself is driven by arguments: `API_URL`, `APP_MOUNT_URI`, `EXTENSIONS_API_URL`, `STATIC_URL`, `SKIP_SOURCEMAPS` and `LOCALE_CODE`. The environment defaults are set with sensible fallbacks, `API_URL` defaulting to `http://localhost:8000/graphql/` and `APP_MOUNT_URI` to `/dashboard/`, and the build runs `pnpm run generate:main` followed by a Vite build with an 8GB Node heap, matching the `cross-env NODE_OPTIONS=--max-old-space-size=8192` in the `build` script.

The runner stage is `nginx:stable-alpine`, and it copies the built assets in, along with `nginx/default.conf` and an entrypoint script `nginx/replace-env-vars.sh` that rewrites the environment values into the served files at container start. So the image is a static bundle behind nginx with the API address injected at runtime, not a Node application server.

The image carries standard OpenContainers labels, including the description, the source URL, the revision and version, the authors, and a licences label of BSD 3.

The wider tooling, and what the dashboard is not

The `package.json` scripts show a project that invests in its own checks, and that is the strongest signal about how the work is done.

There is a type checking family, `check-types` running `check-types:e2e`, `check-types:e2e-legacy`, `check-types:scripts` and `check-types:src`, the last being `tsc --noEmit && tsc-strict`, plus a separate `check-strict-null-errors` script that counts strict null check errors through a small Node script. `dep-status` runs dependency-cruiser over `src`. `actionlint` runs the GitHub Actions linter inside a pinned Docker image. `chromatic` publishes visual changes, `build-storybook` builds the component workshop, and `knip.json` is there to find dead code.

The repository carries `AGENTS.md`, `CLAUDE.md`, `CONTEXT.md`, `WARP.md` and a `prompts/` directory, plus `.changeset/` for versioned changelog entries and `.husky/` for commit hooks.

With all that in place, the boundary is worth stating for an evaluator. This is the admin interface for one commerce platform. It is not a headless admin framework, it does not run without a Saleor API behind it, it has no offline mode, and it does not talk to any other backend. If your requirement is a customisable admin panel over your own data model, this is the wrong repository, and the amount of GraphQL schema it assumes is the reason why.

On maintenance, the record is busy and regular. 3.23.37 and 3.23.38 were published on 2026-10-01, 3.23.39 on 2026-10-06, and the last push to the repository was on 2026-10-15.

Editorial conclusion

saleor-dashboard fits a team running Saleor as its commerce backend and wanting an admin interface that is developed in the open alongside the API, and it fits anyone who wants to run a Saleor instance locally without a cloud account. It does not fit a team that wants a general purpose admin panel for a different backend, and it is the wrong choice if you need a single build that talks to both a stable and an unreleased API, because the target is fixed at build time. Before you build it, check the environment variables in `docs/configuration.md` and the API URL your instance exposes, and note that the dashboard and the API version separately.

Frequently asked questions

How do I run the Saleor dashboard locally?

Clone the repository, enter the directory, run `pnpm install`, configure the env vars as described in docs/configuration.md, and start the dev server with `pnpm run dev`. You need Node.js v24, a running Saleor instance, and PNPM, preferably installed via corepack.

Why do I get CORS errors when starting the dashboard?

The readme says to check the CORS configuration of your Saleor instance, or the CORS settings in the Cloud Console. The dashboard is a browser app calling the GraphQL API on another origin, so the API has to allow it.

Can the dashboard talk to two Saleor versions at once?

The dashboard supports both Saleor generations, but which one a build speaks to is decided at build time, so each target needs a separate Saleor and a separate bundle. The container build shows this with `pnpm run generate:main` and a single schema copied into the image.

Do I need a Saleor Cloud account to run the E2E tests?

No. The suite brings up its own Saleor in Docker, seeds it, builds the dashboard from your working tree and drives it in a browser, with no Saleor Cloud and no credentials. The older e2e-legacy suite did require a provisioned cloud instance and is frozen.

How is the dashboard container built and served?

A node:24-alpine builder stage installs dependencies with a frozen lockfile, generates the code and builds with Vite, and the runtime stage is nginx:stable-alpine serving the built assets, with an entrypoint script that substitutes environment values such as API_URL and STATIC_URL into the served files at container start.

Official sources

  1. License: BSD-3-Clause
  2. Project website
  3. README
  4. Releases
  5. saleor/saleor-dashboard on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/saleor-saleor-dashboard.svg)](https://hysenlabs.com/projects/saleor-saleor-dashboard)

Was this useful?