Skip to content

Add shopify store auth and store execute#7122

Open
dmerand wants to merge 2 commits intomainfrom
store-execute
Open

Add shopify store auth and store execute#7122
dmerand wants to merge 2 commits intomainfrom
store-execute

Conversation

@dmerand
Copy link
Copy Markdown
Contributor

@dmerand dmerand commented Mar 27, 2026

What

Add shopify store auth and shopify store execute so Shopify CLI can authenticate an app against a store and then run Admin API GraphQL without needing a local app project in the current repo.

  • shopify store auth starts a PKCE OAuth flow and stores online per-user auth for later store commands.
  • shopify store execute runs Admin API GraphQL against that stored auth.
  • Writes require --allow-mutations.
  • The command surface is aligned with shopify app execute where it makes sense.

Why

We want a clear store workflow in CLI:

  1. authenticate the app against a store
  2. verify access
  3. run store-scoped Admin API operations

This makes store operations usable outside an app project while still using standard app auth and explicit mutation safety.

How

shopify store auth

Add a new shopify store auth command that:

  • authenticates with PKCE (response_type=code, code_challenge, code_challenge_method=S256, code_verifier)
  • listens on a loopback callback bound to 127.0.0.1
  • validates the callback store and state before exchanging the code
  • stores the resulting online token, refresh token, expiry metadata, scopes, and associated user details for reuse by later store commands

shopify store execute

Add a new shopify store execute command that:

  • accepts query / query-file / variables / variable-file / version / output-file flags similar to shopify app execute
  • parses and validates the GraphQL operation before execution
  • blocks mutations unless --allow-mutations is passed
  • fails the command on GraphQL errors instead of treating them as successful output

Session handling

Store auth is persisted per store and per user for the configured app client ID.

When stored auth is expired, shopify store execute refreshes it before version resolution / execution. When stored auth is no longer valid, the current stored session is cleared and the user is prompted to re-run shopify store auth.

That invalid-auth path is handled consistently for:

  • refresh failures
  • invalid refresh responses
  • version-discovery auth failures
  • execute-time 401s

Internal structure

The execute path is split into request preparation, auth/context loading, transport, and result emission helpers so the command stays thin and the execution flow is easier to reason about.

The implementation also adds a small internal target seam for future store-scoped GraphQL APIs while keeping the current behavior Admin-only.

Manual testing

# Authenticate the app against the store
pnpm run shopify store auth \
  --store shop.myshopify.com \
  --scopes read_products,write_products

# Run a read query
pnpm run shopify store execute \
  --store shop.myshopify.com \
  --query 'query { shop { name id } }'

# Run a query with variables from the command line
pnpm run shopify store execute \
  --store shop.myshopify.com \
  --query-file ./operation.graphql \
  --variables '{"id":"gid://shopify/Product/1"}'

# Verify execute fails without stored auth
pnpm run shopify store execute \
  --store shop.myshopify.com \
  --query 'query { shop { name } }'
# after clearing stored auth, should prompt to run `shopify store auth`

# Verify mutations require explicit opt-in
pnpm run shopify store execute \
  --store shop.myshopify.com \
  --query 'mutation { shop { id } }'
# should fail until --allow-mutations is added

Copy link
Copy Markdown
Contributor Author

dmerand commented Mar 27, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 2026

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 82.35% 15352/18642
🟡 Branches 74.75% 7530/10074
🟢 Functions 81.48% 3853/4729
🟢 Lines 82.75% 14517/17543

Test suite run success

4032 tests passing in 1541 suites.

Report generated by 🧪jest coverage report action from 7aae5d3

@dmerand
Copy link
Copy Markdown
Contributor Author

dmerand commented Mar 27, 2026

/snapit

@github-actions
Copy link
Copy Markdown
Contributor

🫰✨ Thanks @dmerand! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260327162821

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@dmerand dmerand force-pushed the store-execute branch 2 times, most recently from e15c53b to 893af23 Compare March 27, 2026 16:59
Copy link
Copy Markdown
Contributor Author

dmerand commented Mar 27, 2026

/snapit

@github-actions
Copy link
Copy Markdown
Contributor

🫰✨ Thanks @dmerand! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260327203223

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@dmerand dmerand force-pushed the store-execute branch 2 times, most recently from 97030c7 to 0a0c94d Compare March 31, 2026 17:35
@dmerand dmerand changed the title Proto: shopify store execute Add shopify store auth and store execute Mar 31, 2026
@dmerand dmerand force-pushed the store-execute branch 4 times, most recently from 3d0c271 to c83e839 Compare March 31, 2026 22:36
@dmerand dmerand marked this pull request as ready for review March 31, 2026 22:37
@dmerand dmerand requested review from a team as code owners March 31, 2026 22:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “store” workflow to Shopify CLI that can authenticate an app against a store (PKCE, per-user online tokens) and then execute Admin API GraphQL operations without requiring a local app project.

Changes:

  • Introduces shopify store auth (PKCE OAuth to loopback callback; persists online auth per store/user).
  • Introduces shopify store execute (Admin API GraphQL execution with variable/query file support, mutation safety via --allow-mutations, and error/failure handling).
  • Adds supporting session storage, context/version resolution, transport helpers, and CLI docs/topic updates.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/cli/src/index.ts Registers store:auth and store:execute commands with the CLI entrypoint.
