A JSON-first command-line interface for the Shopify Admin GraphQL API — for store owners, operators, developers, AI agents, and CI jobs.
shopi is intentionally a thin layer over Shopify Admin GraphQL. It does not
hide the API behind hand-written wrappers. Instead it reads the live schema of
your store and lets you call any query or mutation Shopify exposes:
shopi read products --first 10 --select 'nodes { id title status }'
shopi write productCreate --input @product.json --confirm
shopi gql --query '{ shop { name plan { publicDisplayName } } }'This project is open source under the MIT license. It is not affiliated with, endorsed by, or sponsored by Shopify Inc. "Shopify" is a trademark of Shopify Inc.
- What it does
- Getting started ← install + connect in 3 steps
- Choose your scopes
- First commands
- Common workflows
- Output formats
- Troubleshooting
- Command reference
- Development
- License
- Runs exact Admin GraphQL documents, with variables from files, flags, or stdin.
- Builds read commands from any
QueryRootfield (products,orders,shop, …). - Builds write commands from any
MutationRootfield (productCreate,metafieldsSet, …). - Discovers and inspects every live query/mutation through schema introspection.
- Picks sensible output automatically: tables in a terminal, JSON in pipes and CI.
- Guards every write behind an explicit
--confirmflag.
shopi can never bypass Shopify access scopes. Each operation only works if your
app was installed with the matching read_* / write_* scope.
shopi runs on Bun (≥ 1.1).
bun add -g shopi-cli
shopi version # → shopi-cli 0.1.0shopi connects with a Client ID and Client secret from a Dev Dashboard
app that is installed on your store.
- Open https://admin.shopify.com/settings/apps/development in your store admin and click through to the Dev Dashboard.
- Create an app and give it a name (only you see it).
- Add the Admin API access scopes your work needs — e.g.
read_products,write_products,read_orders. See Choose your scopes. - Install the app on your store and copy the Client ID and Client secret from the app's Settings.
Keep the Client secret like a password — never commit it.
Create a .env file in your working directory with your shop and credentials
(shopi reads .env automatically; you can also export the variables
instead):
SHOPIFY_SHOP=your-store.myshopify.com
SHOPIFY_CLIENT_ID=your-client-id
SHOPIFY_CLIENT_SECRET=your-client-secretThat's it — you're connected. shopi exchanges your credentials for a
short-lived Admin API token on every run (refreshed automatically), so nothing
sensitive is written to disk. Verify it:
shopi auth status --validateA successful run prints your shop name, plan, and granted scopes. There's a
ready-made .env.example to copy from.
Prefer a saved login over a .env file? Store the same credentials in a
named profile with shopi auth login:
shopi auth login \
--shop your-store.myshopify.com \
--client-id your-client-id \
--client-secret your-client-secret \
--profile production \
--validateshopi still refreshes the token automatically on every run. (If you already
have an Admin API access token, pass --token shpat_… instead of
--client-id/--client-secret.)
Profiles are written to ~/.config/shopi/config.json (0600 permissions — it
holds your credentials, so don't commit it). Select one later with
--profile production, or scope it to a single repo with --local
(./.shopi/config.json).
Grant the narrowest set of scopes that covers your workflow. A read scope can
only run reads; mutations need the matching write_* scope. Typical starting
points:
read_products,write_products,read_orders,read_customers,read_inventory,write_inventory
Broad "manage most of the shop" scope string used against the test store
read_assigned_fulfillment_orders,write_assigned_fulfillment_orders,read_customers,write_customers,read_price_rules,write_price_rules,read_discounts,write_discounts,write_draft_orders,read_draft_orders,read_files,write_files,read_fulfillments,write_fulfillments,write_inventory,read_inventory,read_legal_policies,read_locales,write_locales,write_locations,read_locations,write_marketing_events,read_marketing_events,read_markets,write_markets,read_merchant_managed_fulfillment_orders,write_merchant_managed_fulfillment_orders,read_metaobject_definitions,write_metaobject_definitions,read_metaobjects,write_metaobjects,read_online_store_pages,write_order_edits,read_order_edits,read_orders,write_orders,read_products,write_products,read_reports,read_returns,write_returns,read_shipping,write_shipping,read_content,write_content,read_themes,write_themes,read_third_party_fulfillment_orders,write_third_party_fulfillment_orders,read_translations,write_translations
Some scopes require app review, a specific app type, or Shopify Plus. Only add scopes your app is eligible for — Shopify rejects the rest. After changing scopes, reinstall/update the app on the store.
shopi version
shopi auth status --validate # confirm the connection
shopi gql --query '{ shop { name myshopifyDomain } }'
shopi schema pull # cache the live Admin schema
shopi ops list --kind query --filter product # discover available operations
shopi ops show productCreate --kind mutation --json --prettyBuilt-in help is always the source of truth:
shopi --help
shopi help read
shopi help writeshopi read loads the live schema, validates the field and its arguments, then
builds the query for you.
shopi read products --first 10 --select 'nodes { id title handle status }'
shopi read orders --first 25 --query 'financial_status:paid' --output json
shopi read product --id gid://shopify/Product/1234567890 --json --prettyPreview the generated GraphQL without calling Shopify:
shopi read products --first 5 --dry-run --json --prettyshopi write targets MutationRoot. It refuses to run unless you pass
--confirm.
shopi write productCreate \
--input @examples/product-create.json \
--select 'product { id title handle } userErrors { field message }' \
--confirm --json --prettyFor mutations with multiple arguments, pass them explicitly:
shopi write metafieldsSet \
--arg metafields=@examples/metafields-set.json \
--select 'metafields { id key namespace value } userErrors { field message }' \
--confirmSafety pattern: dry-run first, then confirm the exact same command.
shopi write productCreate --input @product.json --dry-run --json --pretty
shopi write productCreate --input @product.json --confirm --json --prettyshopi gql --file examples/shop-info.graphql --json --pretty
shopi gql --file examples/products-list.graphql --variables '{"first": 10}'
shopi gql --file mutation.graphql --variables @variables.json --confirm--full includes GraphQL extensions (such as query-cost data). Without it,
shopi prints only data.
shopi ops list --kind mutation --filter metafield # find operations
shopi ops show orders --kind query --json --pretty # inspect args & return type
shopi schema show Product --json --pretty # inspect a type
shopi schema path # where the cache livesThe schema is cached under $XDG_CACHE_HOME/shopi (or ~/.cache/shopi). Add
--refresh to any read/write/ops command to re-pull it.
Interactive terminals default to table; pipes and CI default to json.
Override explicitly:
shopi read products --first 10 --output json --pretty
shopi read products --first 10 --output markdown
shopi read products --first 10 --tableshop_not_permitted / "Client credentials cannot be performed on this shop".
The app and the store are not in the same Dev Dashboard organization. Open
the Dev Dashboard, confirm the store appears under Stores in the same org
as the app, and that SHOPIFY_SHOP matches its *.myshopify.com domain exactly.
A store created from the Shopify admin (rather than the Dev Dashboard) is usually
the cause — recreate the dev store from the Dev Dashboard's Stores page.
401 Unauthorized / "Invalid API key or access token". Re-check your
SHOPIFY_CLIENT_ID / SHOPIFY_CLIENT_SECRET and that the app is installed on the
store. shopi auth doctor --api-debug prints HTTP status/timing to stderr (never
the token).
A write is refused. Mutations require --confirm. Run with --dry-run first
to preview the generated GraphQL, then re-run with --confirm.
Access denied for a field. Your app is missing the required scope. Add the
matching read_* / write_* scope in the Dev Dashboard, reinstall/update the
app on the store, then try again.
See more detail. SHOPI_DEBUG=1 shopi … prints extra error detail;
--api-debug prints request diagnostics.
Repo documentation:
You can also read these from the CLI itself:
shopi docs show auth
shopi docs show commands
shopi docs show use-casesshopi ships a pack of Agent Skills that teach coding agents
— Claude Code, the Claude Agent SDK, and Claude.ai — how to drive the CLI across
the whole Shopify Admin API: discovery, reading, writing, and end-to-end domain
workflows. A hub skill (shopi-cli-usage) covers the CLI mechanics, and
focused domain skills layer the exact Admin GraphQL fields, inputs, and
gotchas on top of it.
| Skill | For |
|---|---|
shopi-cli-usage |
(hub) running, designing, and debugging any shopi command |
shopi-auth-and-profiles |
credentials, scopes, profiles, CI auth |
shopi-products-and-collections |
products, variants, media, collections |
shopi-orders-and-fulfillment |
orders, fulfillment, returns, refunds |
shopi-customers |
customers, segments, B2B companies |
shopi-inventory-and-locations |
stock levels, locations, cost & tracking |
shopi-metafields-and-metaobjects |
custom data (metafields & metaobjects) |
shopi-discounts-and-pricing |
discounts, redeem codes, price lists |
shopi-bulk-operations |
large exports/imports via bulk operations |
Install the whole pack so it's available in any project:
mkdir -p ~/.claude/skills
cp -R skills/shopi-* ~/.claude/skills/The skills trigger automatically when a request matches — you don't need to name them. See skills/README.md for per-skill descriptions, project-scoped installation, and the design principles they follow.
bun install
bun run check # typecheck + tests
bun run build # bundle to dist/shopiThe CLI has no runtime dependencies. Development uses Bun, TypeScript, and
bun test.
MIT — see LICENSE.
