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!—truefor staff members,falsefor customers. It is exposed publicly on theUsertype and is also included as a claim in the JWT issued bytokenCreate.- Role-specific fields — see below.
- Permissions required to manage them —
MANAGE_USERSfor customers,MANAGE_STAFFfor 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 using the SMTP App or any other app you build or install.
| # | Mutation | Async events emitted | |
|---|---|---|---|
| 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. SMTP App) 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. |
Saleor core ships an "admin email" plugin that covers staff notifications (staff invite, staff password reset, etc.) out of the box. It does not send emails to customers. To deliver account confirmation, customer password reset, order confirmation, and other customer-facing emails, install an app such as the SMTP App and subscribe it to the relevant ACCOUNT_* and CUSTOMER_* webhook events.
isStaff at the time of the requestrequestPasswordReset branches on the user's current role: it emits ACCOUNT_SET_PASSWORD_REQUESTED for customers and STAFF_SET_PASSWORD_REQUESTED for staff. Once a user has been promoted via staffCreate, all subsequent password-related notifications go through the staff event and the admin email plugin.
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— requiresMANAGE_USERSorMANAGE_ORDERS. Returns customer accounts. Staff users who have placed an order also appear in this list, because order history is tied to the underlyingUser.staffUsers— requiresMANAGE_STAFF. Returns staff accounts only.user— lookup byid,email, orexternalReference. The result is scoped by the caller's permissions:MANAGE_STAFFresolves staff,MANAGE_USERSorMANAGE_ORDERSresolves 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:
tokenCreateissues an access token and refresh token for either role. The decoded token includes theis_staffclaim, but permissions are not embedded — they are resolved on each request from the user's groups and direct permissions.tokenRefresh,tokenVerify, andtokensDeactivateAllwork 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.