jeffersongoncalves / laravel-shopify
Shopify Admin API integration for Laravel
Package info
github.com/jeffersongoncalves/laravel-shopify
pkg:composer/jeffersongoncalves/laravel-shopify
Fund package maintenance!
Requires
- php: ^8.2
- illuminate/contracts: ^12.0|^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.21
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel Shopify
A Laravel client for the Shopify Admin API. A fluent Shopify facade groups the shop, product, order, customer, and webhook endpoints behind resource accessors, signs every call with the X-Shopify-Access-Token header, and throws a ShopifyException on a non-2xx response instead of returning a silent error array. The GraphQL Admin API is one call away for everything REST cannot express.
Features
- Shop —
shop()->get() - Products —
products()->list(),get(),count(),create(),update(),delete(),variants() - Orders —
orders()->list(),get(),count(),close(),open(),cancel(),fulfillments() - Customers —
customers()->list(),get(),search(),count(),create(),update(),delete(),orders() - Webhooks —
webhooks()->list(),get(),create(),delete() - GraphQL —
Shopify::graphql($query, $variables)returns thedatapayload and turns anerrorsarray into an exception, even though Shopify answers those with HTTP 200 - Escape hatch —
Shopify::get(),post(),put(),delete()reach any endpoint the resources do not cover yet - Forgiving shop domain —
my-store,my-store.myshopify.comandhttps://my-store.myshopify.com/adminall work - Thin by design — every method returns the raw decoded JSON response as an array, no DTOs
- Fails loud — a non-2xx response throws
ShopifyExceptioncarrying the API's error message and HTTP status code
Installation
composer require jeffersongoncalves/laravel-shopify
Optionally publish the config file:
php artisan vendor:publish --tag="shopify-config"
Configuration
Add to your .env:
SHOPIFY_SHOP_DOMAIN=my-store.myshopify.com SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxxxxxx SHOPIFY_API_VERSION=2024-01
Create the access token as a custom app in your store under Settings → Apps and sales channels → Develop apps, then grant it the Admin API scopes you need (read_products, write_orders, ...). A token from a public app's OAuth flow works the same way.
Config Options
// config/shopify.php return [ 'shop_domain' => env('SHOPIFY_SHOP_DOMAIN'), 'access_token' => env('SHOPIFY_ACCESS_TOKEN'), 'api_version' => env('SHOPIFY_API_VERSION', '2024-01'), 'base_url' => env('SHOPIFY_BASE_URL'), 'timeout' => (int) env('SHOPIFY_TIMEOUT', 30), ];
base_url is built from the shop domain and API version when left unset — set it only to point the client at something else, such as a local mock server.
Usage
use JeffersonGoncalves\Shopify\Facades\Shopify; use JeffersonGoncalves\Shopify\Exceptions\ShopifyException;
Shop
Shopify::shop()->get();
Products
Shopify::products()->list(limit: 50, filters: ['vendor' => 'Acme', 'status' => 'active']); Shopify::products()->get(123); Shopify::products()->count(); Shopify::products()->variants(123); Shopify::products()->create([ 'title' => 'Product Name', 'body_html' => '<p>Description</p>', 'vendor' => 'Brand', 'product_type' => 'Category', 'variants' => [ ['price' => '99.00', 'sku' => 'SKU-001'], ], ]); Shopify::products()->update(123, ['title' => 'Renamed']); Shopify::products()->delete(123);
Orders
// Shopify defaults to open orders only; this defaults to `any` Shopify::orders()->list(limit: 50, status: 'any'); Shopify::orders()->get(42); Shopify::orders()->fulfillments(42); Shopify::orders()->cancel(42, ['reason' => 'customer']); Shopify::orders()->close(42); Shopify::orders()->open(42);
Customers
Shopify::customers()->list(limit: 50); Shopify::customers()->get(7); Shopify::customers()->search('email:ada@example.com'); Shopify::customers()->orders(7); Shopify::customers()->create(['email' => 'ada@example.com', 'first_name' => 'Ada']); Shopify::customers()->update(7, ['last_name' => 'Lovelace']); Shopify::customers()->delete(7);
Webhooks
Shopify::webhooks()->create('orders/create', 'https://example.com/hooks/orders'); Shopify::webhooks()->list(); Shopify::webhooks()->delete(1);
Common topics: orders/create, orders/paid, orders/fulfilled, customers/create, products/update, checkouts/create.
GraphQL
$data = Shopify::graphql(<<<'GQL' query ($first: Int!) { products(first: $first) { edges { node { id title totalInventory } } } } GQL, ['first' => 10]); $data['products']['edges'];
Any other endpoint
Shopify::get('/inventory_levels', ['location_ids' => '123']); Shopify::post('/price_rules', ['price_rule' => [/* ... */]]);
Handling errors
try { $products = Shopify::products()->list(); } catch (ShopifyException $e) { // $e->getMessage() — the API's error message, or the raw response body // $e->statusCode — the HTTP status returned by Shopify (0 for config errors) }
Rate limits
The REST Admin API allows about 2 requests per second per store and answers a burst with 429 Too Many Requests, which surfaces here as a ShopifyException with statusCode 429. Wrap bulk work in Laravel's HTTP retry or a queued job with backoff when you push that hard.
Testing
composer test
Static Analysis
composer analyse
Code Formatting
composer format
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.
