/shopify-admin-customer-merge
Merges duplicate customer records: invokes Shopify's native customer merge API where supported, otherwise consolidates the loser record's tags and notes into the winner via customerUpdate.
$ npx -y skills add 40rty-ai/shopify-admin-skills --skill shopify-admin-customer-merge --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/shopify-admin-customer-merge
Context preview
The summary Claude sees to decide when to auto-load this skill.
Merges duplicate customer records: invokes Shopify's native customer merge API where supported, otherwise consolidates the loser record's tags and notes into the winner via customerUpdate.
SKILL.md
shopify-admin-customer-merge.SKILL.mdname: shopify-admin-customer-merge
role: customer-support
description: "Merges duplicate customer records: invokes Shopify's native customer merge API where supported, otherwise consolidates the loser record's tags and notes into the winner via customerUpdate."
toolkit: shopify-admin, shopify-admin-execution
api_version: "2025-01"
graphql_operations:
- customer:query
- customerMerge:mutation
- customerUpdate:mutation
status: stable
compatibility: Claude Code, Cursor, Codex, Gemini CLI
Purpose
Resolves duplicate customer records identified by `duplicate-customer-finder`. Where the Shopify Admin API exposes `customerMerge` (a native merge that moves orders, addresses, subscriptions, and metafields onto a winner record), this skill calls it directly. When `customerMerge` is unavailable or fails for the given account pair, the skill falls back to consolidating searchable metadata — tags, notes, marketing consent — onto the winner via `customerUpdate`, then writes a clear annotation to the loser record so staff can complete the merge manually in Shopify Admin.
Prerequisites
- Authenticated Shopify CLI session: `shopify store auth --store <domain> --scopes read_customers,write_customers`
- API scopes: `read_customers`, `write_customers`
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 | true | Preview merge plan without executing mutations | | customer_winner_id | string | yes | — | GID of the customer record to keep (e.g., `gid://shopify/Customer/12345`) | | customer_loser_id | string | yes | — | GID of the customer record to merge into the winner | | use_native_merge | bool | no | true | Try `customerMerge` first; if it fails or is unavailable, fall back to consolidation via `customerUpdate` | | merge_tags | bool | no | true | Union the loser's tags onto the winner | | merge_note | bool | no | true | Append the loser's note to the winner (with timestamp prefix) | | annotate_loser | bool | no | true | Write a note on the loser record pointing to the winner GID for manual cleanup |
Safety
> ⚠️ Steps 2–4 execute mutations that modify customer records. `customerMerge` is irreversible — once orders and addresses are moved to the winner, the loser record is closed and cannot be split back. Run with `dry_run: true` first to confirm winner/loser GIDs and the merge plan. The default is `dry_run: true`. Always verify both records belong to the same human (matching email, phone, name) using `duplicate-customer-finder` output before committing. Do not merge a customer with active subscriptions or unfulfilled orders without confirming downstream systems will follow the new owner GID.
Workflow Steps
1. **OPERATION:** `customer` — query (called twice: winner and loser) **Inputs:** `id: <customer_id>`, select `id`, `displayName`, `firstName`, `lastName`, `defaultEmailAddress { emailAddress }`, `phone`, `tags`, `note`, `numberOfOrders`, `amountSpent`, `emailMarketingConsent { marketingState }`, `smsMarketingConsent { marketingState }`, `addresses(first: 25) { id }`, `createdAt` **Expected output:** Both records' full identity payload — abort if either GID does not resolve
2. **OPERATION:** `customerMerge` — mutation (only if `use_native_merge: true` and not `dry_run`) **Inputs:** `customerOneId: <customer_winner_id>`, `customerTwoId: <customer_loser_id>`, `overrideFields`: prefer winner's name/email/phone/locale/marketing-consent **Expected output:** `job.id` (merge runs asynchronously), `userErrors`. If `userErrors` indicates merge is not supported for this pair (B2B, gift card holder, subscriber, etc.), proceed to step 3 fallback.
3. **OPERATION:** `customerUpdate` — mutation (winner) — fallback path or when `use_native_merge: false` **Inputs:** `input.id: <customer_winner_id>`, `input.tags: <union of winner.tags and loser.tags>` (only if `merge_tags`), `input.note: <winner.note + "\n[YYYY-MM-DD] Merged from <loser_email>:\n" + loser.note>` (only if `merge_note`) **Expected output:** `customer.id`, `customer.tags`, `customer.note`, `userErrors`
4. **OPERATION:** `customerUpdate` — mutation (loser) — only if `annotate_loser: true` **Inputs:** `input.id: <customer_loser_id>`, `input.note: "<existing note>\n[YYYY-MM-DD] DUPLICATE — merge target: <customer_winner_id>. Manually close in Shopify Admin once orders are reviewed."`, `input.tags: <existing + ["duplicate", "merged-loser"]>` **Expected output:** `customer.id`, `customer.tags`, `customer.note`, `userErrors`
GraphQL Operations
# customer:query — validated against api_version 2025-01
query CustomerForMerge($id: ID!) {
customer(id: $id) {
id
displayName
firstName
lastName
defaultEmailAddress { emailAddress }
phone
tags
note
numberOfOrders
amountSpent { amount currencyCode }
emailMarketingConsent { marketingState marketingOptInLevel consentUpdatedAt }
smsMarketingConsent { marketingState marketingOptInLevel consentUpdatedAt }
addresses(first: 25) { id address1 city provinceCode countryCodeV2 zip }
createdAt
}
}# customerMerge:mutation — validated against api_version 2025-01
mutation CustomerMerge(
$customerOneId: ID!
$customerTwoId: ID!
$overrideFields: CustomerMergeOverrideFields
) {
customerMerge(
customerOneId: $customerOneId
customerTwoId: $customerTwoId
overrideFields: $overrideFields
) {
job { id done }
resultingCustomerId
userErrors { field message code }
}
}# customerUpdate:mutation — validated against api_version 2025-01
mutation CustomerConsolidate($input: CustomerInput!) {
customerUpdate(input: $input) {
customer { id displayName tags note }
userErrors { field message }
}
}Session Trac
Read more
name: shopify-admin-customer-merge role: customer-support description: "Merges duplicate customer records: invokes Shopify's native customer merge API where supported, otherwise consolidates the loser record's tags and notes into the winner via customerUpdate." toolkit: shopify-admin, shopify-admin-execution api_version: "2025-01" graphql_operations: - customer:query - customerMerge:mutation - customerUpdate:mutation status: stable compatibility: Claude Code, Cursor, Codex, Gemini CLI
Purpose
Resolves duplicate customer records identified by `duplicate-customer-finder`. Where the Shopify Admin API exposes `customerMerge` (a native merge that moves orders, addresses, subscriptions, and metafields onto a winner record), this skill calls it directly. When `customerMerge` is unavailable or fails for the given account pair, the skill falls back to consolidating searchable metadata — tags, notes, marketing consent — onto the winner via `customerUpdate`, then writes a clear annotation to the loser record so staff can complete the merge manually in Shopify Admin.
Prerequisites
- Authenticated Shopify CLI session: `shopify store auth --store <domain> --scopes read_customers,write_customers`
- API scopes: `read_customers`, `write_customers`
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 | true | Preview merge plan without executing mutations | | customer_winner_id | string | yes | — | GID of the customer record to keep (e.g., `gid://shopify/Customer/12345`) | | customer_loser_id | string | yes | — | GID of the customer record to merge into the winner | | use_native_merge | bool | no | true | Try `customerMerge` first; if it fails or is unavailable, fall back to consolidation via `customerUpdate` | | merge_tags | bool | no | true | Union the loser's tags onto the winner | | merge_note | bool | no | true | Append the loser's note to the winner (with timestamp prefix) | | annotate_loser | bool | no | true | Write a note on the loser record pointing to the winner GID for manual cleanup |
Safety
> ⚠️ Steps 2–4 execute mutations that modify customer records. `customerMerge` is irreversible — once orders and addresses are moved to the winner, the loser record is closed and cannot be split back. Run with `dry_run: true` first to confirm winner/loser GIDs and the merge plan. The default is `dry_run: true`. Always verify both records belong to the same human (matching email, phone, name) using `duplicate-customer-finder` output before committing. Do not merge a customer with active subscriptions or unfulfilled orders without confirming downstream systems will follow the new owner GID.
Workflow Steps
1. **OPERATION:** `customer` — query (called twice: winner and loser) **Inputs:** `id: <customer_id>`, select `id`, `displayName`, `firstName`, `lastName`, `defaultEmailAddress { emailAddress }`, `phone`, `tags`, `note`, `numberOfOrders`, `amountSpent`, `emailMarketingConsent { marketingState }`, `smsMarketingConsent { marketingState }`, `addresses(first: 25) { id }`, `createdAt` **Expected output:** Both records' full identity payload — abort if either GID does not resolve
2. **OPERATION:** `customerMerge` — mutation (only if `use_native_merge: true` and not `dry_run`) **Inputs:** `customerOneId: <customer_winner_id>`, `customerTwoId: <customer_loser_id>`, `overrideFields`: prefer winner's name/email/phone/locale/marketing-consent **Expected output:** `job.id` (merge runs asynchronously), `userErrors`. If `userErrors` indicates merge is not supported for this pair (B2B, gift card holder, subscriber, etc.), proceed to step 3 fallback.
3. **OPERATION:** `customerUpdate` — mutation (winner) — fallback path or when `use_native_merge: false` **Inputs:** `input.id: <customer_winner_id>`, `input.tags: <union of winner.tags and loser.tags>` (only if `merge_tags`), `input.note: <winner.note + "\n[YYYY-MM-DD] Merged from <loser_email>:\n" + loser.note>` (only if `merge_note`) **Expected output:** `customer.id`, `customer.tags`, `customer.note`, `userErrors`
4. **OPERATION:** `customerUpdate` — mutation (loser) — only if `annotate_loser: true` **Inputs:** `input.id: <customer_loser_id>`, `input.note: "<existing note>\n[YYYY-MM-DD] DUPLICATE — merge target: <customer_winner_id>. Manually close in Shopify Admin once orders are reviewed."`, `input.tags: <existing + ["duplicate", "merged-loser"]>` **Expected output:** `customer.id`, `customer.tags`, `customer.note`, `userErrors`
GraphQL Operations
# customer:query — validated against api_version 2025-01
query CustomerForMerge($id: ID!) {
customer(id: $id) {
id
displayName
firstName
lastName
defaultEmailAddress { emailAddress }
phone
tags
note
numberOfOrders
amountSpent { amount currencyCode }
emailMarketingConsent { marketingState marketingOptInLevel consentUpdatedAt }
smsMarketingConsent { marketingState marketingOptInLevel consentUpdatedAt }
addresses(first: 25) { id address1 city provinceCode countryCodeV2 zip }
createdAt
}
}# customerMerge:mutation — validated against api_version 2025-01
mutation CustomerMerge(
$customerOneId: ID!
$customerTwoId: ID!
$overrideFields: CustomerMergeOverrideFields
) {
customerMerge(
customerOneId: $customerOneId
customerTwoId: $customerTwoId
overrideFields: $overrideFields
) {
job { id done }
resultingCustomerId
userErrors { field message code }
}
}# customerUpdate:mutation — validated against api_version 2025-01
mutation CustomerConsolidate($input: CustomerInput!) {
customerUpdate(input: $input) {
customer { id displayName tags note }
userErrors { field message }
}
}Session Trac
Community-maintained AI agent skills for operating Shopify stores — workflows, optimization, reports and more
Other skills on shopify-admin-skills.
- /shopify-admin-agentic-crawler-access
Edit the theme's robots.txt.liquid to explicitly allow AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, OAI-SearchBot, Amazonbot) so AI assistants are permitted to read the catalog.
Open skill - /shopify-admin-agentic-description-enrichment
Rewrite thin product descriptions into structured, fact-rich copy (materials, fit, use-cases, the words shoppers actually type) so AI agents have something concrete to quote and match.
Open skill - /shopify-admin-agentic-image-alt-text
Generate and set descriptive alt text on product images so AI agents (which can't 'see' pixels) can understand and recommend what each product looks like.
Open skill - /shopify-admin-agentic-llms-txt
Generate and publish an /llms.txt guide (brand summary, flagship products, key policies, contact) via a theme template so AI assistants get a curated, machine-readable map of the store.
Open skill - /shopify-admin-agentic-metafields-setup
Define and populate agentic-commerce metafields (material, attributes, key features, specs, sizing) so AI agents can filter and match products to specific shopper requirements.
Open skill - /shopify-admin-agentic-organization-schema
Inject an Organization JSON-LD block (name, logo, sameAs social links, contactPoint) into the theme so AI agents can verify the store is a real, trusted brand and link it to its public identity.
Open skill

