it-bens/shopware-sdk
A PHP SDK for the Shopware 6 Admin and Store APIs, providing typed request builders, response hydration, and PSR-18 client integration so you can query products, orders, and customers without hand-writing endpoints.
Check your PHP version before anything else. v0.6.0 dropped PHP 8.1 and 8.2 and added 8.4 — the floor is now PHP 8.3. If you're still on 8.1/8.2 and run a bare composer require it-bens/shopware-sdk, Composer will silently resolve to 0.5.x rather than erroring, and you'll spend an afternoon debugging behaviour that was fixed in 0.6.0. Constrain explicitly:
composer require it-bens/shopware-sdk:^0.6
If that fails, composer why-not it-bens/shopware-sdk 0.6.0 tells you immediately whether it's your php platform config or a transitive dependency holding you back. Remember to bump config.platform.php in composer.json if you pin it, and to update your CI matrix — a pipeline still on 8.1 will fail at install, not at test.
Confirm the namespace second. The fork's autoload namespace may differ from upstream vin-sw/shopware-sdk, so check composer.json (autoload.psr-4) before writing imports. The Rector pass in 0.6.0 touched a lot of files; if the maintainer also renamed or reorganised anything, the autoload block is the source of truth.
Create credentials in Shopware. In the admin go to Settings → System → Integrations, create an integration, and copy the Access key ID and Secret access key. These are not app credentials — they use the OAuth client_credentials grant against /api/oauth/token, which returns a short-lived bearer token (default 600s).
Where to look first, in order:
composer.json — require.php and autoload.psr-4. Two minutes here saves an hour of class not found.src/Client.{php} (or ClientFactory) — how base URI, credentials, and the PSR-18 HTTP client are wired.src/Api/ (or src/Endpoint/) — one class per resource: Product, Order, Customer, Media, etc. This is your day-to-day surface.src/Entity/ and src/Struct/ — typed models and request payloads. Most friction lives here (missing fields, customFields).tests/ — often the only real usage documentation in a small fork; mirror its fixtures.First use case — read a product:
$client = new Client('https://shop.example.com', $accessKey, $secretKey);
$criteria = [
'limit' => 10,
'filter' => [['type' => 'equals', 'field' => 'active', 'value' => true]],
];
foreach ($client->products()->search($criteria) as $product) {
echo $product->getName(), PHP_EOL;
}
Verify in a REPL (php -a, now safely on 8.3 or 8.4) that the returned objects are typed entities and not raw arrays before building on top of them — this is the single biggest fork-to-fork difference.
Bind the client as a singleton in your container. Authentication is a network round-trip; never construct a client per call.
// services.php
$container->set(Client::class, fn () => new Client(
$_ENV['SHOPWARE_URL'],
$_ENV['SHOPWARE_CLIENT_ID'],
$_ENV['SHOPWARE_CLIENT_SECRET'],
));
Wrap the SDK behind your own repository interface. This insulates your app from both the fork's bus factor and Shopware version drift — and now also from the PHP-version treadmill, since a future 0.7.0 may drop 8.3 the same way 0.6.0 dropped 8.1:
interface ProductCatalog {
public function findBySku(string $sku): ?Product;
public function upsert(ProductPayload $p): void;
}
Criteria building. Shopware's search API is where the power is. Patterns you'll use daily:
filter — equals, contains, range, multi, not, prefixsort — [['field' => 'name', 'order' => 'ASC']]associations — eager-load relations (manufacturer, categories, media) in one requestincludes — cut response size dramatically by restricting fields per entitytotal-count-mode — 0 none, 1 exact, 2 source; exact is slower on large catalogsSync jobs. Use searchIds first, then batch by ID; or rely on the SDK's pagination helper:
$page = 1;
do {
$result = $client->products()->search(['limit' => 100, 'page' => $page]);
// process $result->getData()
$page++;
} while ($result->getData() !== []);
For bulk upserts (products, prices, stock), prefer Shopware's _action/sync endpoint. If the SDK lacks a typed wrapper, drop to whatever generic request() method exists and pass the payload directly — it's far faster than 1,000 PUTs.
Webhook handling. Shopware signs webhooks with shopware-shop-signature, an HMAC-SHA256 of the raw body keyed by the app secret, not the integration secret. If you're consuming webhooks in your integration context you'll need a small app; don't try to reuse the OAuth secret here.
Testing locally. Run dockware/dev in Docker, create an integration, and point the client at http://localhost:8000. Match the container's PHP version to your production PHP (8.3 or 8.4 now) — a dockware/dev image on 8.x-built extensions can mask problems that only appear under the newer runtime. Write a smoke test per endpoint class you touch; it doubles as documentation for the next developer.
CI and dependency hygiene. 0.6.0's own release is mostly housekeeping — Rector rules, dependency bumps, GitHub Actions, Renovate. Mirror that: run Renovate/Dependabot against your lock file, and keep a Composer platform config so a maintainer's PHP bump shows up as a failed install in CI, not a runtime fatal in production.
composer why-not php 8.3 across your whole dependency graph before committing to it.^0.6 and commit the lock file.readonly, constructor promotion, and possibly declare(strict_types=1). Anywhere you subclassed an SDK class or overrode a method with a looser signature, PHP will now throw a fatal or a TypeError that didn't exist in 0.5.x. If you extend anything under src/, diff the signatures between tags before upgrading.Type $x = null) were fatal-adjacent in 8.4; a Rector pass is exactly what fixes those. If you were previously running 8.4 with --ignore-platform-req=php, drop that flag — it was masking the incompatibility.composer.json and vendor-lock it (or mirror the repo) so a force push can't break you. If you hit a bug, be prepared to fork and point at your VCS.Shopware\Sdk\… (upstream) or a renamed namespace. A stale use statement produces a confusing "class not found" rather than an auth error.Uuid or call Ramsey\Uuid methods; Shopware will reject the format outright. (Rector's type tightening may make this fail at the boundary now instead of at the API.)data for single resources but {data, meta, links} for collections. The SDK may or may not unwrap this. Confirm the shape before writing foreach over the return value.translated fields, and some admin action endpoints. Test against the exact Shopware minor you run in production, not latest.translated fields. Names, descriptions, and custom fields are nested under translated for the active language; requesting name when you wanted German text returns the default language. Pass _inherit or add the language to the request explicitly.Retry-After; respect it with an exponential backoff decorator around the PSR-18 client rather than sleeping in business code.ClientInterface in the constructor. That's your hook for logging, retries, tracing, and pointing at a mock in tests. Also decorate your repository interface (from the patterns section) rather than subclassing endpoint classes — doubly so now that a Rector pass can change protected/internal signatures between minor versions.errors[] array the SDK silently swallowed.customFields are always nullable. Persisted product custom fields only exist once saved; treat every read defensively with ?? in the model.How can I help you explore Laravel packages today?