Version: Next

Writing Apps for Saleor

Key Concepts

Architecture

Saleor Apps are separate applications that use GraphQL to talk to a Saleor server and that can receive webhooks with event notifications from Saleor.

Authentication

Unlike regular users, Saleor Apps use a bearer token. The token is assigned at App installation time and needs to be stored in a secure manner.

The authorization header for Apps has the following format:

Authorization: Bearer <app-token>

The App manifest

The App manifest is a JSON file that describes the application, including its name, version, required permissions, and how it handles the store's data.

Saleor expectes the manifest to use the following format:

{
"id": "example.app.wonderful",
"version": "1.0.0",
"name": "My Wonderful App",
"about": "My Wonderful App is a wonderful App for Saleor.",
"permissions": ["MANAGE_USERS", "MANAGE_STAFF"],
"appUrl": "http://localhost:3000/app",
"configurationUrl": "htpp://localhost:3000/configuration",
"tokenTargetUrl": "http://localhost:3000/register",
"dataPrivacy": "Lorem ipsum",
"dataPrivacyUrl": "http://localhost:3000/app-data-privacy",
"homepageUrl": "http://localhost:3000/homepage",
"supportUrl": "http://localhost:3000/support"
}
note

During an installation of third-party App, Saleor will try to send an authentication token to provided tokenTargetUrl. For more details, see installing an app.

Permissions

NameDescription
MANAGE_STAFFAccess to staff users data
MANAGE_APPSManage apps
MANAGE_USERSAccess to customers data
MANAGE_DISCOUNTSManage discounts
MANAGE_PLUGINSManage plugins
MANAGE_GIFT_CARDManage gift cards
MANAGE_MENUSManage the structure of menus
MANAGE_ORDERSAccess to orders data
MANAGE_PAGESManage pages
MANAGE_PRODUCTSManage products
MANAGE_SHIPPINGManage shipping
MANAGE_SETTINGSManage shop settings
MANAGE_TRANSLATIONSManage tranlsations
MANAGE_CHECKOUTSManage checkout

Webhooks

Webhooks can be used to receive data from Saleor when particular events happen. The available events are:

NameDescription
ANY_EVENTSReceive webhooks when any of the available events is triggered (wildcard event).
CHECKOUT_CREATEDA new checkout is created.
CHECKOUT_UPDATEDA checkout is updated; triggered for all updates related to an checkout;.
CUSTOMER_CREATEDA new customer account is created.
ORDER_CREATEDA new order is placed.
ORDER_FULLY_PAIDPayment is made and an order is fully paid.
ORDER_UPDATEDAn order is updated; triggered for all changes related to an order; covers all other order webhooks, except for ORDER_CREATED.
ORDER_CANCELLEDAn order is cancelled.
ORDER_FULFILLEDAn order is fulfilled.
FULFILLMENT_CREATEDA new fulfillment is created.
PRODUCT_CREATEDA new product is created.

Webhook protocols

Webhooks support below protocols:

  • HTTP(S)
  • Google Cloud Pub/Sub
  • AWS SQS
HTTP(S)

Webhook payloads are sent in POST requests.

All webhooks with targetUrl, where the scheme is HTTP(S) will use this protocol.

Saleor calculates the HMAC sha256 header - X-Saleor-Signature based on secretKey and the payload

Google Cloud Pub/Sub

All webhooks where scheme is gcpubsub will be treated as a Google Cloud Pub/Sub webhooks. The structure of targetUrl should be as below:

gcpubsub://cloud.google.com/projects/<yourproject>/topics/<yourtopic>

To proper use the Google Cloud Pub/Sub you need to set the GOOGLE_APPLICATION_CREDENTIALS environment variable

AWS SQS

All webhooks where scheme is awssqs will be treated as AWS SQS webhooks.

The structure of targetUrl should be as below:

awssqs://<access_key_id>:<secret_access_key>@sqs.<region>.amazonaws.com/<account_id>/<queue_name>

Type of Apps

  • local: create a custom App instance, assign explicitly provided permissions. The app is fully manageable from dashboard. You can change assigned permissions, add webhooks, or authentication token. local app can be useful for creating integration with some services which will parse the webhook payload.

    note

    If you're building a single service dedicated only to your shop this solution will handle all your needs.

  • third-party: install App in Saleor and proceed with all required steps to prepare the communication channel for App.
    Installation is fully automated. Saleor uses a defined App manifest to gather all required information.
    If there are no errors during the installation, App gets the possibility to attach to Saleor Dashboard over the iframe, communicate with Saleor over API and use webhooks to subscribe for the given events.

    note

    If you're building an App that will be published in the marketplace, then this solution is dedicated to you.

