kyon147/laravel-shopify
A maintained full-featured Laravel package for Shopify app development, with OAuth, webhooks, billing, and support for expiring offline access tokens. Works with Laravel 8+ and installs via Composer.
Architecture fit. This is an opinionated, batteries-included Shopify app framework for Laravel — it occupies the same architectural slot as shopify_app does in Rails. It owns the OAuth handshake, session-token/App Bridge validation, shop persistence, webhook registration and HMAC verification, billing (recurring/one-time/usage), and now expiring offline token lifecycle. That is a lot of surface area for one dependency, and it is the right call only if we intend to build a standard embedded Shopify app on a Laravel monolith. If our target architecture is a thin API service, a multi-tenant platform with non-Shopify channels, or a serverless/event-driven split, this package becomes a partial fit at best: it assumes a shops table it owns, a User model trait, and its own route/middleware stack.
The package is a maintained community fork of a deprecated original (gnikyt/osiset). It retains the Osiset\ShopifyApp namespace, which is functionally harmless but tells you the codebase's lineage: it is a continuity fork, not a greenfield design. The README's own unchecked Goals list (per-user auth, better SPA/Vue support, Blade templates under Shopify's newer auth flow) is an honest admission that the front-end integration story is behind where Shopify's platform has moved.
Integration feasibility. Straightforward on the happy path: composer require, publish config, run migrations, wire middleware. The friction is in the seams:
shops table (including the new shopify_offline_refresh_token and *_expires_at columns). Schema ownership becomes shared between the package and our app — we need a policy on this before install, not after.User model trait (ShopModel / initializeShopModel) and uses mergeCasts; apps with custom $casts must merge datetime casts manually — a documented, easy-to-miss footgun.gnikyt/basic-shopify-api, which the README itself flags as a candidate for replacement or fork. OAuth/expiring-token logic has been deliberately decoupled from that dependency, but bulk data access has not.Technical risk. Scored 43.99 overall with 498 stars and 0 listed dependents. The zero-dependents figure is probably a metadata artifact rather than true adoption — most Shopify apps are private codebases and never register as Packagist dependents — but we should not treat it as evidence of a healthy ecosystem either.
| Risk | Severity | Notes |
|---|---|---|
| Bus factor / maintainer capacity | High | Primary maintainer states they work on this in spare time, alongside family and work. Two named maintainers, one dominant. |
| Deprecated upstream HTTP client | Medium–High | gnikyt/basic-shopify-api is acknowledged as not actively maintained; the fork has worked around it for OAuth but not for REST/Graph traffic. |
| Platform churn | Medium | Shopify's auth, App Bridge, and token models have changed repeatedly (session tokens, expiring offline tokens mandated for new public apps from April 1, 2026). The fork is tracking this, but each change forces a major or minor upgrade on our side. |
| Incomplete roadmap items | Medium | Unchecked goals imply known gaps in SPA and per-user auth; we should not assume these are solved. |
| Security history | Low–Medium | v27.1.0 shipped redirect-URL escaping and blocks API routes opened in a browser — good responsiveness, but indicates prior exposure. |
| One-way token migration | Medium | Migrating legacy non-expiring offline tokens revokes the old token on success. No rollback path. |
| Docs location | Low–Medium | Canonical documentation lives in the GitHub wiki, not in-repo. Wiki content is not versioned against releases and cannot be reviewed in PRs. |
Key questions.
composer.json, not the README.shops table schema — us or the package? What is our policy when a package migration conflicts with ours?Stack fit. Strong fit for a standard Laravel monolith serving an embedded Shopify app: Eloquent-backed shop records, Laravel queues for token jobs and webhooks, Laravel's encrypter for refresh-token storage, and Artisan commands that are explicitly serverless-aware (no HTTP in the batch commands, designed for Vapor). Weak fit for polyglot or microservice architectures, and for anything where Shopify is one channel among many — the package's models and middleware assume it owns the shop identity.
Migration path. Two distinct migrations to plan:
gnikyt/osiset original to this fork. The retained Osiset\ShopifyApp namespace reduces this to a composer swap plus verification, which is the fork's main selling point. Budget for regression testing of the auth flow rather than code changes.apiHelper() call (default, SHOPIFY_AUTO_MIGRATE_LEGACY=true), a batch CLI (shopify-app:migrate-expiring-offline-tokens) with dry-run and per-shop targeting, a per-shop Action class, and a low-level ApiHelper exchange. Choose deliberately:
--dry-run first, then real runs in cohorts. It is idempotent-ish by design, serverless-safe, and gives us observable progress per shop.Because migration is one-way, take a shop-token inventory and a communication plan before the first real run.
Compatibility. Verify, don't assume: PHP and Laravel version constraints in composer.json, queue driver support (the sync driver had a bug where the migrate command halted on the first failed shop — fixed in v27.0.1, so pin at or above that), and whether our app already has a conflicting /authenticate or webhook route. The APP_KEY dependency is non-negotiable: refresh tokens are stored encrypted, so key rotation without a re-encryption plan forces every shop through re-auth.
Sequencing.
--dry-run, then cohort the batch migration.Schedule::command('shopify-app:refresh-expiring-offline-tokens')->daily() in our own console kernel/routes, targeting a dedicated queue and connection via the config keys.Step 6 is ours to build. The package explicitly does not schedule it, and skipping it means dormant shops silently lose refresh tokens after ~90 days.
Maintenance. This is a shared-ownership dependency with a single dominant part-time maintainer. We inherit the upgrade treadmill of a fast-moving platform: the 2026 release history alone spans majors for PHP/Laravel support drops and token model changes. Practical posture:
composer update.gnikyt/basic-shopify-api as a known liability and budget for eventual replacement or vendoring, especially if we do heavy bulk Graph/REST work.Support. Community-only: GitHub issues with a required template, a wiki, no commercial support, no SLA, no security disclosure policy documented in the sources provided. Escalation for a production auth outage is "file an issue and wait." For anything customer-facing with revenue attached, we should assume we may have to patch a fork ourselves. Two mitigations: (a) fork-and-vendor readiness — confirm we can maintain a private fork, and (b) keep the auth/webhook surface small enough to reimplement in an emergency.
Scaling. Auth, webhook receipt, and billing are per-request and cheap — they scale with the app like any Laravel app. The real scaling concerns are queue-bound:
SHOPIFY_*_JOB_QUEUE / *_JOB_CONNECTION keys so token jobs don't starve webhook or customer-facing workers.SHOPIFY_REFRESH_OFFLINE_TOKEN_BEFORE_API_CALL=true (with a per-call cost) or call $shop->offlineAccessTokenIsFresh() / refreshOfflineAccessTokenIfNeeded() / resetApiClient() per iteration. Any bulk-sync job must adopt one of these patterns — this is a code-review checklist item, not an optimization.Failure modes.
| Failure | Symptom | Mitigation |
|---|---|---|
| Refresh token lapse (dormant shop > ~90 days) | Shop silently drops out; API calls fail | Scheduled refresh-expiring-offline-tokens daily; alert on shops with refresh expiring inside the window |
APP_KEY rotation |
All refresh tokens undecryptable; mass re-auth | Key-rotation runbook with re-encryption; treat APP_KEY as immutable per environment |
| Stale cached client in long job | 401s mid-run after token expiry | Per-iteration freshness check or the opt-in before-call refresh flag |
| Fail-open legacy migration | Shop keeps working on a legacy token; migration silently incomplete | Prefer batch CLI; alert on migrated=false outcomes and log volume |
| One-way migration mistake | Old token revoked, no rollback | Dry-run, cohort rollout, per-shop inventory before execution |
| Webhook HMAC rejection | Missing order/product events, drift in data | Signed fixture tests in CI; monitor webhook endpoint 4xx rate |
| Sync queue driver on batch command | Command halts on first failed shop | Pin ≥ v27.0.1; use a real queue driver, not sync |
| Upstream package abandonment | No security patches for new Shopify auth changes | Private fork readiness; documented reimplementation scope for OAuth/webhooks |
Ramp-up. Moderate. A Laravel developer gets a dev-store app running in a day; getting production-safe takes considerably longer because the hard parts are not in the quickstart. Budget for:
APP_KEY rotation, webhook verification failure, billing disputes, and re-auth storms.$casts merge, the long-job token pattern, and the schema-ownership rule.Bottom line. Adopt for a standard embedded Laravel Shopify app, contingent on the spike passing and on written acceptance of the maintainer-concentration and upstream-HTTP-client risks. Do not adopt if Shopify is a secondary channel, if the team cannot absorb a platform-churn upgrade cadence, or if the SPA front end is central to the product — the package's own roadmap says that path is unfinished.
How can I help you explore Laravel packages today?