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

Shopify Laravel Package

statamic-rad-pack/shopify

Statamic addon that connects your Shopify store, imports products as Statamic entries, and keeps catalog data in sync so you can build fast, editable storefronts with Laravel and Statamic.

View on GitHub
Deep Wiki
Context7

Getting Started

statamic-rad-pack/shopify is a Statamic 5 addon (so: a Laravel package) that turns a Shopify catalogue into Statamic entries and exposes the cart/checkout flow to Antlers and Blade. You use it when the marketing site lives in Statamic but the commerce engine (payments, tax, fulfilment, inventory) stays in Shopify.

v8 note first: this release swaps the HTTP layer to the official shopify/shopify-app-php library. That library is GraphQL-first and session-aware, so treat v8 as a config-and-integration break, not a drop-in. Read config/shopify.php and the addon's README/upgrade notes before touching templates.

Minimal path:

composer require statamic-rad-pack/shopify
php please vendor:publish --tag=shopify-config
php please migrate            # variants/carts/orders tables the addon ships

Then populate credentials (names may have shifted in v8 — always diff the freshly published config against your old one):

SHOPIFY_SHOP_DOMAIN=your-store.myshopify.com
SHOPIFY_ACCESS_TOKEN=shpat_xxx          # custom app, Admin API scopes
SHOPIFY_API_VERSION=2025-07
php please shopify:import     # confirm exact command name via `php please list`

Where to look first, in order:

  1. config/shopify.php — collection handles, field mappings, API version, webhook settings, log channel.
  2. The addon's routes/ — cart/checkout/webhook endpoints; you will be posting to these from JS.
  3. The addon's src/Tags/ — the Antlers surface you'll actually template against.
  4. src/Jobs/, src/Listeners/ — where product import and webhook sync happen.

First use case to get end-to-end before anything else: a product listing page rendering imported entries, a variant selector, an add-to-cart POST, and a redirect to Shopify's hosted checkout. If that loop works, the rest is polish.

Implementation Patterns

Catalogue as entries. Imported products land in a normal Statamic collection, so everything you already know applies — Nav, search, Antlers, the REST/GraphQL content APIs, revisions, multi-site.

{{ collection:products limit="12" sort="title" }}
  <a href="{{ url }}">
    {{ if featured_image }}<img src="{{ featured_image }}" alt="{{ title }}">{{ /if }}
    <h2>{{ title }}</h2>
    <p>{{ price | format_number(2) }} {{ currency }}</p>
    {{ if variants|length > 1 }}<span>{{ variants|length }} options</span>{{ /if }}
  </a>
{{ /collection:products }}

Cart mutations over AJAX. The addon exposes cart routes; keep the JS thin and let the server hold state so Antlers and Blade agree.

async function addToCart(variantId, quantity = 1) {
  const res = await fetch('/!/shopify/cart/add', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
      'X-Requested-With': 'XMLHttpRequest',
    },
    body: JSON.stringify({ variant_id: variantId, quantity }),
  });
  return res.json(); // { cart, count, total }
}

Re-render a cart drawer from the JSON response rather than reloading, and let the checkout step be a plain redirect to the cart's checkout_url. Never build your own payment form — Shopify's hosted checkout is the compliant path.

Sync via webhooks, import as backfill. The import command is a one-off; webhooks (products/create|update|delete, inventory_levels/update) are the steady state. Point Shopify at your endpoint, keep it out of CSRF, and make the handler idempotent — Shopify retries, and out-of-order delivery is normal.

// routes/web.php
Route::post('shopify/webhooks', [ShopifyWebhookController::class, 'handle'])
    ->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);

Extension points worth knowing. Rather than editing vendor code: hook the import/sync events the addon fires, override the importer/importer job bindings in a service provider, or subclass the tags. Field mapping (Shopify product → Statamic fields, metafields → your fieldsets) belongs in config or a small mapper class you own.

Local development. Webhooks need a public URL — use ngrok/expose/Laravel Herd's share, and keep a SHOPIFY_WEBHOOK_SECRET-style value per environment. Also run the import against a development store, never production, on first setup.

Blade instead of Antlers is fine where you need typed access:

$products = \Statamic\Facades\Entry::query()
    ->where('collection', 'products')
    ->where('available', true)
    ->limit(12)->get();

Gotchas and Tips

v8's library swap is the big one. shopify/shopify-app-php is a different API shape from the previous client. Anything you wrote that reached into the addon's client directly (custom commands, bespoke imports, jobs calling REST endpoints, tests mocking the old client) will break. Republish the config and diff it, then grep your codebase for the old library's namespaces.

Session storage is now a first-class concept. The official library expects a SessionStorage implementation. If the addon defaults to a file-based store, that breaks on multi-server deployments, ephemeral containers, and some queue workers. Push it onto the database or cache driver, and confirm the CLI/queue context resolves the same shop session as the web request.

GraphQL cost limits ≠ REST call limits. REST's 2 req/s leaky bucket is gone; you're now spending points from a calculated bucket. Bulk catalogue imports that were fine on v7 can get throttled. For a large store, chunk the import (--chunk, or per-collection slices), queue it, and consider Shopify's bulkOperationRunQuery for the initial pull. Add backoff on 429/THROTTLED — do not just retry in a tight loop.

Webhook verification moved. The library verifies HMAC from the raw body. Don't let middleware normalise or re-encode the payload before it reaches the handler, don't json_decode then re-verify, and keep the endpoint outside CSRF. If verification silently fails you get 401s and a catalogue that quietly stops updating — assert webhook success in your logs, and check Shopify's webhook delivery health page.

API version pinning. Set an explicit SHOPIFY_API_VERSION and put a quarterly reminder in the calendar. Shopify ships a new version every three months and supports each for ~12 months; an unpinned or stale version is the most common cause of mysterious field-level errors after a quiet period.

Scopes and token type. Custom-app Admin API tokens are permanent; public/distributed apps use OAuth with expiring access tokens and, for 2026 compliance, refresh tokens. If you plan to distribute, design for token rotation now — the session storage work above is the same work.

Don't cache inventory. Stock levels change outside Statamic. Render availability from a short-TTL value or the live cart response; never bake it into a statically cached page. And exclude /cart, /checkout, and any cart partial from Statamic's static caching and from your CDN, or customers will get each other's carts.

Money is not a float. Prices arrive in minor units. Keep integers end-to-end and format only at the edge; format_number on a float derived from 19.99 * 100 will eventually show 1998.9999.

Debugging toolkit. Put the addon's log channel on daily, tail -f storage/logs/laravel.log, and reproduce with php please shopify:import --product=<id> style single-record runs rather than a full catalogue. When something looks wrong on the storefront, check the entry's stored Shopify ID and variant IDs before suspecting templates — mismatched IDs after a re-import are the usual culprit.

Multi-site. Shopify handles translations via its own Translations API; Statamic handles them via sites. Decide which is authoritative per field (usually: Shopify for product copy, Statamic for landing-page content) and write that down, or you'll fight drift forever.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource