npm package: @tailor-platform/shopify
Description: Shopify Admin API client with Tailor Platform integration
Latest version: 0.6.0

# @tailor-platform/shopify

A drop-in replacement for [`@shopify/admin-api-client`](https://www.npmjs.com/package/@shopify/admin-api-client) that uses Tailor Platform integration tokens instead of Shopify offline tokens.

## Installation

```bash
npm install @tailor-platform/shopify
# or
yarn add @tailor-platform/shopify
# or
pnpm add @tailor-platform/shopify
```

## Usage

### Basic Usage

```typescript
import { createAdminApiClient, ApiVersion } from '@tailor-platform/shopify';

const client = createAdminApiClient({
  storeDomain: 'your-shop.myshopify.com',
  apiVersion: ApiVersion.October25, // or ApiVersion.Unstable
  accessToken: 'tst_YOUR_INTEGRATION_TOKEN', // Integration token from Tailor Platform
});

// Make GraphQL requests
const { data, errors } = await client.request(
  `
  query getProduct($id: ID!) {
    product(id: $id) {
      title
      handle
      description
    }
  }
`,
  {
    variables: {
      id: 'gid://shopify/Product/123456789',
    },
  }
);
```

### Type-Safe Usage

The SDK provides full TypeScript support with automatic type inference:

```typescript
import {
  createAdminApiClient,
  ApiVersion,
  gql,
} from '@tailor-platform/shopify';

// Define your types
interface ProductData {
  product: {
    id: string;
    title: string;
    handle: string;
    description: string;
  };
}

interface ProductVariables {
  id: string;
}

const client = createAdminApiClient({
  storeDomain: 'your-shop.myshopify.com',
  apiVersion: ApiVersion.October25,
  accessToken: 'tst_YOUR_INTEGRATION_TOKEN',
});

// Type-safe query with gql template tag
const productQuery = gql<ProductData, ProductVariables>`
  query getProduct($id: ID!) {
    product(id: $id) {
      id
      title
      handle
      description
    }
  }
`;

// TypeScript automatically infers types from the gql tag
const { data, errors } = await client.request(productQuery, {
  variables: {
    id: 'gid://shopify/Product/123456789',
  },
});

// data is fully typed as ProductData
if (data?.product) {
  console.log(data.product.title); // TypeScript knows this is a string
}

// Alternative: Use types explicitly without gql
const { data } = await client.request<ProductData, ProductVariables>(
  `query getProduct($id: ID!) { ... }`,
  { variables: { id: 'gid://shopify/Product/123456789' } }
);
```

### Working with Mutations

```typescript
interface CreateProductData {
  productCreate: {
    product: {
      id: string;
      title: string;
    };
    userErrors: Array<{
      field: string[];
      message: string;
    }>;
  };
}

interface CreateProductVariables {
  input: {
    title: string;
    descriptionHtml?: string;
    vendor?: string;
  };
}

const createProductMutation = gql<CreateProductData, CreateProductVariables>`
  mutation createProduct($input: ProductInput!) {
    productCreate(input: $input) {
      product {
        id
        title
      }
      userErrors {
        field
        message
      }
    }
  }
`;

// Type inference works automatically - no need to specify types again
const { data } = await client.request(createProductMutation, {
  variables: {
    input: {
      title: 'New Product',
      descriptionHtml: '<p>Description</p>',
    },
  },
});

// Handle user errors
if (data?.productCreate.userErrors.length > 0) {
  console.error('Failed to create product:', data.productCreate.userErrors);
}
```

## API Versions

The SDK exports constants for all available Shopify API versions:

```typescript
import { ApiVersion, LATEST_API_VERSION } from '@tailor-platform/shopify';

// Use a specific version
const client = createAdminApiClient({
  storeDomain: 'your-shop.myshopify.com',
  apiVersion: ApiVersion.January25,
  accessToken: 'tst_YOUR_TOKEN',
});

// Use the latest stable version
const client2 = createAdminApiClient({
  storeDomain: 'your-shop.myshopify.com',
  apiVersion: LATEST_API_VERSION, // Currently October25
  accessToken: 'tst_YOUR_TOKEN',
});

// Use unstable version for testing new features
const client3 = createAdminApiClient({
  storeDomain: 'your-shop.myshopify.com',
  apiVersion: ApiVersion.Unstable,
  accessToken: 'tst_YOUR_TOKEN',
});
```

## Type Safety Features

### Typed GraphQL Documents

The `gql` template tag creates typed documents that carry type information:

```typescript
const query = gql<ResponseType, VariablesType>`...`;
// Types flow automatically to client.request()
```

### Benefits

- **Auto-completion**: Full IntelliSense support for response data
- **Type checking**: Catch errors at compile time, not runtime
- **Type inference**: No need to specify types twice
- **Better DX**: See available fields and their types while coding

## API Compatibility

This SDK provides the same interface as `@shopify/admin-api-client`, making it a drop-in replacement. Simply replace your import statement and use your Tailor Platform integration token instead of a Shopify offline token.

## Token Permissions

Integration tokens support two permission levels for enhanced security:

### Full Access Tokens

- Can execute both GraphQL queries and mutations
- Full read and write access to Shopify Admin API
- **Use for**: Inventory sync, order processing, product management, fulfillment operations

### Read-Only Tokens

- Can only execute GraphQL queries
- Cannot perform mutations or modify data
- Attempts to execute mutations will result in a `403 Forbidden` error
- **Use for**: Analytics dashboards, reporting tools, read-only integrations, data exports

### Best Practices

1. **Principle of Least Privilege**: Always use read-only tokens when write access isn't needed
2. **Security**: Read-only tokens significantly reduce risk if compromised
3. **Compliance**: Easier to audit access when permissions are explicit
4. **Integration Type**: Match token permissions to your integration's actual requirements

### Example: Read-Only Analytics Token

```typescript
import { createAdminApiClient, ApiVersion } from '@tailor-platform/shopify';

// Create a client with a read-only token
const analyticsClient = createAdminApiClient({
  storeDomain: 'your-shop.myshopify.com',
  apiVersion: ApiVersion.October25,
  accessToken: 'tst_YOUR_READONLY_TOKEN', // Read-only token
});

// ✅ This works - queries are allowed
const { data } = await analyticsClient.request(`
  query {
    orders(first: 10) {
      edges {
        node {
          id
          totalPriceSet {
            shopMoney {
              amount
            }
          }
        }
      }
    }
  }
`);

// ❌ This fails - mutations are blocked
try {
  await analyticsClient.request(`
    mutation {
      orderUpdate(input: { id: "gid://shopify/Order/123" }) {
        order { id }
      }
    }
  `);
} catch (error) {
  // Error: "This token has read-only access and cannot perform mutations"
  console.error(error);
}
```

### Error Handling for Read-Only Tokens

```typescript
import {
  createAdminApiClient,
  ShopifyApiError,
  NetworkError,
} from '@tailor-platform/shopify';

const client = createAdminApiClient({
  storeDomain: 'your-shop.myshopify.com',
  apiVersion: ApiVersion.October25,
  accessToken: 'tst_YOUR_READONLY_TOKEN',
});

try {
  const { data } = await client.request(someMutation);
} catch (error) {
  if (error instanceof NetworkError && error.statusCode === 403) {
    console.error('Permission denied - token may be read-only');
    // Handle gracefully: show user message, switch to different token, etc.
  } else if (error instanceof ShopifyApiError) {
    console.error('Shopify API error:', error.errors);
  }
}
```

### Creating Tokens with Permissions

When creating tokens through the Tailor Platform dashboard:

1. Navigate to the **Tokens** page
2. Click **Create new token**
3. Enter a descriptive name
4. Select permission level:
   - **Full access**: For integrations that need to modify data
   - **Read-only**: For analytics, reporting, or monitoring tools
5. Set an expiration date (recommended for security)
6. Copy and securely store the token (it won't be shown again)

## Response Types

All GraphQL responses follow this structure:

```typescript
interface GraphQLResponse<TData> {
  data?: TData;
  errors?: GraphQLError[];
  extensions?: {
    cost?: {
      requestedQueryCost: number;
      actualQueryCost: number;
      throttleStatus: {
        maximumAvailable: number;
        currentlyAvailable: number;
        restoreRate: number;
      };
    };
    [key: string]: unknown;
  };
}

interface GraphQLError {
  message: string;
  extensions?: Record<string, unknown>;
  path?: (string | number)[];
  locations?: Array<{
    line: number;
    column: number;
  }>;
}
```

## Error Handling

The SDK provides custom error types for better error handling:

```typescript
import {
  ShopifyApiError,
  NetworkError,
  ConfigurationError,
} from '@tailor-platform/shopify';

try {
  const { data } = await client.request(query);
} catch (error) {
  if (error instanceof ShopifyApiError) {
    // Handle Shopify API errors
    console.error('Shopify API error:', error.errors);
  } else if (error instanceof NetworkError) {
    // Handle network errors
    console.error('Network error:', error.statusCode);
  } else if (error instanceof ConfigurationError) {
    // Handle configuration errors
    console.error('Configuration error:', error.message);
  }
}
```

## Webhook Management

The SDK provides webhook support with Tailor Platform's managed webhook system, offering automatic registration, reliable delivery, and retry logic.

### Creating Webhooks

```typescript
const CREATE_WEBHOOK = gql`
  mutation webhookSubscriptionCreate(
    $topic: WebhookSubscriptionTopic!
    $webhookSubscription: WebhookSubscriptionInput!
  ) {
    webhookSubscriptionCreate(
      topic: $topic
      webhookSubscription: $webhookSubscription
    ) {
      webhookSubscription {
        id
        topic
        callbackUrl
      }
      userErrors {
        field
        message
      }
    }
  }
`;

const { data } = await client.request(CREATE_WEBHOOK, {
  variables: {
    topic: 'ORDERS_CREATE',
    webhookSubscription: {
      callbackUrl: 'https://your-app.com/webhooks/orders',
    },
  },
});
```

### Webhook Topics

The SDK exports webhook topic utilities:

```typescript
import {
  getWebhookEventName,
  getWebhookTopicEnum,
} from '@tailor-platform/shopify';

// Convert between GraphQL enum and event names
getWebhookEventName('ORDERS_CREATE'); // 'orders/create'
getWebhookTopicEnum('orders/create'); // 'ORDERS_CREATE'
```

### Other Webhook Operations

```typescript
// List webhooks
const LIST_WEBHOOKS = gql`
  query webhookSubscriptions($first: Int!) {
    webhookSubscriptions(first: $first) {
      edges {
        node {
          id
          topic
          callbackUrl
        }
      }
    }
  }
`;

// Update webhook
const UPDATE_WEBHOOK = gql`
  mutation webhookSubscriptionUpdate(
    $id: ID!
    $webhookSubscription: WebhookSubscriptionInput!
  ) {
    webhookSubscriptionUpdate(
      id: $id
      webhookSubscription: $webhookSubscription
    ) {
      webhookSubscription {
        id
        callbackUrl
      }
      userErrors {
        field
        message
      }
    }
  }
`;

// Delete webhook
const DELETE_WEBHOOK = gql`
  mutation webhookSubscriptionDelete($id: ID!) {
    webhookSubscriptionDelete(id: $id) {
      deletedWebhookSubscriptionId
      userErrors {
        field
        message
      }
    }
  }
`;
```

### Webhook Reliability

Tailor Platform automatically:

- Registers webhooks with Shopify
- Retries failed deliveries with exponential backoff
- Pauses webhooks after 10 consecutive failures
- Provides monitoring through the dashboard

### Common Webhook Topics

- **Orders**: `ORDERS_CREATE`, `ORDERS_UPDATED`, `ORDERS_PAID`, `ORDERS_FULFILLED`
- **Products**: `PRODUCTS_CREATE`, `PRODUCTS_UPDATE`, `PRODUCTS_DELETE`
- **Customers**: `CUSTOMERS_CREATE`, `CUSTOMERS_UPDATE`, `CUSTOMERS_DELETE`
- **Inventory**: `INVENTORY_ITEMS_CREATE`, `INVENTORY_LEVELS_UPDATE`

For a complete list, see the [Shopify documentation](https://shopify.dev/docs/api/admin-graphql/unstable/enums/WebhookSubscriptionTopic).