Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Shopify App Php Laravel Package

shopify/shopify-app-php

A PHP library for building Shopify apps, with tools for OAuth, session storage, webhooks, and Shopify API access to help integrate stores into Laravel or plain PHP projects.

Deep Wiki
Context7

Getting Started

Install and scope the decision first. composer require shopify/shopify-app-php pulls the app-level scaffolding (OAuth, session storage contracts, webhook registry, API client) that sits above the raw Admin API. If you're already on the lower-level shopify/shopify-api, this package is the ergonomic wrapper — you get the same primitives but with an app lifecycle opinion baked in. Confirm which one your existing code targets before mixing them.

Minimum viable config. Four values gate everything: API key, API secret, app URL (public HTTPS), and scopes. Put them in .env, resolve them once at boot, and never read getenv() in library code. Pin the API version explicitly — the default moves and silent version drift is the single most common "it worked last month" failure.

Where to look, in order:

  1. The README quickstart — the install → callback → embedded index loop is the canonical path; get that running unmodified before adapting it.
  2. The example/demo app in the repo if present — it demonstrates the intended request lifecycle better than the docs.
  3. src/Auth — OAuth and session-token handling; this is where 80% of production bugs land.
  4. src/SessionStorage — read the adapter interface before writing your own; it defines the contract your DB layer must satisfy.
  5. src/Webhooks — registration and HMAC verification.
  6. tests/ — the storage and webhook tests double as executable documentation for edge cases.

First real use case: an embedded app that installs via OAuth, persists an offline token keyed by shop domain, and renders one page that executes a single GraphQL shop { name } query. That exercises config, auth, storage, and the client end-to-end. Do it against a development store with a tunnel (ngrok or cloudflared) pointed at your local server — the install redirect will not work against localhost.

Implementation Patterns

Bootstrap once per request, inject everywhere. Construct the app object in a service provider / container binding from config, not statically. This makes it swappable in tests and avoids the singleton-with-stale-config problem when you run queue workers and web requests in the same process.

Shop resolution as middleware. Two entry paths, and they must be distinguishable:

  • Install path — ?shop= query parameter, validated against ^[a-zA-Z0-9][a-zA-Z0-9-]*\.myshopify\.com$ before it touches a redirect.
  • Embedded path — Authorization: Bearer <session token> header. Validate the JWT signature with the app secret, check exp/nbf, and cross-check the dest claim against the shop you're about to load.

Resolve the shop in one middleware, attach the session to the request, and let controllers assume it exists. Every controller that re-derives the shop from user input is a cross-tenant leak waiting to happen.

Own your session storage. The package defines the interface; you implement it against Eloquent/PDO/Redis. Model it as (shop, is_online, user_id?) with a unique index on the offline tuple. Cache offline sessions in Redis with a short TTL — you'll load one on nearly every request and the DB round-trip is pure overhead. Invalidate on uninstall webhook.

Webhook handling shape: register centrally (a config array or a single registry call, not scattered addHandler calls), verify HMAC against the raw body before any framework parsing, persist the X-Shopify-Webhook-Id to a dedupe table, enqueue the work, return 200 immediately. Shopify's timeout is short and retries are aggressive; a webhook handler that does real work inline will eventually time out and then process the same event three times.

GraphQL as the default, REST only where GraphQL hasn't caught up. Wrap the client in a thin service so you can add: cost-aware backoff (read extensions.cost.throttleStatus, sleep on THROTTLED), structured logging of extensions.code, and a single place to bump the API version. For large exports use bulk operations rather than pagination loops — a 50k-variant loop will hit the cost bucket and take an hour.

Billing gates. Create recurring charges through the GraphQL mutation, store the subscription state locally, and check it in the same middleware that resolves the shop. Treat the local record as a cache of Shopify's truth and reconcile on app_subscriptions/update.

Queued jobs carry identity, not state. Pass shop domain + session key into the job; rehydrate the session inside handle(). If the token is gone (uninstalled), fail gracefully and mark the app uninstalled rather than retrying forever.

Local dev: tunnel + correct SHOPIFY_APP_URL, and make sure your framework's trusted-proxy config is right or Laravel/Symfony will generate http:// redirect URIs and OAuth will fail with a generic error.

Gotchas and Tips

  • HMAC on the raw body. Symfony/Laravel request objects may have already consumed the stream. Capture php://input before middleware, compare with hash_equals on the base64 of the binary digest. Do not json_decode first and re-encode — key order and whitespace will break verification.
  • Webhook HMAC uses the app secret; OAuth HMAC uses the app secret too — but neither uses the access token. Mixing these up produces intermittent failures on one path only, which is maddening to debug.
  • Scope changes silently break. Shopify does not re-prompt. Compare the stored scope against configured scopes on each embedded request and force a re-auth redirect when they diverge. Otherwise you ship a feature and it 403s for every existing merchant.
  • Offline vs. online tokens. Use offline for webhooks and background jobs. Online tokens expire and are user-bound — only adopt them if you genuinely need per-cashier attribution.
  • Rate limits are cost-based, not request-count-based. X-Shopify-Shop-Api-Call-Limit is a REST-era header; on GraphQL read the cost extension. Parallel PHP-FPM workers sharing one token will exhaust the bucket faster than a single serial worker.
  • Webhook deduplication is mandatory, not optional. Shopify retries on any non-2xx and can deliver duplicates even on success. X-Shopify-Webhook-Id as a unique key costs one table and saves you from double-charging customers.
  • The three GDPR webhooks are a hard review gate. customers/data_request, customers/redact, shop/redact must exist and return 200, or App Store submission fails. Implement them as no-op-but-logged handlers early so you're not retrofitting at submission time.
  • Embedded apps need the right CSP. frame-ancestors https://*.myshopify.com https://admin.shopify.com — omit it and the iframe is blank with no console error in the parent frame.
  • Third-party cookie deprecation: prefer session-token-based auth (token exchange) over cookie-based sessions. If you must use cookies, SameSite=None; Secure and be aware Safari's ITP will still bite you.
  • Never use file session storage in production. It's fine for the quickstart, catastrophic behind multiple app servers. Also watch file permissions when running as www-data with a CLI-created storage dir.
  • Extension points: implement the session storage interface and register custom webhook handlers — that's the intended surface. Avoid patching vendor code; the package is young enough (sub-100 stars, active releases) that you will want clean upgrades. Pin to a minor version and read the changelog before bumping; wrap the client behind one internal service class so a breaking change is a one-file edit.
  • Debugging breadcrumbs that pay for themselves: log X-Shopify-Shop-Domain, X-Shopify-Webhook-Id, the GraphQL operation name, and extensions.cost on every call. When a merchant reports "the app is broken," shop domain + operation name usually resolves it in one query.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource