Skip to main content
Connecting Shopify is a one-time per-store action. Most accounts connect a single store at the account level and every project inherits it. Agencies that run multiple brands in one workspace can additionally connect (or bind) a store per project. Either way, deploys automatically receive the right credentials for the effective connection.

Two ways to connect

Account level (start here)

Connect a store once under Settings → Commerce → Shopify. Every project on the account inherits it. This is the right path for one account = one brand = one store.

Project level (multi-shop)

Inherit, override, or disable Shopify per project from the project’s Shopify panel. Override binds a project to a specific store — ideal when one workspace manages several brands.
Both paths use the same OAuth handshake and the same connection store. The only difference is what the connection is bound to — the account default, or a single project. Project-level connect requires that a Shopify OAuth app is already registered on the account (see prerequisites).

Prerequisites

1

A Shopify store

A live or development Shopify store. Hiveku works with any plan (Basic and up). For native subscriptions you’ll also need Shopify Payments turned on — see Subscriptions.
2

Owner or admin role on the Hiveku account

Connecting a store writes account-scoped credentials. Member-role users can’t initiate the OAuth flow.
3

A Shopify OAuth app registered in Hiveku settings

One-time setup per Hiveku account: register a Shopify app in your Shopify Partner dashboard, then add its client_id + client_secret under Settings → OAuth apps in Hiveku. Subsequent stores reuse the same app. Both the account and project flows require this — the project panel refuses to start OAuth if no provider=shopify app exists yet.

Connect at the account level

This is the usual starting point. The first store you connect on an account becomes the account default — every project under that account uses it with no per-project setup.
1

Open the Shopify settings page

Navigate to /dashboard/commerce/settings/shopify.
2

Click "Connect a store"

A dialog asks for two inputs:
  • Shop domain — the permanent *.myshopify.com domain (not your custom domain). Example: acme.myshopify.com.
  • OAuth app — picked from the apps you registered in Hiveku settings. If you only have one, it’s preselected.
3

Authorize on Shopify

Hiveku opens a popup pointing at https://{shop}.myshopify.com/admin/oauth/authorize. The merchant approves the requested scopes (read_products, write_products, read_orders, read_customers, and more — the full list is in the developer section below).
4

Hiveku finishes the handshake

The popup closes automatically. Behind the scenes:
  • Hiveku exchanges the OAuth code for an offline Admin API access token.
  • A Storefront API token is minted via the Admin GraphQL API.
  • Both tokens are encrypted at rest (SHOPIFY_TOKEN_ENCRYPTION_KEY, AES-256-GCM).
  • Webhook subscriptions are registered for the topics Hiveku needs (orders, products, subscription events, GDPR compliance).
  • The connection is saved with purpose = 'account_default' and appears as Active in the settings UI.
The shop domain you enter must match what Shopify’s OAuth callback returns. The callback verifies the ?shop= query param against the domain the flow was started for, to defend against shop-substitution attacks.

The account-default vs project-override model

Hiveku resolves a project’s Shopify connection with a simple precedence, optimized for agency users: project override → account default → none.
When one Hiveku account maps to one brand and one store, you never touch the project settings — everything inherits the account default. When a single workspace manages multiple brands, you set the connection per project.

Connect (or bind) at the project level

Each project has a Shopify panel with three modes. You’ll find it in two places — they’re the same panel:

The three modes

Pick a mode (uses an existing connection)

1

Open the project's Shopify panel

From /dashboard/[projectId]/settings (or the Ecommerce tab). The panel shows the currently effective connection and its source — account default or project override.
2

Choose Inherit, Override, or Disabled

Selecting a mode saves immediately. Override additionally shows a Store dropdown listing every active connection on the account; pick one and the project’s deploys pull credentials from it going forward.
If no store is connected to the account yet, the panel says so and links you to Set up Shopify for this account (/dashboard/commerce/settings/shopify). Connect at the account level first, then inherit or override here.

Connect a project-specific store (new OAuth)

Sometimes the store you want isn’t connected to the account yet and you want it bound directly to this project. The panel’s Connect a project-specific store button runs a fresh OAuth handshake and, on success, binds the result to this project as an override — no separate mode-selection step needed.
1

Click “Connect a project-specific store”

You’ll be asked for the *.myshopify.com shop domain (validated against the acme.myshopify.com shape). The panel uses the account’s registered Shopify OAuth app automatically.
2

Authorize on Shopify

Same popup handshake as the account flow. The merchant approves the requested scopes.
3

Hiveku binds it to this project

