mcpField
S
shopify-admin-graphql

shopify-admin-graphql

github.com/tamiror6Development38106 installsUpdated 38w ago
Repository

About shopify-admin-graphql

shopify-admin-graphql is a development skill for code that uses the Shopify Admin API through GraphQL. It covers authenticated access, common queries, pagination, throttling, and error handling for app and background-job workflows.

Key features

  1. 01Access the API through Remix authentication
  2. 02Use online and offline admin sessions
  3. 03Query shops, products, customers, and orders
  4. 04Paginate large datasets
  5. 05Handle throttling and GraphQL errors
  6. 06Apply GraphQL query best practices

Use cases

  1. 01Build Shopify data queries in Remix loaders
  2. 02Update Shopify records through GraphQL mutations
  3. 03Process webhooks without request context
  4. 04Retrieve large lists with pagination and retries

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/tamiror6/shopify-app-skills --skill shopify-admin-graphql

After installing, type/shopify-admin-graphqlin Claude Code to confirm it loaded.

Details

From the project README

Shopify Admin GraphQL

Use this skill when adding or changing code that talks to the Shopify Admin API via GraphQL.

When to Use

  • Querying Shopify data (shop, customers, orders, products, inventory)
  • Mutating Shopify data (creating/updating customers, orders, products)
  • Implementing pagination for large datasets
  • Handling API throttling and rate limits
  • Working with metafields or other Shopify resources

Getting the GraphQL Client

In Remix Loaders/Actions (Recommended)

Use authenticate.admin() from @shopify/shopify-app-remix:

import { authenticate } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { admin } = await authenticate.admin(request);
  
  const response = await admin.graphql(`
    query GetShopDetails {
      shop {
        id
        name
        myshopifyDomain
        plan {
          displayName
        }
      }
    }
  `);
  
  const { data } = await response.json();
  return json({ shop: data.shop });
};
With Variables
export const action = async ({ request }: ActionFunctionArgs) => {
  const { admin } = await authenticate.admin(request);
  const formData = await request.formData();
  const email = formData.get("email") as string;
  
  // Validate email format to prevent query manipulation
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!email || !emailRegex.test(email)) {
    return json({ error: "Invalid email format" }, { status: 400 });
  }
  
  // Escape quotes and wrap in quotes to treat as literal value
  const sanitizedEmail = email.replace(/"/g, '\\"');
  
  const response = await admin.graphql(`
    query FindCustomerByEmail($query: String!) {
      customers(first: 1, query: $query) {
        edges {
          node {
            id
            email
            phone
            firstName
            lastName
          }
        }
      }
    }
  `, {
    variables: { query: `email:"${sanitizedEmail}"` }
  });
  
  const { data } = await response.json();
  return json({ customer: data.customers.edges[0]?.node });
};
Background Jobs / Webhooks (Offline Access)

When you don't have a request context, use offline session tokens:

import { unauthenticated } from "../shopify.server";

export async function processWebhook(shop: string) {
  const { admin } = await unauthenticated.admin(shop);
  
  const response = await admin.graphql(`
    query GetShop {
      shop {
        name
      }
    }
  `);
  
  const { data } = await response.json();
  return data.shop;
}

Common Query Patterns

Shop Details
query GetShopDetails {
  shop {
    id
    name
    email
    myshopifyDomain
    primaryDomain {
      url
    }
    plan {
      displayName
    }
    currencyCode
    timezoneAbbreviation
  }
}
Products with Pagination
query GetProducts($first: Int!, $after: String) {
  products(first: $first, after: $after) {
    pageInfo {
      hasNextPage
      endCursor
    }
    edges {
      node {
        id
        title
        handle
        status
        variants(first: 10) {
          edges {
            node {
              id
              title
              price
              sku
            }
          }
        }
      }
    }
  }
}
Customer Lookup
query FindCustomer($query: String!) {
  customers(first: 1, query: $query) {
    edges {
      node {
        id
        email
        phone
        firstName
        lastName
        ordersCount
        totalSpent
      }
    }
  }
}
Order by ID
query GetOrder($id: ID!) {
  order(id: $id) {
    id
    name
    email
    phone
    totalPriceSet {
      shopMoney {
        amount
        currencyCode
      }
    }
    lineItems(first: 50) {
      edges {
        node {
          title
          quantity
          variant {
            id
            sku
          }
        }
      }
    }
    shippingAddress {
      address1
      city
      country
    }
  }
}

Handling Throttling

Shopify uses a leaky bucket algorithm for rate limiting. For bulk operations or background jobs, implement retry logic:

interface RetryOptions {
  maxRetries?: number;
  initialDelay?: number;
  maxDelay?: number;
}

async function executeWithRetry<T>(
  admin: AdminApiContext,
  query: string,
  variables?: Record<string, unknown>,
  options: RetryOptions = {}
): Promise<T> {
  const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000 } = options;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await admin.graphql(query, { variables });
      const result = await response.json();
      
      if (result.errors?.some((e: any) => e.extensions?.code === "THROTTLED")) {
        throw new Error("THROTTLED");
      }
      
      return result.data as T;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw new Error("Max retries exceeded");
}

Error Handling

const response = await admin.graphql(query, { variables });
const { data, errors } = await response.json();

if (errors) {
  // Handle GraphQL errors
  console.error("GraphQL errors:", errors);
  
  // Check for specific error types
  const throttled = errors.some((e: any) => 
    e.extensions?.code === "THROTTLED"
  );
  
  const notFound = errors.some((e: any) => 
    e.message?.includes("not found")
  );
  
  if (throttled) {
    // Retry with backoff
  }
}

Best Practices

  1. Use operation names for debugging: query GetShopDetails { ... }
  2. Request only needed fields to reduce response size and improve performance
  3. Use pagination for lists - never request unbounded data
  4. Handle errors gracefully - check for both errors array and HTTP errors
  5. Implement retries with exponential backoff for background jobs
  6. Use fragments for repeated field selections across queries
  7. Prefer GraphQL over REST for complex queries with relationships

References

Comments

Markdown supported