Skip to main content
Version: 3.x

Channels

Introduction​

Since version 3.0, Saleor has supported multiple sales channels. This feature allows the shop owner to configure many aspects of the store depending on the channel used.

  • Your store needs at least one channel to display products
  • You can add as many channels as you need
  • Each channel has precisely one currency
  • Channels can be deactivated for customers

Popular use cases:

  • If you sell internationally, you can use separate channels for different currencies or regions
  • If you want to have different prices in the custom mobile app and website, you can set up website-channel and mobile-channel

There are many models you can customize per channel. The details of which fields you can define on a per-channel basis are described in the ChannelListing models:

Many Saleor Apps allow to configure them per-channel. We recommend to allow it for the best flexibility.

Permissions​

Listing channels is allowed only for users with the active isStaff flag.

You need the MANAGE_CHANNELS permission to create, edit, and remove channels. Changing ChannelListing does not require additional permissions. For example, changing Product availability requires only MANAGE_PRODUCTS permission.

Objects with customized per-channel visibility are restricted by channel permissions. If a user with restricted channel access fetches per-channel objects, only objects from accessible channels will be returned. You can read more about channel permissions here.

Getting channel details​

Use channel query to get channel details.

Channel query requires channel ID, but from Saleor 3.6 it can be executed with channel slug too.

Available fields:

  • id: Channel object ID
  • slug: When using channel-dependant queries, the slug is used to select the right one (for example, when requesting Product details)
  • name: Channel name
  • isActive: Flag signalling that the channel is available for shop customers.
  • currencyCode: Currency code used by the channel.
  • hasOrders: Returns true when there are already created orders using that channel. If that's the only channel using this currency, you won't be able to remove it.
  • defaultCountry: Default country for the channel. The default country will be used in checkout to determine the stock quantities or calculate taxes when the country was not explicitly provided, either as a query parameter or through a shipping address.
  • warehouses: Warehouses that are available in the given channel.
  • countries: List of shippable countries for the channel.
  • availableShippingMethodsPerCountry: Shipping methods that are available for the channel.
  • stockSettings: contains all stock-related settings, e.g. the allocation strategy for the channel.
  • orderSettings: Channel-specific order settings.

Request:

query {
channel(slug: "mobile") {
id
slug
name
isActive
currencyCode
hasOrders
defaultCountry {
code
country
}
warehouses {
slug
}
countries {
code
country
}
availableShippingMethodsPerCountry {
countryCode
shippingMethods {
id
name
}
}
stockSettings {
allocationStrategy
}
orderSettings {
allowUnpaidOrders
}
}
}
Expand â–¼

Response:

{
"data": {
"channel": {
"id": "Q2hhbm5lbDo0MzM=",
"slug": "mobile",
"name": "Mobile",
"isActive": false,
"currencyCode": "USD",
"hasOrders": true,
"defaultCountry": {
"code": "US",
"country": "United States of America"
},
"warehouses": [
{
"slug": "oceania"
},
{
"slug": "europe"
}
],
"availableShippingMethodsPerCountry": [
{
"countryCode": "AD",
"shippingMethods": [
{
"id": "U2hpcHBpbmdNZXRob2Q6Mg==",
"name": "DHL"
},
{
"id": "U2hpcHBpbmdNZXRob2Q6Mw==",
"name": "UPS"
},
{
"id": "U2hpcHBpbmdNZXRob2Q6NA==",
"name": "Registered priority"
},
{
"id": "U2hpcHBpbmdNZXRob2Q6NQ==",
"name": "DB Schenker"
}
]
}
// ... more countries
],
"stockSettings": {
"allocationStrategy": "PRIORITIZE_HIGH_STOCK"
},
"orderSettings": {
"allowUnpaidOrders": false
}
}
}
}
Expand â–¼

Creating a new channel​

You can create a new channel using channelCreate mutation. During checkout creation, you can define the allocation strategy. Right now the two possible options are available: PRIORITIZE_HIGH_STOCK and PRIORITIZE_SORTING_ORDER, the PRIORITIZE_SORTING_ORDER strategy is used as default. You can read more about the allocation strategies here.

Request:

mutation {
channelCreate(
input: {
currencyCode: "USD"
defaultCountry: US
name: "Mobile"
slug: "mobile"
stockSettings: { allocationStrategy: PRIORITIZE_HIGH_STOCK }
}
) {
channel {
id
isActive
name
slug
currencyCode
defaultCountry {
code
country
}
stockSettings {
allocationStrategy
}
}
errors {
code
field
message
}
}
}
Expand â–¼

Response:

{
"data": {
"channelCreate": {
"channel": {
"id": "Q2hhbm5lbDo0MzM=",
"isActive": false,
"name": "Mobile",
"slug": "mobile",
"currencyCode": "USD",
"defaultCountry": {
"code": "US",
"country": "United States of America"
},
"stockSettings": {
"allocationStrategy": "PRIORITIZE_HIGH_STOCK"
}
},
"errors": []
}
}
}
Expand â–¼

Channel list​

Because some of the channels may be considered non-public (for example - a channel for business partners), non-staff users cannot use the channels query.

Request:

