Skip to content
Back to skills

Shopify Admin Product Affinity Cross Sell

ASecurity

Mine order history to find which products are most frequently bought together, then rank pairs by support, confidence, and lift to power bundles and cross-sell recommendations.

  • 181 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 28, 2026
businessnodeapi

Works with

  • claude code
  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned May 28, 2026

npx -y skills add 40RTY-ai/shopify-admin-skills --skill shopify-admin-product-affinity-cross-sell --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Shopify Admin Product Affinity Cross Sell?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Shopify Admin Product Affinity Cross Sell
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/40rty-ai-shopify-admin-product-affinity-cross-sell/badge)](https://www.skillsdirectory.com/skills/40rty-ai-shopify-admin-product-affinity-cross-sell)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: shopify-admin-product-affinity-cross-sell
role: order-intelligence
description: "Mine order history to find which products are most frequently bought together, then rank pairs by support, confidence, and lift to power bundles and cross-sell recommendations."
toolkit: shopify-admin, shopify-admin-execution
api_version: "2025-01"
graphql_operations:
  - orders:query
status: stable
compatibility: Claude Code, Cursor, Codex, Gemini CLI
---

## Purpose
Applies market basket analysis to your order history to surface product pairs that customers naturally buy together. For every co-purchased pair it calculates **support** (how often the pair appears), **confidence** (given product A, how likely is B?), and **lift** (how much more likely than chance). The output is actionable input for product bundles, "frequently bought together" widgets, cross-sell email flows, and homepage recommendations. Read-only — no mutations are executed.

## Prerequisites
- Authenticated Shopify CLI session: `shopify auth login --store <domain>`
- API scopes: `read_orders`, `read_products` (validator-confirmed: line item `product` field traverses the product graph)

## Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| store | string | yes | — | Store domain (e.g., mystore.myshopify.com) |
| format | string | no | human | Output format: `human` or `json` |
| dry_run | bool | no | false | Preview operations without executing mutations |
| date_range_start | string | yes | — | Start date in ISO 8601 (e.g., `2025-01-01`) |
| date_range_end | string | yes | — | End date in ISO 8601 (e.g., `2025-03-31`) |
| min_support | integer | no | 5 | Minimum number of orders a pair must co-appear in to be included |
| min_confidence | float | no | 0.1 | Minimum P(B\|A) threshold (0–1) |
| min_lift | float | no | 1.0 | Only include pairs where lift > this value (> 1 means non-random) |
| top_n | integer | no | 20 | Number of top pairs to show in the ranked output |
| sort_by | string | no | lift | Ranking metric: `lift`, `confidence`, or `support` |
| exclude_tags | string | no | — | Comma-separated product tags to exclude (e.g., `gift-wrap,donation`) |

## Workflow Steps

1. **OPERATION:** `orders` — query
   **Inputs:** `first: 250`, `query: "created_at:>='<date_range_start>' created_at:<='<date_range_end>'"`, pagination cursor; select `lineItems` with `product { id, title }` and `quantity`; skip orders with a single line item
   **Expected output:** All multi-item orders in range; paginate until `hasNextPage: false`; build a product frequency map (`product_id → order_count`) and a pair frequency map (`(product_a_id, product_b_id) → co_occurrence_count`)

2. **In-memory analysis:**
   - For each order with ≥ 2 distinct products, enumerate every unique unordered pair and increment the pair counter
   - Compute metrics for each pair that meets `min_support`:
     - **Support** = `pair_count / total_orders`
     - **Confidence A→B** = `pair_count / count(orders containing A)`
     - **Confidence B→A** = `pair_count / count(orders containing B)`
     - **Lift** = `support / (P(A) × P(B))`
   - Filter by `min_confidence` and `min_lift`; sort by `sort_by`; truncate to `top_n`

## GraphQL Operations

```graphql
# orders:query (multi-item basket analysis) — validated against api_version 2025-01
query OrdersForAffinityAnalysis($first: Int!, $after: String, $query: String) {
  orders(first: $first, after: $after, query: $query) {
    edges {
      node {
        id
        createdAt
        lineItems(first: 50) {
          edges {
            node {
              quantity
              product {
                id
                title
                tags
              }
            }
          }
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

## Session Tracking

**Claude MUST emit the following output at each stage. This is mandatory.**

**On start**, emit:
```
╔══════════════════════════════════════════════╗
║  SKILL: product-affinity-cross-sell          ║
║  Store: <store domain>                       ║
║  Started: <YYYY-MM-DD HH:MM UTC>             ║
╚══════════════════════════════════════════════╝
```

**After each step**, emit:
```
[N/TOTAL] <QUERY|MUTATION>  <OperationName>
          → Params: <brief summary of key inputs>
          → Result: <count or outcome>
```

**On completion**, emit:

For `format: human` (default):
```
══════════════════════════════════════════════
OUTCOME SUMMARY
  Orders analysed:      <n>
  Unique products:      <n>
  Pairs evaluated:      <n>
  Pairs above threshold:<n>
  Date range:           <start> to <end>
  Sort by:              <lift|confidence|support>
  Errors:               0
  Output:               product_affinity_<date>.csv
══════════════════════════════════════════════
```

Followed by an inline ranked table of the top `top_n` pairs:

| Rank | Product A | Product B | Support | Conf A→B | Conf B→A | Lift |
|------|-----------|-----------|---------|----------|----------|------|
| 1 | ... | ... | ... | ...% | ...% | ... |

For `format: json`, emit:
```json
{
  "skill": "product-affinity-cross-sell",
  "store": "<domain>",
  "started_at": "<ISO8601>",
  "completed_at": "<ISO8601>",
  "dry_run": false,
  "steps": [
    { "step": 1, "operation": "OrdersForAffinityAnalysis", "type": "query", "params_summary": "<date_range_start> to <date_range_end>", "result_summary": "<n> orders, <n> multi-item baskets", "skipped": false }
  ],
  "outcome": {
    "orders_analysed": 0,
    "unique_products": 0,
    "pairs_evaluated": 0,
    "pairs_above_threshold": 0,
    "date_range_start": "<date_range_start>",
    "date_range_end": "<date_range_end>",
    "sort_by": "lift",
    "results": [],
    "errors": 0,
    "output_file": "product_affinity_<date>.csv"
  }
}
```

## Output Format
CSV file `product_affinity_<YYYY-MM-DD>.csv` with one row per qualifying pair:

| Column | Description |
|--------|-------------|
| `rank` | Position in sorted output |
| `product_a_id` | Shopify product GID for the first item |
| `product_a_title` | Product A name |
| `product_b_id` | Shopify product GID for the second item |
| `product_b_title` | Product B name |
| `co_occurrence_count` | Number of orders containing both products |
| `support` | `co_occurrence_count / total_orders` |
| `confidence_a_to_b` | P(B\|A) — likelihood of B given A is in cart |
| `confidence_b_to_a` | P(A\|B) — likelihood of A given B is in cart |
| `lift` | How much more likely than random co-occurrence |
| `recommendation_type` | `bundle_candidate` if lift > 2, else `cross_sell` |

## Error Handling
| Error | Cause | Recovery |
|-------|-------|----------|
| `THROTTLED` | API rate limit from paginating large order history | Wait 2 s, retry up to 3 times; narrow date range if persistent |
| `product` is null on line item | Product was deleted after purchase | Skip that item from pair analysis; count the order in the total |
| Zero pairs above threshold | Store has few multi-item orders or thresholds too strict | Lower `min_support` to 2 and `min_confidence` to 0.05, or widen date range |
| Combinatorial explosion | Stores with very large line item counts per order | The pair-enumeration loop skips orders with > 20 distinct products to cap O(n²) growth |

## Best Practices
1. Use at least 90 days of order history — short windows produce noisy lift scores because the probability denominators are small.
2. **Lift > 2** is a strong bundle signal: customers are buying these products together at least twice as often as chance would predict.
3. **Confidence A→B > 30%** makes for a reliable "frequently bought with" widget: three in ten shoppers who buy A also buy B.
4. Filter out accessories and add-ons (like gift wrap or donation SKUs) with `exclude_tags` before ranking — they inflate support scores without being meaningful cross-sell pairs.
5. Use the `recommendation_type` column to split your output: `bundle_candidate` pairs are best for pre-built bundles or volume discounts; `cross_sell` pairs are better suited to cart upsells and post-purchase email recommendations.
6. Re-run quarterly — seasonal products enter and exit the top pairs list, and ignoring that produces stale recommendations.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.