kyon147 / laravel-shopify
Shopify package for Laravel to aide in app development
Requires
- php: ^8.2
- ext-json: *
- funeralzone/valueobjects: ^0.5
- gnikyt/basic-shopify-api: ^9.0 || ^10.0 || ^11.0
- jenssegers/agent: ^2.6
- laravel/framework: ^12.0 || ^13.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.8
- friendsofphp/php-cs-fixer: ^3.0
- laravel/legacy-factories: ^1.3.0
- mockery/mockery: ^1.0
- orchestra/testbench: ^10.0 || ^11.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^8.0 || ^9.0 || ^10.0 || ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-master
- v27.3.0
- v27.2.1
- v27.2.0
- v27.1.3
- v27.1.2
- v27.1.1
- v27.1.0
- v27.0.1
- v27.0.0
- 26.x-dev
- v26.1.2
- v26.1.1
- v26.1.0
- v26.0.0
- 25.x-dev
- v25.2.2
- v25.2.1
- v25.2.0
- v25.1.1
- v25.1.0
- v25.0.2
- v25.0.1
- v25.0.0
- v24.1.0
- v24.0.0
- v23.1.0
- v23.0.0
- v22.0.1
- v22.0.0
- v21.2.0
- v21.1.0
- v21.0.0
- v20.0.0
- v19.2.0
- v19.1.0
- v19.0.0
- v18.2.0
- v18.1.0
- v18.0.1
- v18.0.0
- v17.5.0
- v17.4.4
- v17.4.3
- v17.4.2
- v17.4.1
- v17.4.0
- v17.3.4
- v17.3.3
- dev-fix/billing-return-26.x
- dev-fix/billing-return
- dev-fix/billing-return-25.x
- dev-feature/accept-shopify-id-token
- dev-feature/accept-id-token
- dev-hotfix/fix-auth-flow-08-11-25
- dev-hotfix/remove-some-app-references
- dev-bugfix/fix-connections-and-queue-not-working-on-job-internal
- dev-support/move-to-appbridge-v4
- dev-hotfix/error-when-no-theme
- dev-Kyon147-patch-1-composer-support
- dev-hotfix/242/add-host-to-the-install-redirect
- dev-support/allow_changing_of_the_appbridge_cdn
- dev-support/config_comment_update_namespace
- dev-hotfix/job_dispatch_sync
- dev-support/ci_fix_v2
- dev-support/fix_ci_test
- dev-support/update_billing_redirect_to_use_appbridge
- dev-bugfix/+73/double_host_issue
- dev-hotfix/fix_missing_host_on_authcon_and_fullpage
- dev-hotfix/iframe_protection_no_longer_booting
- dev-hotfix/fix_host_getting_dropped_on_auth_redirect
- dev-support/update_app_bridge_to_only_use_host
- dev-support/update_host_for_redirection_places
- dev-support/update_autoloader
- dev-hotfix/revert_recent_billing_auth_changes
This package is auto-updated.
Last update: 2026-10-27 12:16:56 UTC
README
This is a maintained version of the wonderful but now deprecated original Laravel Shopify App. To keep things clean, this has been detached from the original.
To install this package run:
composer require kyon147/laravel-shopify
Publish the config file:
php artisan vendor:publish --tag=shopify-config
A full-featured Laravel package for aiding in Shopify App development, similar to shopify_app for Rails. Works for Laravel 8 and up.
Table of Contents
* Wiki pages
For more information, tutorials, etc., please view the project's wiki.
Goals
- Per User Auth Working
- Better support for SPA apps using VueJS
- Getting "Blade" templates working better with Shopify's new auth process???
Documentation
For full resources on this package, see the wiki.
Request authentication
Routes protected by verify.shopify require a valid Shopify session token in both SPA and MPA applications. Send it in Authorization: Bearer <token> or the token input. A store name, installed-store record, or Laravel session cookie alone does not authenticate a protected action.
The token determines the authenticated store. Any supplied shop, shopDomain, or X-Shop-Domain must identify that same store, including values in query parameters, form or JSON bodies, route parameters, and referer query parameters. Conflicting values return HTTP 403 before the controller runs. Controllers should use $request->user() to select their store; application-specific store IDs and resource IDs still need ownership checks.
Browser GET requests without a token are redirected through the existing App Bridge token page. Billing callbacks follow this flow and retain their plan and charge parameters. POST and other non-safe requests must carry a token; they cannot use the token-page redirect to replay their bodies. Billing actions also require a valid authenticated store, even when used in custom routes.
When upgrading, update SPA requests and billing forms that relied on a store name or cookie to include a session token. Only the configured authentication and token-bootstrap route names are exempt from session-token authentication.
Billing callbacks also retrieve the charge using the authenticated store's credentials and verify Shopify's stored return URL against the intended store and plan before changing billing state. The charge ID, billing terms, and approval status must match; changing the callback plan or substituting another charge returns HTTP 403. Already active charges do not need a second activation call. Custom billing flows must create charges with the same return URL and terms as ChargeHelper::details(). Changes to the app URL, callback host value, or plan terms during approval can require restarting approval. This check adds a Shopify charge lookup, with no database migration or new dependency.
Expiring offline access tokens
Shopify requires expiring offline access tokens for new public apps created on or after April 1, 2026. This package supports them when enabled:
- Run package migrations so your shops table includes
shopify_offline_refresh_token,shopify_offline_access_token_expires_at, andshopify_offline_refresh_token_expires_at. - Set
SHOPIFY_EXPIRING_OFFLINE_TOKENS=truein.env(seeexpiring_offline_tokens,auto_migrate_legacy,offline_access_token_refresh_skew_seconds, andoffline_refresh_token_renewal_daysinconfig/shopify-app.php). - Keep
APP_KEYstable: refresh tokens are stored encrypted with Laravel’s encrypter.
Authorization code exchange, session-token exchange, and refresh_token grants are handled inside this package (Osiset\ShopifyApp\Services\ApiHelper and OfflineAccessTokenRefresher), not via gnikyt/basic-shopify-api updates. A valid access token is refreshed automatically before apiHelper() builds the API session when the offline token is expired or within the configured skew.
Migrating existing installs (optional): Shops that already have a non-expiring offline token are not upgraded automatically when you enable the flag alone. Options:
- Passive (default): With
SHOPIFY_AUTO_MIGRATE_LEGACY=true(default), the firstapiHelper()call for a legacy shop runs Shopify Step 4 token exchange synchronously. Failures are logged and the request continues with the legacy token (fail-open). - Batch CLI (serverless-safe):
php artisan shopify-app:migrate-expiring-offline-tokens(--dry-run,--shop=example.myshopify.com,--queue=,--connection=) chunks matching shops and dispatchesMigrateShopTokenJobto the queue — no HTTP in the command itself (suitable for Laravel Vapor). Without--queue=/ config, jobs use the app default queue. SetSHOPIFY_MIGRATE_OFFLINE_TOKENS_JOB_QUEUE/SHOPIFY_MIGRATE_OFFLINE_TOKENS_JOB_CONNECTION(or matchingjob_queues/job_connectionskeys) for a dedicated worker. - Action:
MigrateShopToExpiringOfflineAccessToken— call per shop from your own code; returnsmigrated,skipped,reason, anderrorkeys. - API:
ApiHelper::exchangeNonExpiringOfflineTokenForExpiring($shopDomain, $currentOfflineToken)then persist withShopCommand::setAccessToken.
Migration is one-way: Shopify revokes the old token on successful exchange.
Proactive renewal for dormant shops (optional): Refresh tokens expire after ~90 days. If a shop has no API activity (webhooks, scheduled jobs, etc.) for that long, tokens can lapse. The package provides an opt-in CLI that queues renewal for shops whose refresh token expires within a configurable window (default 14 days via SHOPIFY_OFFLINE_REFRESH_TOKEN_RENEWAL_DAYS):
- Batch CLI:
php artisan shopify-app:refresh-expiring-offline-tokens(--dry-run,--shop=example.myshopify.com,--days=14,--queue=,--connection=) chunks matching shops and dispatchesRefreshShopOfflineTokenJobto the queue. UseSHOPIFY_REFRESH_OFFLINE_TOKENS_JOB_QUEUE/SHOPIFY_REFRESH_OFFLINE_TOKENS_JOB_CONNECTION(or CLI overrides) to target a dedicated worker; otherwise jobs use the app default queue. - Scheduler (app-owned): the package does not register a schedule automatically. Add to your app's
routes/console.phporKernel:
Schedule::command('shopify-app:refresh-expiring-offline-tokens')->daily();
Long-running jobs: Token refresh runs when the API client is first built ($shop->api() / $shop->apiHelper()). If a job reuses the same shop model for hours, the cached client keeps the old access token even after expiry. Options:
- Opt-in config:
SHOPIFY_REFRESH_OFFLINE_TOKEN_BEFORE_API_CALL=truere-checks expiry before eachapi()/apiHelper()call and rebuilds the client when needed. - Manual helpers on your shop model:
$shop->offlineAccessTokenIsFresh()—truewhen the token is outside the refresh skew window$shop->refreshOfflineAccessTokenIfNeeded()— refreshes via Shopify and clears the cached client$shop->resetApiClient()— clears the cached client so the next API call rebuilds it
// Per loop iteration in a bulk job if (! $shop->offlineAccessTokenIsFresh()) { $shop->refreshOfflineAccessTokenIfNeeded(); } $shop->api()->graph('...');
If your User model overrides $casts, merge datetime casts for the two *_expires_at columns (the ShopModel trait uses mergeCasts when initializeShopModel runs).
Longer term, consider replacing or forking gnikyt/basic-shopify-api for REST/Graph traffic if you need an actively maintained HTTP client; expiring offline OAuth is already decoupled from that dependency.
Issue or request?
If you have found a bug or would like to request a feature for discussion, please use the ISSUE_TEMPLATE in this repo when creating your issue. Any issue submitted without this template will be closed.
License
This project is released under the MIT license.
Misc
Repository
Contributors
Contributions are always welcome! Contibutors are updated each release, pulled from Github. See CONTRIBUTORS.txt.
If you're looking to become a contributor, please see CONTRIBUTING.md.
Maintainers
Maintainers are users who manage the repository itself, whether it's managing the issues, assisting in releases, or helping with pull requests.
Currently this repository is maintained by:
- @kyon147
- @JonPurvis
@gnikytOriginal author of the package. See announcement for details.
Looking to become a maintainer? E-mail @kyon147 directly.
Special Note
I develop this package in my spare time, with a busy family/work life like many of you! So, I would like to thank everyone who's helped me out from submitting PRs, to assisting on issues, and plain using the package (I hope its useful). Cheers.

