> For the complete documentation index, see [llms.txt](https://docs.violet.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.violet.io/ecom-platforms/shopify/sales-channel/setup.md).

# Setting Up Your App

Build the Shopify sales channel app that powers publication-scoped catalog sync: app creation, the channel\_config extension, scopes, deploy/release, and Violet setup.

This page is for channel partners building the Shopify app that will act as their sales channel. It assumes the concepts on [Sales Channel Publishing on Shopify](/ecom-platforms/shopify/sales-channel.md).

This guide walks you through creating a Shopify app that installs on a single store and registers as a native **sales channel**, so a merchant can publish products to your channel and only those products sync to Violet. You (the developer/partner) own the Shopify app; Violet integrates with it.

This document ends where [Enablement & Rollout](/ecom-platforms/shopify/sales-channel/enablement.md) begins: once your app is built, deployed, and released, that page covers configuring Violet and onboarding merchants.

## Overview

Publication-scoped catalog sync requires your Shopify app to be a genuine sales channel app, one that owns a channel on the merchant's store. That is what produces the checkbox merchants tick on each product. An ordinary app without a channel configuration cannot do this; Shopify rejects channel creation for it.

A sales channel is declared by deploying a `channel_config` extension. That's an app-level property, not something that requires a public App Store listing.

{% hint style="danger" %}
**Deploying the `channel_config` extension is irreversible.** It permanently declares the app a sales channel. Always build and test against a Shopify **development store** first, and confirm you are working against the intended app before you deploy.
{% endhint %}

Building it is a sequence of steps:

1. Install the Shopify CLI.
2. Create the app in the Dev Dashboard and link it to a local project.
3. Add a `channel_config` extension and define your channel specification.
4. Set access scopes.
5. Deploy **and** release the app version.
6. Select custom distribution and install on the merchant's store.
7. Configure the corresponding Violet app to match.

## Before you start

**Prerequisites**

* A Shopify Partner account and access to the **Dev Dashboard** ([dev.shopify.com/dashboard](https://dev.shopify.com/dashboard)). This is the same Partner account described in the general [Prerequisites](/ecom-platforms/shopify/prerequisites.md).
* **Node.js 22.12 or higher**, a Node package manager (npm, Yarn 1.x, or pnpm), and **Git 2.28 or higher**. These are prerequisites for the Shopify CLI.
* The **Shopify CLI** installed (see Step 1).
* The **`myshopify.com` domain** of the store this channel will run on.

{% hint style="info" %}
**Things to know going in**

* Declaring the app a sales channel (deploying the extension) is **irreversible**. Test on a development store first.
* Custom distribution **cannot use Shopify's Billing API**. That's fine for a channel you operate yourself.
  {% endhint %}

## Step 1: Install the Shopify CLI

First confirm your Node.js version meets the requirement:

```bash
node -v    # must be 22.12 or higher
```

If Node is missing or too old, install the latest LTS from [nodejs.org](https://nodejs.org/) (or via a version manager like nvm) before continuing.

Install the CLI globally using whichever package manager you use:

```bash
# npm
npm install -g @shopify/cli@latest

# or yarn
yarn global add @shopify/cli@latest

# or pnpm
pnpm install -g @shopify/cli@latest
```

On macOS you can instead use Homebrew, which also pulls in a compatible Node.js:

```bash
brew tap shopify/shopify
brew install shopify-cli
```

Verify the install:

```bash
shopify version
```

{% hint style="info" %}
Shopify CLI 4.0+ auto-upgrades itself after commands (skipped in CI and for local installs). To upgrade manually at any time, run `shopify upgrade`. If you're behind a corporate proxy, set `SHOPIFY_HTTP_PROXY` (and optionally `SHOPIFY_HTTPS_PROXY`) before running CLI commands.
{% endhint %}

## Step 2: Create the app in the Dev Dashboard

1. Go to [dev.shopify.com/dashboard](https://dev.shopify.com/dashboard) and create a new app.
2. Give it a name.
3. Open the app and copy its **Client ID**; you'll need it in the next step.

## Step 3: Link the app to your local project

From an empty project directory in your terminal:

```bash
shopify app config link --client-id <YOUR_CLIENT_ID>
```

This logs you in and writes a `shopify.app.toml` into the folder, connecting the directory to your Dev Dashboard app.

## Step 4: Add a package.json

The CLI needs a `package.json` at the project root to treat the folder as an app and hold extensions. Create one:

```json
{
  "name": "your-channel-sales-channel",
  "version": "1.0.0",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "dev": "shopify app dev",
    "deploy": "shopify app deploy",
    "generate": "shopify app generate"
  },
  "devDependencies": {
    "@shopify/cli": "latest"
  }
}
```

Then install:

```bash
npm install
```

## Step 5: Generate the channel config extension

```bash
shopify app generate extension --template channel_config --name channel-config
```

This creates:

```
extensions/channel-config/
├── shopify.extension.toml
└── specifications/
    ├── example-us.toml      ← sample, delete
    ├── example-de.toml      ← sample, delete
    ├── example-au.json      ← sample, delete
    └── icons/
        └── example-channel-icon.svg
```

## Step 6: Define your channel specification

Delete the three `example-*` spec files (leave the `icons/` folder). Then create one specification file describing your channel, named for your channel (e.g. `extensions/channel-config/specifications/your-channel.toml`). Use your own brand name for `handle` and `label`. The `label` is what merchants see in the Shopify admin, and the `handle` is the specification handle Violet uses when it creates the channel on the merchant's store.

```toml
handle = "your-channel"
label = "Your Channel"
icon = "icons/example-channel-icon.svg"   # replace with your own 20x20 SVG
productFeedManagement = "automatic"

# Capabilities default to false. Turn on only what the channel actually supports.
[capabilities]
bundles = true
digitalProducts = true
subscriptions = false
combinedListings = false
unlistedProducts = true
scheduledPublishing = false

[requirements]
# false = marketplace-style, channel can carry its own catalog/pricing.
# true  = referral-style (like Google/Meta) mirroring the online store;
#         this also requires the store to have a web-presence market covering the countries.
expectsOnlineStoreParity = false
# "channel"  = your external surface processes checkout/payment.
# "merchant" = the merchant's own Shopify checkout processes it.
merchantOfRecord = "merchant"

[[countries]]
code = "US"
languages = ["en"]
currency = "USD"

[[countries]]
code = "GB"
languages = ["en"]
currency = "GBP"
```

Field notes:

* **`handle`** is the specification handle. Note the exact spelling: Violet must send the same value when it creates the channel, and the two must match exactly.
* **`merchantOfRecord`**: set `"channel"` if you are the merchant of record, `"merchant"` if the Shopify store is.
* **`expectsOnlineStoreParity`**: keep `false` for marketplace-style channels. If you set `true`, channel creation will fail unless the store has a market with a valid web presence covering your countries.
* **`countries`**: one entry per country, with ISO country/language/currency codes. Each country generates its own product feed.

Also update the extension's own metadata in `extensions/channel-config/shopify.extension.toml`:

```toml
[[extensions]]
type = "channel_config"
name = "Your Sales Channel"
description = "Your sales channel configuration"
handle = "channel-config"
create_legacy_channel_on_app_install = false
```

### How the specification handle is matched

Violet sends a specification handle when it creates the channel on a merchant's store, and it must match the `handle` in your deployed spec exactly.

* If you set `specification_handle` on the Violet app configuration, that value is sent verbatim.
* If you leave it unset, Violet derives it from the Violet app's name by lowercasing it and replacing runs of whitespace with hyphens. An app named `Violet Marketplace` produces `violet-marketplace`.

Set `specification_handle` explicitly whenever your deployed handle doesn't follow that convention. A mismatch is the most common setup failure and surfaces as a `Channel specification not found` error during provisioning, which is non-fatal: affected merchants silently stay on full-catalog syncing.

## Step 7: Set access scopes

Open `shopify.app.toml` and make sure the `scopes` line includes these, at minimum:

```toml
[access_scopes]
scopes = "read_channels,write_channels,read_customers,write_customers,read_discounts,read_draft_orders,write_draft_orders,read_fulfillments,read_inventory,read_legal_policies,read_locales,read_locations,read_markets,write_merchant_managed_fulfillment_orders,read_metaobject_definitions,read_metaobjects,read_orders,write_orders,read_price_rules,read_products,read_product_listings,read_publications,write_publications,read_returns,read_shipping,read_translations,unauthenticated_read_product_inventory,unauthenticated_read_product_listings,unauthenticated_write_checkouts"
```

Beyond the product and order scopes a standard integration already uses (see the [26 scopes in the general App Setup guide](/ecom-platforms/shopify/shopify-app-setup.md#required-api-scopes)), a sales channel app needs the channel- and publication-specific scopes below.

| Scope                             | Why                                                                                                         | Required?                                                                                                        |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `read_channels`, `write_channels` | Create and manage the channel on the merchant's store                                                       | **Required** for a sales channel app                                                                             |
| `read_publications`               | Identifies the publication attached to your channel, the list of products the merchant has published to you | **Required.** Without it, provisioning cannot complete and every merchant silently stays on full-catalog syncing |
| `write_publications`              | Sets the channel's auto-publish behavior for products created after setup                                   | Only if auto-publish is being configured                                                                         |
| `read_product_listings`           | Reads the published product listings for the channel                                                        | **Required** for a sales channel app                                                                             |

{% hint style="danger" %}
**`read_publications` is not included in the generalized product scopes.** An app requesting only generalized scopes will not receive it. It must be declared explicitly, and it is the easiest prerequisite to miss.
{% endhint %}

{% hint style="warning" %}
Changing scopes means an existing app will be prompted to re-grant permissions the next time it's installed or opened. Select everything you need up front.
{% endhint %}

## Step 8: Deploy *and* release the app version

```bash
shopify app deploy
```

This bundles the config and the extension into a new **app version**. When prompted, **accept the release**.

{% hint style="danger" %}
The extension must be both **deployed** and **released**. A deployed-but-unreleased version behaves exactly like no extension at all: channel creation fails, and merchants continue on full-catalog syncing with no error surfaced to them. If you deploy without releasing (answering "no", or running non-interactively without `--force`), the store keeps running the previous version and your new specification won't exist as far as the API is concerned.
{% endhint %}

To release explicitly if needed:

```bash
shopify app release --version=<version-name>
```

Verify in the Dev Dashboard → your app → **Versions** that the **active/released** version's `channel-config` extension lists your real spec handle (e.g. `your-channel`), not the `example-*` handles.

## Step 9: Set custom distribution and install

1. In the Partner Dashboard → **App distribution**, select the app → **Choose distribution** → **Custom distribution**. This choice is permanent.
2. Enter the store's `myshopify.com` domain. To lock it to one store, uncheck "Allow multi-store installs for one Plus organization."
3. Click **Generate link**.
4. Pre-register the merchant in the Violet [Channel Dashboard](https://channel.violet.io) and complete installation.

This is the same custom-distribution and pre-registration flow used for every Shopify merchant. See [Shopify App Setup](/ecom-platforms/shopify/shopify-app-setup.md) and [Pre-Registration Process](/ecom-platforms/shopify/pre-registration.md) for the detailed walkthrough and the Install Link.

After installing, the app appears under the store's **Settings → Apps and sales channels** and in the **Sales channels** list.

## Step 10: The channel connection (handled by Violet)

Being listed as a sales channel is not enough for products to be publishable to it. A channel only appears in a product's **Manage publishing** dialog after a **channel connection** exists, which creates the publication and product feeds.

{% hint style="info" %}
**You don't create the channel connection yourself.** Violet performs it during merchant provisioning. When the merchant connects (or reconnects) through Violet, Violet calls Shopify's `channelCreate` mutation against the store using your specification handle, then identifies the resulting publication and turns on publication-scoped syncing. See [Enablement & Rollout](/ecom-platforms/shopify/sales-channel/enablement.md#what-happens-when-a-merchant-connects).
{% endhint %}

For reference, this is the Shopify mutation Violet issues against the store's GraphQL Admin API. The `specificationHandle` must exactly match the `handle` in your deployed, released spec:

```graphql
mutation ChannelCreate($input: ChannelCreateInput!) {
  channelCreate(input: $input) {
    channel { id handle specificationHandle }
    userErrors { field message code }
  }
}
```

On success, the channel has a publication, appears in the product Manage-publishing dialog, and product feeds begin syncing. From that point the merchant can immediately choose which products are published to your channel.

## Configure the Violet app

Set the following on your Violet app configuration so Violet provisions the channel for connecting merchants.

| Setting                                       | Purpose                                                                                                                                        |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `sales_channel_behavior.enabled`              | Turns on channel provisioning for merchants connecting to this app                                                                             |
| `sales_channel_behavior.specification_handle` | The handle from your deployed extension. Optional; see [Step 6](#how-the-specification-handle-is-matched) for the derivation rule when omitted |
| `sales_channel_behavior.channel_name`         | Display name merchants see in their Shopify admin. Optional; defaults to the Violet app name                                                   |

## Verify the setup

On a Shopify development store:

1. Connect the store to your channel.
2. Confirm the channel appears in the store's admin under **Sales channels**, named as you expect.
3. Open any product and confirm your channel appears in the **Sales channels and apps** section.
4. Publish a product and confirm it reaches your Violet catalog.
5. Unpublish it and confirm it is withdrawn.

If step 2 fails, work through [The channel doesn't appear in the merchant's Shopify admin](/ecom-platforms/shopify/sales-channel/troubleshooting.md) in the troubleshooting guide.

## Converting an existing (legacy) app

For legacy merchants you can link their existing app and convert it to a sales channel app:

```bash
shopify app config link --client-id <YOUR_CLIENT_ID>
```

then follow the extension, scope, and deploy/release steps above. Converted merchants will need to be pre-registered again and re-install the app to accept the new scopes and inherit the new configuration. This is the same re-authorization requirement described in [Enablement & Rollout](/ecom-platforms/shopify/sales-channel/enablement.md#rollout-to-existing-merchants).

## Troubleshooting the build

**`shopify app generate extension` → "doesn't have a package.json".** `config link` didn't scaffold a project. Add the `package.json` from [Step 4](#step-4-add-a-packagejson), run `npm install`, and retry.

For merchant-facing and provisioning issues, see [Troubleshooting & FAQ](/ecom-platforms/shopify/sales-channel/troubleshooting.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.violet.io/ecom-platforms/shopify/sales-channel/setup.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
