Shopify Admin API for plugins

Plugins talk to your store with sp.shopify.graphql(query, variables), which is Shopify's GraphQL Admin API (version 2026-10). Anything that API offers can be built, within the permissions you grant. This page lists the main areas, the permission (scope) each needs and a ready-to-use example. Shopify's full reference: shopify.dev/docs/api/admin-graphql.

Add the scopes your plugin needs to plugin.scopes. Products and files are always available.

Products and variants

Scopes: always available (read_products, write_products).

const r = sp.shopify.graphql(`query($q: String!) {
  products(first: 50, query: $q) { nodes { id title vendor productType tags status
    variants(first: 20) { nodes { id sku price inventoryQuantity } } } } }`, { q: "status:active vendor:Trixie" });

Change things with productUpdate, productVariantsBulkUpdate, tagsAdd and tagsRemove. SEO fields are productUpdate(product: { id, seo: { title, description } }). Search syntax: status:active, vendor:X, product_type:X, tag:X, created_at:>2026-01-01, inventory_total:<5.

Collections

Scopes: products. Read collections(first:), create with collectionCreate, add products with collectionAddProducts. Smart collections use ruleSet.

Images and files

Scopes: always available (read_files, write_files). A product's images are under product.media(first:) { nodes { ... on MediaImage { id alt image { url width height } } } }. Change alt text with fileUpdate(files: [{ id, alt }]).

Inventory and locations

Scopes: read_inventory, write_inventory, read_locations.

const r = sp.shopify.graphql(`{ locations(first: 10) { nodes { id name } } }`);
sp.shopify.graphql(`mutation($input: InventorySetQuantitiesInput!) { inventorySetQuantities(input: $input) { userErrors { message } } }`,
  { input: { name: "available", reason: "correction", ignoreCompareQuantity: true, quantities: [{ inventoryItemId: "gid://shopify/InventoryItem/1", locationId: r.locations.nodes[0].id, quantity: 10 }] } });

Orders, draft orders, fulfilment, returns

Scopes: read_orders / write_orders, read_draft_orders / write_draft_orders, read_merchant_managed_fulfillment_orders, read_returns. Order data includes customer details, which Shopify protects: full access needs the app's data-protection review. Without it, protected fields come back empty or the query is refused.

const r = sp.shopify.graphql(`{ orders(first: 20, query: "financial_status:paid created_at:>-7d") {
  nodes { id name totalPriceSet { shopMoney { amount currencyCode } } tags } } }`);

Events: orders/create, orders/paid, orders/fulfilled, orders/cancelled, orders/updated, refunds/create, draft_orders/create, fulfillments/create. The webhook payload is in sp.event.data, and its admin_graphql_api_id is the GraphQL ID.

Customers

Scopes: read_customers / write_customers (protected data, as above). Tag customers with tagsAdd on the customer ID. Events: customers/create and customers/update.

Discounts and price rules

Scopes: read_discounts / write_discounts. Create code or automatic discounts with discountCodeBasicCreate and discountAutomaticBasicCreate. For rule-based discounts that Shopify's built-in types can't do, use ShopPress's rule discount function (discountAutomaticAppCreate with functionHandle: "shoppress-rule-discount"); see Plugins → Discounts.

sp.shopify.graphql(`mutation($d: DiscountCodeBasicInput!) { discountCodeBasicCreate(basicCodeDiscount: $d) { userErrors { message } } }`,
  { d: { title: "WELCOME10", code: "WELCOME10", startsAt: sp.utils.date.now(), customerSelection: { all: true },
         customerGets: { value: { percentage: 0.1 }, items: { all: true } } } });

Content: pages, blogs, navigation

Scopes: read_content / write_content, read_online_store_navigation / write_online_store_navigation. Pages: pageCreate / pageUpdate. Blogs and articles: articleCreate. Menus: menuUpdate.

Metafields and metaobjects

Custom data on any resource. Metafields on products use the product scopes; metaobjects use read_metaobjects / write_metaobjects (definitions: *_metaobject_definitions). Write with metafieldsSet(metafields: [{ ownerId, namespace, key, type, value }]). Metaobjects are ideal for plugin data that your theme shows: lookbooks, size charts, FAQs.

Markets, translations, languages

Scopes: read_markets / write_markets, read_translations / write_translations, read_locales / write_locales. Translate content with translationsRegister, list markets with markets(first:).

Sales channels (publications)

Scopes: read_publications / write_publications. Publish or unpublish products on channels with publishablePublish and publishableUnpublish.

Shipping

Scopes: read_shipping / write_shipping. Delivery profiles and rates: deliveryProfiles, deliveryProfileUpdate.

Gift cards

Scopes: read_gift_cards / write_gift_cards. giftCardCreate(input: { initialValue, note }).

B2B (Shopify Plus)

Scopes: read_companies / write_companies. Companies, locations, catalogs and price lists. Check sp.shop.plus first: on other plans these queries are refused.

if (!sp.shop.plus) return { error: "B2B needs Shopify Plus" };
const r = sp.shopify.graphql(`{ companies(first: 50) { nodes { id name locations(first: 10) { nodes { name } } } } }`);

Reports and analytics

Scope: read_reports. Shopify's analytics query language (ShopifyQL) is available where Shopify offers it to apps.

What plugins can't do

  • Edit theme files directly (Shopify reserves that for approved apps). Use theme blocks, the app embed, widgets and metafields instead, see Theme blocks.
  • Run inside checkout. Checkout extensions and Shopify Functions ship with ShopPress versions, not plugins.
  • Reach another store, the server, or the internet beyond approved hosts.

Tips

  • Ask only for what you need (plugin.scopes); merchants trust smaller lists.
  • Read userErrors after every mutation and sp.log them.
  • Big jobs: process 50 to 100 items per run, keep a cursor in sp.kv, and use schedule to continue.
  • Shopify rate limits are handled for you (automatic retry), but fewer, larger queries are faster.