On success the connection is saved with purpose = 'project_override' and the project is switched to Override mode, pointed at the new connection. The panel refreshes to show it as the effective store.
If the account has no Shopify OAuth app registered, this button stops with: “No Shopify OAuth app registered. Add one under Settings → OAuth apps first.” Register the app once (see prerequisites), then retry.
Prefer the account-default path unless you genuinely run multiple brands from one workspace. It’s fewer moving parts, and every project just works. Reach for project-level override only when different projects legitimately need different stores.

Under the hood (for developers)

Both flows share one start → callback pair, distinguished by intent_type:The project flow’s start body carries intent_data.project_id. The shared callback is GET /api/oauth/shopify/callback; it verifies in three layers — signed-state HMAC (CSRF/replay), Shopify callback HMAC (keyed by the app’s client_secret), and a shop-domain match — then exchanges the code, mints tokens, persists, and registers webhooks.
Two tables back the whole thing:
  • shopify_connections — one row per connected store. purpose is 'account_default' or 'project_override'. shop_domain is globally unique, so connecting a shop already bound to a different account is rejected.
  • project_shopify_settings — one row per project. override_mode is 'inherit' | 'override' | 'disabled'; when 'override', shopify_connection_id points at the bound connection.
resolveShopifyCredentials({ accountId, projectId }) in src/lib/shopify/credentials.ts applies the precedence: override_mode = 'override' (and a live connection) wins; override_mode = 'disabled' returns none even if an account default exists; otherwise it falls back to the account default (purpose = 'account_default', not disconnected). Results are cached in-process for 60 seconds and busted via bustShopifyCredentialsCache on connect / disconnect / override changes.
The project Shopify panel talks to:
  • GET /api/builder/shopify/{projectId}/status — the effective connection + the project’s stored override_mode / shopify_connection_id.
  • PUT /api/builder/shopify/{projectId}/settings — save a mode (body: override_mode, optional shopify_connection_id).
  • GET /api/builder/shopify/connections — the account’s connections. Returns only non-secret fields (id, shop_domain, shop_name, purpose, disconnected_at) — never token columns.
  • GET /api/oauth-apps?provider=shopify — the registered Shopify OAuth app(s) used to start OAuth.
  • Requested scopes live in DEFAULT_SHOPIFY_SCOPES (src/lib/shopify/auth.ts) — the single source of truth shared by the OAuth start route and the scope-drift cron: read_products, write_products, read_orders, read_customers, write_customers, read_content, write_content, read_themes, write_themes. Your Shopify app’s configured scopes must match.
  • API version is pinned by SHOPIFY_API_VERSION = '2025-04'.
  • The Admin token is offline (long-lived, valid until the app is uninstalled) — no grant_options[]=per-user, because Hiveku makes server-to-server Admin API calls.
  • Env vars (builder web service): SHOPIFY_TOKEN_ENCRYPTION_KEY (32-byte base64, AES-256-GCM for stored tokens) and NEXT_PUBLIC_APP_URL (drives the callback and the /api/webhooks/shopify endpoint). The OAuth app’s client_id/client_secret live per-account in oauth_apps, not in env.

What gets stored

Hiveku stores the minimum needed to operate the integration, on the shopify_connections row: Tokens never appear in the API responses to your browser or in logs — the connection-list endpoint returns only non-secret fields.

Multiple stores on one account

You can connect any number of Shopify stores. Only one can be the account default at a time (purpose = 'account_default'); the rest are purpose = 'project_override' and selectable per project via Override mode. To swap defaults: disconnect the current default, then reconnect the store you want as the new default. Connecting a fresh account default automatically retires the previous active default for that account.

Disconnecting

1

Disconnect from Hiveku

Click Disconnect on the connection in /dashboard/commerce/settings/shopify. Hiveku marks the row disconnected_at = now(), stops registering webhook events, and clears the credentials from the next deploy. The Admin token is not force-revoked at Shopify — your Shopify admin can also un-install the app to be thorough.
2

(Optional) Uninstall in Shopify admin

Visit Shopify admin → Apps → uninstall Hiveku. Shopify revokes the token and fires app/uninstalled to Hiveku, which sets disconnected_at if it wasn’t already.
After disconnect, your deployed sites lose access to Shopify credentials on the next deploy. The currently-running site keeps working until then. Plan accordingly if you need a clean cutover.

Reconnecting

Re-running OAuth for a previously connected store (a reconnect — intent_type = 'shopify_reconnect', keyed by the existing connection_id) updates that same connection row in place — encrypted tokens, scopes, shop metadata, and timestamps all refresh, and the revalidation_token is rotated. The connection’s purpose and any project-level bindings survive.

What’s next

Build the storefront

What the AI agent scaffolds after a connection is active.

Adding to an existing Next.js project

Pre-flight compat check + dry-run for retrofits.