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
userErrorsafter every mutation andsp.logthem. - Big jobs: process 50 to 100 items per run, keep a cursor in
sp.kv, and usescheduleto continue. - Shopify rate limits are handled for you (automatic retry), but fewer, larger queries are faster.