# Shopify Custom App

Connect a Shopify custom app to call Shopify APIs from workflows. Covers dev stores and live stores with custom distribution.

You can connect a Shopify custom app to workflows, not just the store where JsWorkflows is installed. This lets a workflow call another Shopify store, use a separate Shopify app token, or access Shopify API surfaces that are outside the standard JsWorkflows app connection.

For the store where JsWorkflows is installed, you usually do not need this. Use the built-in Shopify Admin API access instead — JsWorkflows automatically injects the installed store's access token when your workflow calls that store's Admin API URL.

Use a separate Shopify connection when you need one of these patterns:

- **Multi-store workflows**: read from one Shopify store and update another
- **Wholesale or regional stores**: sync products, inventory, customer tags, or order data between related stores
- **Migration and backfill jobs**: pull data from a source store and write cleaned data into another store
- **Separate Shopify scopes**: use scopes that are not part of the main JsWorkflows app installation
- **Agency or developer workflows**: manage controlled access to a client's secondary store

## 1. Create the Shopify app

1. Go to [dev.shopify.com/dashboard](https://dev.shopify.com/dashboard/) and log in
2. Click **Create app**, then **Start from Dev Dashboard**
3. Enter an app name and click **Create**. This opens the **Create version** screen

## 2. Configure the app version

On the **Create version** screen:

1. **App URL**: Enter any URL, or leave the default `https://example.com`
2. **Scopes**: Click **Select scopes** and choose the Admin API scopes your workflow needs
3. **Redirect URLs**: Add the following URL exactly as shown:

   ```text
   https://oauth.jsworkflows.com/oauth2/callback
   ```

4. Click **Release** to publish the version

## 3. Get the Client Secret

In the left sidebar, click **Settings**. Under **Credentials** you will find the **Client ID** and **Secret**. Keep the Secret — you will need it when adding the connection.

---

## Connecting a dev store

If the target store is a development store, you can use the standard **Custom (OAuth 2.0)** connector.

Go to **Settings → OAuth2 Tokens**, click **Connect to Service**, and choose **Custom (OAuth 2.0)**.

Fill in the fields:

| Field | Value |
| --- | --- |
| **Client ID** | From Dev Dashboard → Settings → Credentials |
| **Client Secret** | From Dev Dashboard → Settings → Credentials |
| **Authorization URL** | `https://your-store.myshopify.com/admin/oauth/authorize` |
| **Token URL** | `https://your-store.myshopify.com/admin/oauth/access_token` |
| **Scopes** | Comma-separated list of your configured scopes. Copy from **Versions → [latest version] → Scopes** in the Dev Dashboard |
| **OAuth Name** | A friendly label (e.g. `Dev Store`) |
| **Handle** | A short identifier used in workflow code (e.g. `dev-store`) |

Click **Generate and Authorize**. A popup opens Shopify's authorization screen. Approve the request — the token is stored and the connection appears in your list.

---

## Connecting a live store

Live stores require the app to use **Custom distribution**. This adds an extra step before authorizing.

### Set the distribution method

From the app overview page in the Dev Dashboard:

1. Click **Select distribution method**
2. Select **Custom** distribution
3. Enter the live store's domain. Shopify accepts either format:

   ```text
   shop1.myshopify.com
   admin.shopify.com/store/shop1
   ```

Shopify generates an installation link for the store. Copy that link — you need it in the next steps.

### Install the app on the target store

Open the installation link in a browser tab. You will need to be logged in to the target Shopify store. Shopify will install the app on that store.

This step happens **outside JsWorkflows** and only needs to be done once per store. After the app is installed, you can connect it.

### Add the connection in JsWorkflows

Go to **Settings → OAuth2 Tokens**, click **Connect to Service**, and choose **Shopify Custom App**.

Fill in the fields:

| Field | Value |
| --- | --- |
| **Installation Link** | The full installation link from the Dev Dashboard distribution settings |
| **Client Secret** | From Dev Dashboard → Settings → Credentials |
| **Scopes** | Comma-separated list of your configured scopes. Copy from **Versions → [latest version] → Scopes** in the Dev Dashboard |
| **OAuth Name** | A friendly label (e.g. `Secondary Store`) |
| **Handle** | A short identifier used in workflow code (e.g. `secondary-store`) |

The **Client ID**, **Authorization URL**, and **Token URL** are extracted and derived automatically from the installation link. You do not need to enter them manually.

Click **Generate and Authorize**. A popup opens Shopify's authorization screen for the target store. Approve the request — the token is stored and the connection appears in your list.

---

## Use it in a workflow

```js
export class Workflow {
  async start(_data, _headers, api) {
    const { token, error } = await api.getOAuthToken('secondary-store');
    if (error || !token) throw new Error(error || 'Missing Shopify token');

    const res = await fetch(
      'https://your-store.myshopify.com/admin/api/2026-04/graphql.json',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Shopify-Access-Token': token,
        },
        body: JSON.stringify({
          query: `{
            products(first: 5) {
              edges {
                node {
                  id
                  title
                  status
                  totalInventory
                }
              }
            }
          }`,
        }),
      }
    );

    if (!res.ok) throw new Error(`Shopify API ${res.status}: ${await res.text()}`);

    const { data } = await res.json();
    const products = data.products.edges.map(e => e.node);
    console.log(`Fetched ${products.length} products`);
  }
}
```

Replace `your-store` with the store's myshopify.com subdomain and `secondary-store` with the handle you chose.

## Common scopes

Select the scopes you need from the **Select scopes** UI in the Dev Dashboard when creating your app version.

| Scope | What it allows |
| --- | --- |
| `read_products` | Read products and variants |
| `write_products` | Create and update products and variants |
| `read_orders` | Read orders (last 60 days) |
| `read_all_orders` | Read all orders regardless of age |
| `write_orders` | Update orders |
| `read_inventory` | Read inventory levels and locations |
| `write_inventory` | Update inventory levels |
| `read_customers` | Read customer data |
| `write_customers` | Create and update customers |
| `read_themes` | Read theme files |
| `write_themes` | Create and update theme files |

For a full list, see the [Shopify API access scopes reference](https://shopify.dev/docs/api/usage/access-scopes).