Creating and installing Apps from the command line

Saleor provides commands that can be used to create/install an App from the command line.

Creating local Apps

To create a local App with explicitly provided permissions use the create_app command.

Arguments:

  • <name>: the name of the App, required.
  • --permission <PERM>: assign permission PERM to the App. This argument can be repeated to specify multiple permissions.
  • --activate: immediately activate the new App.
  • --target-url <URL>: URL to send the API auth token to once installation is complete. [Optional]

Example: The following command will create an App named "Order App" that can access users and orders:

python manage.py create_app "Order App" \
--permission MANAGE_USERS \
--permission MANAGE_ORDERS \
--target_url https://your-order-app/your-register-endpoint/

If the target URL is provided, Saleor will POST the API auth token, in the same JSON format, to the provided URL:

If the target URL is not specified, the command will output an API auth token in the following format:

{"auth_token": "new-app-token"}

Installing third-party Apps

To install a third-party App based on its manifest URL use the install_app command.

Arguments:

  • <manifest URL>: URL of the manifest file, required.
  • --activate: immediately activate the new App.

Example: The following command will download given manifest and install the app:

python manage.py install_app https://www.url.to.manifest

App installation is confirmed by sending an API token to the specified token target URL. To see workflow details check - install app workflow.

The GraphQL API

Third-party Apps

Installing an App

Installing an App with appInstall mutation creates an App based on its manifest URL.

This mutation is only available to staff users with the MANAGE_APPS permission. Staff user can't grant app more permissions than he has for his own.

The mutation takes the following input fields:

  • appName: the name of the App, required.
  • manifestUrl: URL of the manifest file, required.
  • permissions: list of permissions the app should be granted.
  • activateAfterInstallation: immediately activate the new App. Default true.

The response of the mutation contains the appInstallation which contains all information about installation process.

mutation{
appInstall(
input: {
appName: "External Order App",
manifestUrl: "http://localhost:3000/manifest",
permissions:[MANAGE_ORDERS]
}
) {
appInstallation{
id
status
appName
manifestUrl
}
appErrors{
field
message
code
permissions
}
}
}

If there are no errors in the response, a new app installation process is created:

{
"data": {
"appInstall": {
"appInstallation": {
"id": "QXBwT25nb2luZ0luc3RhbGxhdGlvbjoz",
"status": "PENDING",
"appName": "Order App",
"manifestUrl": "http://localhost:3000/manifest"
},
"appErrors": []
}
}
}

The chart below explains workflow of the installation process:

Schedule installation
Install an App

Retrieving ongoing installations

To fetch all ongoing and failed installations, use the appsInstallations query. This query is only available to staff users with the MANAGE_APPS permission. The query returns list of AppInstallation objects.

{
appsInstallations{
id
status
appName
}
}

The query returns a similar response:

{
"data": {
"appsInstallations": [
{
"id": "QXBwT25nb2luZ0luc3RhbGxhdGlvbjoy",
"status": "FAILED",
"appName": "Shipping App"
},
{
"id": "QXBwT25nb2luZ0luc3RhbGxhdGlvbjoz",
"status": "PENDING",
"appName": "Order App"
}
]
}
}

Retrying failed installation

If the appsInstallations query returns App with failed status, there is a possibility to retry the installation process. This mutation is only available to staff users with the MANAGE_APPS permission. Staff user can't retry app installation if app requires more permissions than he has for his own. The appRetryInstall mutation will run the installation process for a given app.

The mutation takes the following fields:

  • id: the id of the AppInstallation, required.
  • activateAfterInstallation: immediately activate the new App. Default true.

Example of retrying installation of app with id QXBwT25nb2luZ0luc3RhbGxhdGlvbjoy:

mutation{
appRetryInstall(id:"QXBwT25nb2luZ0luc3RhbGxhdGlvbjoy"){
appErrors{
field
message
code
}
appInstallation{
id
status
}
}
}

Deleting failed installation

If the appsInstallations query returns App with failed status, there is a possibility to delete failed installation. This mutation is only available to staff users with the MANAGE_APPS permission. The appDeleteFailedInstallation mutation will delete failed installation.

The mutation takes the following fields:

  • id: the id of the AppInstallation, required.

Local Apps

Creating an App

To create a new app, use the appCreate mutation. This mutation is only available to staff users with the MANAGE_APPS permission. Staff user can't grant app more permissions than he has for his own. The mutation takes the following input fields:

  • name: the name of the app.
  • isActive: whether to activate the app, defaults to true.
  • permissions: list of permissions the app should be granted.

