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

Laravel Shopify Laravel Package

jeffersongoncalves/laravel-shopify

A Laravel package for integrating Shopify stores into your application, with helpers for OAuth authentication, API requests, and webhook handling so you can build and manage commerce features more easily.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and wire up credentials before touching any resource class:

composer require <vendor>/<package>
php artisan vendor:publish --tag=shopify-config   # verify the actual tag

Set the shop and an Admin API access token in .env (names depend on the published config — read config/shopify.php first). The shop value is forgiving: a bare handle (my-store), a my-store.myshopify.com domain, or a full admin URL all resolve, so you can paste whatever the merchant gave you.

Verify the connection with the cheapest possible call, in php artisan tinker or a temporary route:

shop(); // returns the store record — proves domain + token are valid

Where to look first:

  1. The service provider and the Shopify facade — confirms how the client is bound and whether it is a singleton (relevant for multi-store later).
  2. The ShopifyException class — this is your single error surface for every resource.
  3. One resource class end to end (products()), since all the others follow the same shape.

First real use case: a scheduled command that pulls products into your database, or a webhook controller that receives orders/create and queues a job. Both exercise auth, pagination, and error handling in one go.

Implementation Patterns

Read-through resources instead of a local mirror. For admin dashboards and support tooling, call products()->list(), orders()->get($id), customers()->search($query) directly and cache the results briefly. Only persist Shopify data locally when you need joins, reporting, or resilience against rate limits.

Resource methods are thin wrappers — compose them in your own service. Wrap the package in something like App\Services\Shopify\CatalogService so controllers and jobs never touch the facade. That gives you one place to add caching, retries, and logging, and one place to swap in a fake during tests.

class CatalogService
{
    public function activeProducts(int $limit = 50): array
    {
        return Cache::remember("shopify.products.{$limit}", 300, fn () =>
            products()->list(['status' => 'active', 'limit' => $limit])
        );
    }
}

Sync jobs should page, not count. Use the cursor/since_id style pagination the client returns rather than offset paging, and advance a stored watermark (updated_at_min or last ID) so re-runs are incremental. Run the job on a queue with $tries and backoff() set — writes especially should never run inline in a request.

Orders need explicit status filters. Order listing returns any status by default. If you only want open work, chain orders()->open(); for anything else, pass the status explicitly. Do the same for counts, otherwise your dashboard numbers will include cancelled and closed orders.

Use graphql() when the REST shape fights you. Nested data — variants with inventory levels, orders with line items and fulfillments in one shot — is far cheaper as one GraphQL query than several REST calls. The package converts Shopify's HTTP 200 errors payload into an exception, so try/catch (ShopifyException $e) behaves consistently across REST and GraphQL.

Reach for the escape hatch deliberately. Shopify::get()/post()/put()/delete() covers endpoints the resources don't wrap (metafields, discounts, inventory levels, themes). Keep those calls in a dedicated class with a comment naming the API version, so a future package release can absorb them cleanly.

Webhooks: register from code, verify in middleware. Create subscriptions with webhooks()->create([...]) during deploy or in an install flow rather than clicking them into the admin, so environments stay reproducible. In the receiving controller, verify the HMAC against the raw request body before parsing, then dispatch a job and return 200 immediately.

Route::post('/webhooks/shopify', WebhookController::class)
    ->middleware('shopify.webhook'); // your HMAC middleware

Handle errors by status code, not by message. ShopifyException carries both the API message and the status. Treat 404 as "gone, delete locally", 401/403 as "token or scope problem, alert", 429 as "back off and retry", 422 as "validation — surface to the user".

try {
    products()->update($id, $payload);
} catch (ShopifyException $e) {
    if ($e->getStatusCode() === 429) {
        $this->release(60);
    }
    throw $e;
}

Multi-store. If the facade is a singleton bound to config, you likely cannot swap shops mid-request. Check the binding before designing a multi-tenant feature; if it isn't supported, resolve a fresh client per shop via the container or the escape hatch rather than mutating config at runtime.

Gotchas and Tips

  • GraphQL "success" is not success. Shopify returns HTTP 200 with an errors array for bad queries and throttling. The package handles this for graphql(), but if you drop to Shopify::post() against /graphql.json you bypass that conversion entirely and must inspect the payload yourself.
  • Rate limits are per shop, not per process. REST uses a leaky bucket, GraphQL uses a calculated cost. Parallel queue workers will trip 429s fast. Cap concurrency per shop and retry with backoff rather than hammering.
  • count() is not free. Count endpoints can be expensive and are cached by Shopify, so they may lag reality. For dashboards, prefer counting your own synced rows.
  • Pagination is cursor-based. Don't assume page=2 works. Follow the returned cursor/Link header until exhausted, and always pass an explicit limit — defaults are small and silently truncate.
  • Shop domain normalization cuts both ways. Admin URLs contain extra path segments; the package strips them, but confirm the resulting host is what you expect (log it once) — a typo'd handle resolves to a plausible-looking but wrong shop.
  • Scopes are per-endpoint and fail late. A token that reads products happily will 403 on orders or customers. When adding a new feature, verify scopes first; the ShopifyException message usually names the missing one.
  • Deletes are permanent. products()->delete(), customers()->delete(), webhooks()->delete() hit the real store. Gate destructive code paths behind a confirmation and point local/testing at a development store, never production.
  • Webhook HMAC must use the raw body. $request->all() re-encoded will not match. Use $request->getContent(). Also dedupe on X-Shopify-Webhook-Id — Shopify retries, and at-least-once delivery means your job will run twice.
  • Fulfillments and cancellations are state machines. orders()->cancel() and creating fulfillments have preconditions (financial status, existing fulfillments). Check the order first; you'll otherwise collect 422s that read like generic validation errors.
  • API version pinning. Shopify deprecates versions quarterly. Note which version the package targets (check the config or the request path) and re-test after upgrades; the escape hatch is version-sensitive too.
  • Testing. Fake at the HTTP layer (Http::fake()) or bind a stub of your own service class. Assert on your service's behaviour, and add one or two tests that assert a non-2xx response surfaces as ShopifyException with the right status code.
  • Extension points worth knowing: the raw HTTP methods for anything new in the API, and the resource classes themselves — if you need a wrapper the package lacks, subclass or extend rather than scattering Shopify::get() calls across controllers.
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.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules