Search by

jeffersongoncalves / laravel-shopify

jeffersongoncalves

Shopify Admin API integration for Laravel

Package info

github.com/jeffersongoncalves/laravel-shopify

pkg:composer/jeffersongoncalves/laravel-shopify

Fund package maintenance!

jeffersongoncalves

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.0.0 2026-09-08 01:28 UTC

This package is auto-updated.

Last update: 2026-09-08 01:32:08 UTC


README

Laravel Shopify

Laravel Shopify

Tests PHPStan Code Style Latest Version on Packagist Total Downloads License

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

  • Shopshop()->get()
  • Productsproducts()->list(), get(), count(), create(), update(), delete(), variants()
  • Ordersorders()->list(), get(), count(), close(), open(), cancel(), fulfillments()
  • Customerscustomers()->list(), get(), search(), count(), create(), update(), delete(), orders()
  • Webhookswebhooks()->list(), get(), create(), delete()
  • GraphQLShopify::graphql($query, $variables) returns the data payload and turns an errors array into an exception, even though Shopify answers those with HTTP 200
  • Escape hatchShopify::get(), post(), put(), delete() reach any endpoint the resources do not cover yet
  • Forgiving shop domainmy-store, my-store.myshopify.com and https://my-store.myshopify.com/admin all work
  • Thin by design — every method returns the raw decoded JSON response as an array, no DTOs
  • Fails loud — a non-2xx response throws ShopifyException carrying 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.