query {
channels {
name
}
}

Response:

{
"data": {
"channels": [
{
"name": "Mobile"
},
{
"name": "Website"
}
]
}
}

Activate / Deactivate channel​

If you want to make the channel unavailable for customers, you can change it's status to deactivated using channelDeactivate mutation:

mutation {
channelDeactivate(slug: "default-channel") {
channel {
name
isActive
}
errors {
message
}
}
}

Response:

{
"data": {
"channelDeactivate": {
"channel": {
"name": "Facebook",
"isActive": false
},
"channelErrors": []
}
}
}

And to reverse the previous operation use the channelActivate mutation:

mutation {
channelActivate(slug: "default-channel") {
channel {
name
isActive
}
errors {
message
}
}
}

Response:

{
"data": {
"channelDeactivate": {
"channel": {
"name": "Facebook",
"isActive": true
},
"channelErrors": []
}
}
}

Reorder warehouses within channels​

The warehouses assigned to the channel can be sorted. The provided order defines the warehouses' order used in PRIORITIZE_SORTING_ORDER allocation strategy. The sort_order in moves input represents the new relative position of the item. So when 1 is provided, the item will be moved one position forward; when -1 - one position backward.

Let's assume that we have a channel with three channels in the following order.

{
"data": {
"channel": {
"id": "Q2hhbm5lbDox",
"warehouses": [
{
"id": "V2FyZWhvdXNlOjU1NTZiOWI0LTc1ZTItNGI3YS1hZWM1LTQxOTY4NDA2OGE4OA==",
"slug": "europe"
},
{
"id": "V2FyZWhvdXNlOjQwZWY1MTQwLWQ5OTYtNDVlNy04NzUzLTlkZThkMTdhMjg1Yw==",
"slug": "oceania"
},
{
"id": "V2FyZWhvdXNlOjY4M2FkMzZhLTRmNjktNDI2ZS1iYzUyLTMyZGJiZTQ2NjUyZA==",
"slug": "americas"
}
]
}
}
}
Expand â–¼

To move the americas warehouse to the first place and the europe warehouse to third, we can run the following mutation.

mutation {
channelReorderWarehouses(
channelId: "Q2hhbm5lbDox"
moves: [
{
# move for `americas` warehouse
id: "V2FyZWhvdXNlOjY4M2FkMzZhLTRmNjktNDI2ZS1iYzUyLTMyZGJiZTQ2NjUyZA=="
sortOrder: -2
}
{
# move for `europe` warehouse
id: "V2FyZWhvdXNlOjU1NTZiOWI0LTc1ZTItNGI3YS1hZWM1LTQxOTY4NDA2OGE4OA=="
sortOrder: 1
}
]
) {
channel {
id
warehouses {
id
slug
}
}
errors {
field
code
message
warehouses
}
}
}
Expand â–¼

And as a response, we get:

{
"data": {
"channelReorderWarehouses": {
"channel": {
"id": "Q2hhbm5lbDox",
"warehouses": [
{
"id": "V2FyZWhvdXNlOjY4M2FkMzZhLTRmNjktNDI2ZS1iYzUyLTMyZGJiZTQ2NjUyZA==",
"slug": "americas"
},
{
"id": "V2FyZWhvdXNlOjQwZWY1MTQwLWQ5OTYtNDVlNy04NzUzLTlkZThkMTdhMjg1Yw==",
"slug": "oceania"
},
{
"id": "V2FyZWhvdXNlOjU1NTZiOWI0LTc1ZTItNGI3YS1hZWM1LTQxOTY4NDA2OGE4OA==",
"slug": "europe"
}
]
},
"errors": []
}
}
}
Expand â–¼

Removing a channel​

Channels can be removed only when:

  • There are no orders created in them.
  • If there are orders created, targetChannel is required. Its currency has to be the same as the channel you are about to delete. All orders will be moved to targetChannel.

channelDelete mutation takes input:

  • id: ID of the Channel that will be deleted
  • channelId: all existing orders will be moved into this channel
channelDelete(
id: ID!
input: ChannelDeleteInput
): ChannelDelete

input ChannelDeleteInput {
channelId: ID! # ID of the channel to migrate orders from origin channel.
}

Errors​

type ChannelError {
field: String
message: String
code: ChannelErrorCode!
}

enum ChannelErrorCode {
ALREADY_EXISTS
GRAPHQL_ERROR
INVALID
NOT_FOUND
REQUIRED
UNIQUE
CHANNEL_TARGET_ID_MUST_BE_DIFFERENT
CHANNELS_CURRENCY_MUST_BE_THE_SAME
}

ChannelErrorCode values:

  • ALREADY_EXISTS: Object already exists in the database
  • GRAPHQL_ERROR: Wrong query
  • INVALID: Invalid data provided
  • NOT_FOUND: Could not found object
  • REQUIRED: Missing required fields
  • UNIQUE: Provided value for field needs to be unique
  • CHANNEL_TARGET_ID_MUST_BE_DIFFERENT: Cannot move orders into the channel you want to delete
  • CHANNELS_CURRENCY_MUST_BE_THE_SAME: Target channel has to have the same currency

Was this page helpful?