Skip to content
Saleor
Esc
↑↓navigate↵open⌘Jpreview
On this page

Customers and Staff as One User

Customers and staff are not separate entities in Saleor. Both are represented by the same User GraphQL type. The role of a given user is determined by the isStaff field and by which mutations and permissions apply to them.

This page explains how the two roles relate at the API level: which fields are meaningful for each, which mutations to use to create or manage them, and how a user can move between roles.

One type, two roles

Every account — whether a shopper or an administrator — is a User. The same fields (id, email, firstName, lastName, addresses, metadata, orders, etc.) are available on both. The difference is:

  • isStaff: Boolean! — true for staff members, false for customers. It is exposed publicly on the User type and is also included as a claim in the JWT issued by tokenCreate.
  • Role-specific fields — see below.
  • Permissions required to manage them — MANAGE_USERS for customers, MANAGE_STAFF for staff.

Fields that are meaningful only for staff

The following fields exist on every User but are only populated for staff. For customers they return empty lists or false:

Field Meaning
permissionGroups Permission groups the user belongs to. Customers cannot belong to permission groups via the public API.
userPermissions Effective permissions granted directly or via groups.
editableGroups Groups the user is allowed to manage (used by the Dashboard).
accessibleChannels Channels the staff user has access to via their permission groups.
restrictedAccessToChannels Whether the staff user’s access is limited to a subset of channels.

See Permissions for how permission groups and channel restrictions work.

Choosing a create mutation

There are three mutations that produce a User. Pick the one that matches the caller and the resulting role:

Mutation Caller Required permission Resulting role
accountRegister The user themselves (storefront) None Customer
customerCreate Staff or app MANAGE_USERS Customer
staffCreate Staff or app MANAGE_STAFF Staff (isStaff: true)

accountRegister and customerCreate both produce a customer; the difference is who initiates the action and whether a password is set up-front or via an emailed link. See Manage Account as a Customer and Customer Management.

Promoting a customer to staff

If staffCreate is called with an email that already belongs to a customer, the existing User is reused and promoted to staff. The same id, addresses, metadata, and order history are preserved — only isStaff flips to true and any provided groups are assigned.

There is no public mutation to demote a staff user back to a customer. To revoke staff access, use staffDelete or remove the user from all permission groups via staffUpdate.

End-to-end lifecycle: self-registration, then staff invite

A common flow that causes confusion: a shopper signs up through the storefront, then is later invited by an administrator to become staff. Because both roles share the same User, the order history, addresses, and metadata gathered as a customer carry over after the promotion.

The table below lists, for each mutation in this flow, the asynchronous events emitted and how the corresponding email is delivered. The split is important:

  • Staff emails (invite / set password / password reset for staff) are sent by the core admin email plugin when it is configured. No external app is required.
  • Customer emails (account confirmation, set password, password reset for customers) are not sent by Saleor core. An external app must subscribe to the corresponding webhook and deliver the email itself — typically Customer Emails on Saleor Cloud, or the SMTP app if you are self-hosting.
# Mutation Async events emitted Email
1 accountRegister CUSTOMER_CREATED; additionally ACCOUNT_CONFIRMATION_REQUESTED if enableAccountConfirmationByEmail is true Customer confirmation email. Not sent by core — requires an external app (e.g. Customer Emails on Cloud, or the SMTP app when self-hosting) subscribed to ACCOUNT_CONFIRMATION_REQUESTED.
2 confirmAccount ACCOUNT_CONFIRMED No email required for the confirmation step itself. If you want a “welcome” email, subscribe an external app to ACCOUNT_CONFIRMED.
3 accountUpdate (customer edits own profile) CUSTOMER_UPDATED; CUSTOMER_METADATA_UPDATED if metadata changed None by default.
4 requestPasswordReset (while still a customer) ACCOUNT_SET_PASSWORD_REQUESTED Customer password reset email. Not sent by core — requires an external app subscribed to ACCOUNT_SET_PASSWORD_REQUESTED.
5 setPassword No webhook event is emitted by this mutation itself. No email.
6 staffCreate called with the existing customer’s email STAFF_CREATED (the same User.id is reused; isStaff becomes true). Additionally STAFF_SET_PASSWORD_REQUESTED if redirectUrl is provided. Staff invite / set password email. Sent by the core admin email plugin when configured. The customer-facing ACCOUNT_SET_PASSWORD_REQUESTED event is not emitted in this case, so an external customer-email app does not receive a duplicate.
7 staffUpdate STAFF_UPDATED None by default.
8 requestPasswordReset (now as a staff user) STAFF_SET_PASSWORD_REQUESTED Staff password reset email. Sent by the core admin email plugin when configured.
9 setPassword No webhook event is emitted by this mutation itself. No email.

Choosing an update or delete mutation

Update and delete mutations are split along the same lines as creation:

Action Self-service Customer (by staff) Staff (by staff)
Update accountUpdate customerUpdate staffUpdate
Delete accountDelete, accountRequestDeletion customerDelete staffDelete

The account* mutations always operate on the currently authenticated user and never change the user’s role. The customer* and staff* mutations require the matching permission and only operate on users of the corresponding role.

Querying customers and staff

The API exposes role-scoped list queries and a single by-identity lookup:

  • customers — requires MANAGE_USERS or MANAGE_ORDERS. Returns customer accounts. Staff users who have placed an order also appear in this list, because order history is tied to the underlying User.
  • staffUsers — requires MANAGE_STAFF. Returns staff accounts only.
  • user — lookup by id, email, or externalReference. The result is scoped by the caller’s permissions: MANAGE_STAFF resolves staff, MANAGE_USERS or MANAGE_ORDERS resolves customers, holding both resolves any user.
  • me — returns the currently authenticated user regardless of role.

Dual-role behavior

Because a staff member and a customer are the same kind of entity, a single User can act in both roles at once. A staff user who places an order keeps their isStaff: true but will also be returned by the customers query. Their order history, addresses, and metadata are all on the same record.

When building integrations, treat the role as a property of the user (isStaff) rather than as a separate identity. Do not assume that “appears in customers” implies “is not staff”.

Authentication

Customers and staff authenticate against the same endpoints and receive tokens of the same shape:

  • tokenCreate issues an access token and refresh token for either role. The decoded token includes the is_staff claim, but permissions are not embedded — they are resolved on each request from the user’s groups and direct permissions.
  • tokenRefresh, tokenVerify, and tokensDeactivateAll work the same for both roles.

The passwordLoginMode shop setting can restrict password login for staff while leaving it available for customers. See Authentication for the full token lifecycle and JWT structure.

Permission groups are staff-only

Permission groups (permissionGroupCreate, permissionGroupUpdate, permissionGroupDelete) require MANAGE_STAFF and only operate on staff members. Customers cannot be added to a permission group through the public API, and the permissionGroups field on a customer’s User will always be empty.

If you need to grant a customer access to staff capabilities, promote them to staff via staffCreate first.

Last updated on September 8, 2026

Was this page helpful?