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

Bold Shopify Toolkit Laravel Package

bold-commerce/bold-shopify-toolkit

A Laravel toolkit from Bold Commerce for building Shopify integrations. It wraps Shopify's Admin API in a fluent PHP client, with support for GraphQL and REST queries, OAuth token handling, webhook verification, and common store operations so you can ship custom apps faster.

View on GitHub
Deep Wiki
Context7

Getting Started

This is the official Shopify Admin API client for PHP (shopify/shopify-api). Day-to-day it gives you four things: OAuth/session-token auth, a REST client, a GraphQL client, and webhook registration + HMAC verification. Everything hangs off a single bootstrap call.

  1. Install and bootstrap. The package has no service provider of its own — you call Context::initialize() exactly once, usually from AppServiceProvider::boot() or a dedicated ShopifyServiceProvider:
use Shopify\Context;
use Shopify\Auth\FileSessionStorage;

Context::initialize(
    apiKey: config('shopify.api_key'),
    apiSecretKey: config('shopify.api_secret'),
    scopes: config('shopify.scopes'),
    hostName: config('shopify.host'),
    sessionStorage: new FileSessionStorage(storage_path('shopify-sessions')),
    apiVersion: '2026-07',
    isEmbeddedApp: true,
);
  1. First use case — a REST call for an offline session. Create a session once during install, then reuse it:
use Shopify\Clients\Rest;

$client = new Rest($session);
$response = $client->get('products', ['limit' => 5]);
$products = $response->getDecodedBody()['products'];
  1. Where to look first. Shopify\Clients\Rest and Shopify\Clients\Graphql are the two workhorses. Shopify\Auth\OAuth, Shopify\Auth\SessionToken, Shopify\Webhooks\Registry, and Shopify\Db\SessionStorage are the other four you'll touch. The per-version generated resource classes live under Shopify\Rest\Admin{Y-m}\… — don't start there; start with the raw clients and drop to typed resources only if you want the IDE autocomplete.

Implementation Patterns

Session storage: file in dev, DB in production. FileSessionStorage is fine locally; for anything multi-process swap to DbSessionStorage and publish the migration:

php artisan vendor:publish --tag=shopify-migrations
php artisan migrate

Load the session per request rather than caching it in a singleton — the package expects a Session object bound to the current shop. Utils::loadCurrentSession($requestHeaders, $requestCookies, $isOnline) covers the embedded-app case.

Embedded apps: verify the session token, don't roll your own JWT check. SessionToken::decode($authorizationHeader) validates signature, aud, dest, and exp. Wrap it in middleware so downstream controllers can assume $request->attributes->get('shop') exists.

GraphQL is where the real API lives. REST is fine for simple reads, but bulk operations, metafields, and anything with nested writes are GraphQL-only. Two patterns worth adopting immediately:

$client = new Graphql($session);
$response = $client->query([
    'query' => $query,
    'variables' => ['id' => $gid],
]);
  • Always inspect userErrors. GraphQL returns HTTP 200 with userErrors populated on a failed mutation — this is the single most common source of silent data loss with this package. $response->getDecodedBody()['data']['x']['userErrors'] is your friend.
  • Use ->query(..., ['tries' => 3]) for cost/rate-limit resilience. Shopify's GraphQL cost-based throttling returns THROTTLED errors rather than 429s; the client can retry, but it won't do anything clever beyond that.

REST for pagination-heavy jobs. $response->getLink() gives you the Link header; $client->get() follows page_info via the next cursor. Wrap it in a generator so a full catalog sync doesn't blow memory.

Webhooks: single registry, one route. Register handlers in a service provider, then point one Laravel route (POST /webhooks) at Registry::process():

Registry::addHandler('PRODUCTS_UPDATE', function ($topic, $shop, $body, $webhookId) {
    // ...
});

// routes/webhooks.php — exclude this route from CSRF
$response = Registry::process($request->getContent());

Registration itself happens once at install: Registry::register($session, $path) after OAuth completes.

OAuth flow. OAuth::begin($shop, '/auth/callback', true) from your install entry point, OAuth::callback($query, $cookies) on the callback route. Store the resulting Session immediately.

Gotchas and Tips

8.0.0 is a signature break if you subclass anything. Explicit nullable parameter types landed across Asset, Base, Cart, and Client. If you've extended those classes and overrode a method that takes a nullable parameter, PHP will fatal at class-load time with an incompatible-signature error until you change Type $x = null to ?Type $x = null in your override. This is the one thing that can take a working app down on a composer update, so grep for extends Base, extends Client, extends Cart, and extends Asset before upgrading.

Laravel 11 is no longer supported. illuminate/support widened to ^12 || ^13, so a Laravel 11 app cannot resolve 8.0.0 without --ignore-platform-reqs dancing. Either bump Laravel or pin to 7.x. Laravel 13 support is new here, so if you're on 13 this is the release that unblocks you.

8.0.0 is byte-identical to 8.0.0-beta.6. If you already regression-tested the beta against Admin API 2026-07, you've tested the final tag. No need to re-run the full suite — just confirm the beta was the artifact you tested.

PHP 8.4+ users: the implicit-nullable fix means your own deprecation warnings from E_DEPRECATED on nullable parameters should disappear for package code. If you still see them, they're coming from your app or a different dependency.

Webhook HMAC needs the raw body. Registry::process() can verify the X-Shopify-Hmac-Sha256 header only if you hand it the untouched payload. Laravel's VerifyCsrfToken middleware and any request-logging middleware that re-serialises JSON will break this silently (you get a verification failure, not an error you can trace). Exclude the webhook route from CSRF and pass $request->getContent().

Context::initialize() is global and once-only. Calling it twice throws. If you're writing tests that touch Shopify, initialise in setUpBeforeClass() on a base test case, not per-test, or you'll fight it forever.

Versioned resource classes are coupled to the package version. Shopify\Rest\Admin2025_01\Product disappears/renames when the bundled API version rolls forward. Prefer the untyped Rest client with string paths for long-lived code, and treat typed resources as a convenience layer you're willing to churn on during upgrades. This release line is regression-tested against Admin API 2026-07, so that's the version to target.

Base::unserializeModel() no longer calls ReflectionProperty::setAccessible(). That call has been a no-op since PHP 8.1, so the removal is invisible unless you've patched around it — safe to ignore.

Check ->getStatusCode() before decoding. The clients throw HttpResponseException on transport failure but happily return 401/403 bodies as arrays, which then fail confusingly downstream. A small helper that asserts 2xx and throws with the response body attached will save you hours during scope changes.

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.
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
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
cgv/localized-resource