Let's assume that we want to create an order processing service that can fetch and manage orders in Saleor. To do so, we create a new app with the MANAGE_ORDERS permission:

mutation {
appCreate(
input: { name: "Order processing service", permissions: [MANAGE_ORDERS] }
) {
authToken
app {
id
name
}
appErrors {
field
code
}
}
}

If there are no errors in the response, a new app is created:

{
"data": {
"appCreate": {
"authToken": "Es1O0oB2O3PTeG9A4IVYUcaMoru3Ls",
"app": {
"id": "QXBwOjk=",
"name": "Order processing service"
},
"appErrors": []
}
}
}

The response contains the authToken field - this is the token that the app needs to include in requests to Saleor API to authorize itself and have access to queries or mutations that it needs.

note

The authToken field is only available in the mutation response and cannot be fetched later, except for the last four digits.

Creating authorization keys

Creating an app with appCreate mutation creates the default authorization token (authToken). You can create more authorization keys with the appTokenCreate. The mutation takes the following fields:

  • app: ID of the app.
  • name: (optional) name of the key.
mutation {
appTokenCreate(input: { app: "QXBwOjk=" }) {
authToken
appErrors {
field
code
}
appToken {
id
}
}
}

Revoking authorization keys

To revoke an authorization key, use the appTokenDelete:

mutation {
appTokenDelete(id: "QXBwOjk=") {
appErrors {
field
code
}
}

Retrieving all Apps

To fetch list of all apps, use apps query. This query is only available to staff users with the MANAGE_APPS permission.

{
apps(first: 5){
edges{
node{
id
permissions{
code
}
name
isActive
webhooks{
name
}
}
}
}
}

The query returns a similar response:

{
"data": {
"apps": {
"edges": [
{
"node": {
"id": "QXBwOjE=",
"permissions": [
{
"code": "MANAGE_ORDERS"
}
],
"name": "Order App",
"isActive": true,
"webhooks": []
}
}
]
}
}
}

Activating an App

To activate an app, use the appActivate mutation. This mutation is only available to staff users with the MANAGE_APPS permission. Staff user can't activate app which has more permissions than he has for his own.

The mutation takes the following fields:

  • id: the id of the App, required.
mutation {
appActivate(id:"QXBwOjE="){
app{
id
isActive
}
appErrors{
field
message
code
}
}
}

The query returns a similar response:

{
"data": {
"appActivate": {
"app": {
"id": "QXBwOjE=",
"isActive": true
},
"appErrors": []
}
}
}

Deactivating an App

To deactivate an app, use the appDeactivate mutation. This mutation is only available to staff users with the MANAGE_APPS permission. Staff user can't deactivate an app which has more permissions than they have for their own.

note

Deactivating an app will disable all auth-tokens generated for an app. The app won't receive a webhook payload.

The mutation takes the following fields:

  • id: the id of the App, required.
mutation {
appDeactivate(id:"QXBwOjE="){
app{
id
isActive
}
appErrors{
field
message
code
}
}
}

The query returns a similar response:

{
"data": {
"appDeactivate": {
"app": {
"id": "QXBwOjE=",
"isActive": false
},
"appErrors": []
}
}
}

Subscribing to a webhook

To subscribe to a webhook, we need to first create an app with proper permissions. Let's assume that we want to extend the order processing app that we've created in the example above. The app should receive notifications whenever new orders are created in Saleor. To do so, we'll create a new webhook using the webhookCreate mutation. The mutation takes the following input:

  • name: name of the webhook.
  • targetUrl: URL of a service that will receive webhooks requests.
  • events: list of the events to subscribe to.
  • app: ID of the app to which the webhook belongs.
  • isActive: whether to activate the webhook.
mutation {
webhookCreate(
input: {
name: "New orders notification"
targetUrl: "https://order-processing-service.example.com"
events: [ORDER_CREATED]
app: "QXBwOjk="
isActive: true
secretKey: "secret-key"
}
) {
webhook {
id
}
webhookErrors {
field
code
}
}
}

If there are no errors in the response, the webhook is successfully created. From now on, whenever a new order is placed, payload with order data will be sent to your targetUrl.

Updating a webhook

To update a webhook (e.g. to deactivate it or change the permissions), use the webhookUpdate mutation. The mutation takes similar input fields as the webhookCreate mutation. The example below shows how to deactive a webhook:

mutation {
webhookUpdate(id: "V2ViaG9vazox", input: { isActive: false }) {
webhook {
isActive
}
webhookErrors {
field
code
}
}
}

Removing a webhook

To fully remove a webhook, use the webhookDelete mutation:

mutation {
webhookDelete(id: "V2ViaG9vazox") {
webhookErrors {
field
code
}
}
}