packages/cli/src/cli/commands/store/auth.ts New shopify store auth command wiring/flags.
packages/cli/src/cli/commands/store/auth.test.ts Tests flag parsing and service invocation for store auth.
packages/cli/src/cli/commands/store/execute.ts New shopify store execute command wiring/flags.
packages/cli/src/cli/commands/store/execute.test.ts Tests flag parsing and service invocation for store execute.
packages/cli/src/cli/services/store/auth-config.ts Defines store auth client id, callback path/port, session keying, token masking.
packages/cli/src/cli/services/store/session.ts Implements persisted per-store/per-user session bucket and expiry helper.
packages/cli/src/cli/services/store/session.test.ts Unit tests for session bucket behavior and expiry margin logic.
packages/cli/src/cli/services/store/auth.ts Implements PKCE flow, loopback callback server, token exchange, and persistence.
packages/cli/src/cli/services/store/auth.test.ts Unit tests for PKCE helpers, callback server, token exchange, and persistence.
packages/cli/src/cli/services/store/execute-request.ts Parses query/query-file, variables, validates single operation and mutation gating.
packages/cli/src/cli/services/store/execute-request.test.ts Unit tests for request preparation and validation errors.
packages/cli/src/cli/services/store/admin-graphql-context.ts Loads/refreshes stored auth and resolves Admin API version.
packages/cli/src/cli/services/store/admin-graphql-context.test.ts Tests refresh flow, invalid auth paths, and version selection/validation.
packages/cli/src/cli/services/store/admin-graphql-transport.ts Executes Admin GraphQL request and normalizes 401/GraphQL-error handling.
packages/cli/src/cli/services/store/admin-graphql-transport.test.ts Tests success, 401 clearing + reauth message, GraphQL errors, and passthrough errors.
packages/cli/src/cli/services/store/graphql-targets.ts Adds an internal target seam (currently Admin-only) for store-scoped GraphQL APIs.
packages/cli/src/cli/services/store/graphql-targets.test.ts Tests target context preparation and execution delegation.
packages/cli/src/cli/services/store/execute-result.ts Writes results to file or stdout and emits a success UI message.
packages/cli/src/cli/services/store/execute-result.test.ts Tests file output vs stdout output behavior.
packages/cli/src/cli/services/store/execute.ts Orchestrates request prep → context load → execution → result emission.
packages/cli/src/cli/services/store/execute.test.ts End-to-end-ish unit tests for execute flow behaviors and error cases.
packages/cli/package.json Adds a new store topic/category for CLI command grouping.
packages/cli/README.md Adds docs entries for new commands (and includes regenerated usage sections).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

This comment has been minimized.

@dmerand dmerand force-pushed the store-execute branch 7 times, most recently from 870584e to a462574 Compare March 31, 2026 23:51
@@ -0,0 +1,168 @@
import {fetchApiVersions} from '@shopify/cli-kit/node/api/admin'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are a lot of different concerns going on in this services area. it'd be nice to establish some clearer patterns of organization around things.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked this question myself and landed here. This is definitely a big PR, and if you can believe it the current state is a significant decomposition of several earlier attempts. I agree that we can find more patterns, but I'd like to defer further architectural work until we have either better repo guidelines for service architecture, or more APIs we want to use for store execute that would guide patterns.

outputContent`Refreshing expired token for ${outputToken.raw(session.store)} (expired at ${outputToken.raw(session.expiresAt ?? 'unknown')}, refresh_token=${outputToken.raw(maskToken(session.refreshToken))})`,
)

const response = await fetch(endpoint, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'd be nice to split out the gql scaffolding from the specific queries/mutations

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment here, decomposing the API contexts further is good follow-up work IMO.

} catch (error) {
if (
error instanceof AbortError &&
error.message.includes(`Error connecting to your store ${adminSession.storeFqdn}:`) &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a code comment to address the string parsing later?

[key: string]: StoredStoreAppSessionBucket
}

let _storeSessionStorage: LocalStorage<StoreSessionSchema> | undefined
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally this gets initialized as part of some lifecycle and not inlined

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I'd like to address in follow-up given shipping pressure unless you think it's broken.

}

export function getStoreGraphQLTarget(api: StoreGraphQLApi): StoreGraphQLTarget<AdminStoreGraphQLContext> {
switch (api) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what other targets are we going to be supporting?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admin API is a known first target, and we know we're going to be adding more -- the conceptual limit would be all of the APIs that dev-mcp supports. Since each API has its own auth/setup concerns, I've designed it to make those pluggable. It's arguably not necessary for this first PR, but I also wanted to guide agents not to make monolithic additions, which were definitely their preference here.

allowMutations: input.allowMutations,
})

const context = await renderSingleTask({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated as it's an existing pattern, but i really want us to split out rendering and data flow more

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. This could be a function that returns an object that is renderable, or a task, and the rendering could be separate. Not to litigate in this PR though.

Copy link
Copy Markdown
Contributor

@ryancbahan ryancbahan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code generally makes sense. i think there are some improvements we can make to organization, and generally to establishing more clarity around what goes where.

@dmerand dmerand requested a review from ryancbahan April 1, 2026 14:23
Copy link
Copy Markdown
Contributor

@ryancbahan ryancbahan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tophatted and works as expected. nice job! a few small notes:

  • the "authorization successful" callback view that opens in browser after auth is totally unstyled. would be nice to improve that
  • after running auth, the command outputs a "next steps" block. this is nice, but has two minor issues:
    1. it renders backticks inline, presumably for markdown codelbock that doesn't exist
    2. when the text breaks to a newline, it adds a pipe | where the newline break is, which breaks the ability to copy/paste

@craigmichaelmartin
Copy link
Copy Markdown
Contributor

/snapit

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

🫰✨ Thanks @craigmichaelmartin! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260401163359

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants