PHPackages                             esign/laravel-shopify - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. esign/laravel-shopify

ActiveLibrary[API Development](/categories/api)

esign/laravel-shopify
=====================

A modern Laravel package for Shopify app development

3.0.0(2w ago)41.2k↓24.2%[1 PRs](https://github.com/esign/laravel-shopify/pulls)MITPHPPHP ^8.1CI passing

Since Jan 30Pushed 2w agoCompare

[ Source](https://github.com/esign/laravel-shopify)[ Packagist](https://packagist.org/packages/esign/laravel-shopify)[ RSS](/packages/esign-laravel-shopify/feed)WikiDiscussions master Synced 2w ago

READMEChangelog (10)Dependencies (39)Versions (15)Used By (0)

Laravel Shopify
===============

[](#laravel-shopify)

[![run-tests](https://github.com/esign/laravel-shopify/actions/workflows/run-tests.yml/badge.svg)](https://github.com/esign/laravel-shopify/actions/workflows/run-tests.yml)

A modern Laravel package for building **embedded Shopify apps** using **session tokens** and **Shopify Managed Installation**. Built on top of the official `shopify/shopify-app-php` library.

Features
--------

[](#features)

- **Session Token Authentication** - Modern token exchange flow (no OAuth callbacks needed)
- **Shopify Managed Installation** - Scopes managed entirely by Shopify CLI via `shopify.app.toml`
- **Shop Model** - Encrypted tokens, soft deletes, reinstallation support
- **GraphQL Client** - Type-safe queries/mutations with automatic token refresh, rate-limit (throttle) handling, and logging
- **Webhook System** - HMAC verification, job dispatch with queue routing, built-in GDPR handlers
- **8 Middleware Types** - Embedded app, webhooks, App Proxy, UI extensions, Flow actions
- **Configurable Routes** - Move or disable any package route to avoid conflicts with your app
- **Multi-Shop Ready** - Single database, per-shop authentication

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 11, 12, or 13
- Shopify CLI 3.x+ (for deployment)

Installation
------------

[](#installation)

### 1. Install via Composer

[](#1-install-via-composer)

```
composer require esign/laravel-shopify
```

### 2. Publish Configuration &amp; Migrations

[](#2-publish-configuration--migrations)

```
php artisan vendor:publish --provider="Esign\LaravelShopify\ShopifyServiceProvider"
php artisan migrate
```

This publishes:

- `config/shopify.php` - Main configuration
- `database/migrations/` - Shops table
- `resources/views/vendor/shopify/` - Blade templates (app.blade.php, auth-error.blade.php)

### 3. Configure Environment

[](#3-configure-environment)

Add to your `.env`:

```
SHOPIFY_API_KEY=your_api_key_from_shopify_partner_dashboard
SHOPIFY_API_SECRET=your_api_secret_from_shopify_partner_dashboard
SHOPIFY_API_VERSION=2026-01
```

**Important:** Do NOT set `SHOPIFY_SCOPES` in your `.env` file. Scopes are managed by Shopify CLI via your `shopify.app.toml` file.

When [rotating your client secret](https://shopify.dev/docs/apps/build/authentication-authorization/client-secrets/rotate-revoke-client-credentials), set the previous secret as `SHOPIFY_OLD_API_SECRET` — requests signed with either secret are accepted until the rotation completes, after which you can remove it.

### 4. Configure Shopify CLI (`shopify.web.toml`)

[](#4-configure-shopify-cli-shopifywebtoml)

The Shopify CLI needs a `shopify.web.toml` next to your `shopify.app.toml` so `shopify app dev` knows how to serve your Laravel app:

```
name = "My App"
roles = ["frontend", "backend"]
webhooks_path = "/webhooks/app/uninstalled"

[commands]
dev = "php artisan serve"
```

- `roles = ["frontend", "backend"]` is required for embedded apps — without it the CLI won't proxy your app correctly.
- The CLI provides `PORT` and `SERVER_PORT` environment variables; Laravel's `php artisan serve` picks up `SERVER_PORT` automatically, so no port flag is needed.
- `webhooks_path` should match the package's webhook route (`/webhooks/{topic}`, see the Routes section if you changed the prefix).

How It Works
------------

[](#how-it-works)

### Shopify Managed Installation

[](#shopify-managed-installation)

This package uses **Shopify Managed Installation**, which means:

1. **No OAuth Flow** - Shopify handles the entire installation process
2. **No Callback Routes** - Your app doesn't need `/auth/install` or `/auth/callback` endpoints
3. **Scopes in TOML** - All scopes are defined in `shopify.app.toml`, not in your Laravel code
4. **Session Tokens** - App Bridge sends session tokens with every request
5. **Token Exchange** - Session tokens are exchanged for access tokens via Shopify's API

### Authentication Flow

[](#authentication-flow)

```
User installs app in Shopify admin
  ↓
Shopify manages installation (reads shopify.app.toml for scopes)
  ↓
App loads in embedded iframe
  ↓
App Bridge sends session token in request header
  ↓
VerifyEmbeddedApp middleware validates session token
  ↓
Middleware loads/creates shop record
  ↓
If no access token exists, exchanges session token for offline token
  ↓
Shop authenticated via Auth::user()

```

### Routes

[](#routes)

The package automatically registers these routes:

- `GET /shopify/auth/token-refresh` - Session token refresh bounce page (`shopify.auth.token-refresh`)
- `GET /shopify/auth/error` - Error handling (`shopify.auth.error`)
- `GET /` - Embedded app home, requires session token authentication (`shopify.app.home`)
- `POST /webhooks/{topic}` - Webhook handling (`shopify.webhooks.handle`)

**There are no OAuth routes** (`/auth/install`, `/auth/callback`) because Shopify manages installation automatically.

#### Overriding or disabling package routes

[](#overriding-or-disabling-package-routes)

Every route can be relocated or disabled via the `routes` block in `config/shopify.php` — useful when your application already uses `/` for something else:

```
'routes' => [
    'enabled' => true,          // false = the package registers no routes at all
    'app_home' => true,         // false = skip only the "GET /" app home route
    'app_home_path' => '/',     // relocate the app home, e.g. '/shopify-app'
    'prefix' => 'shopify',      // prefix for the auth routes
    'webhooks_prefix' => 'webhooks',
],
```

If you disable routes and register your own, point them at the package controllers and **keep the route names intact** — the token refresh redirect resolves `route('shopify.auth.token-refresh')` internally:

```
use Esign\LaravelShopify\Http\Controllers\AppController;
use Esign\LaravelShopify\Http\Controllers\AuthController;

Route::get('/shopify/auth/token-refresh', [AuthController::class, 'tokenRefresh'])
    ->name('shopify.auth.token-refresh');

Route::middleware('shopify.verify.embedded-app')
    ->get('/my-app', [AppController::class, 'home'])
    ->name('shopify.app.home');
```

Scope Management
----------------

[](#scope-management)

### Important: Scopes Are Managed by Shopify CLI

[](#important-scopes-are-managed-by-shopify-cli)

This package **does not** manage scopes in Laravel. All scopes are defined in your `shopify.app.toml` file and managed by Shopify CLI.

### How to Configure Scopes

[](#how-to-configure-scopes)

1. **Edit your `shopify.app.toml` file:**

```
# The scopes your app needs
scopes = "read_products,write_products,read_orders"
```

2. **Deploy via Shopify CLI:**

```
# Deploy your app (Shopify reads the TOML file)
shopify app deploy

# Or run in development
shopify app dev
```

3. **Updating Scopes:**

When you change scopes in `shopify.app.toml`, merchants will be prompted to reapprove your app on their next visit. Shopify handles this automatically.

### Common Scopes

[](#common-scopes)

```
# Product management
[access_scopes]
scopes = "read_products,write_products"

# Order management
[access_scopes]
scopes = "read_products,write_products,read_orders,write_orders"

# Customer data
[access_scopes]
scopes = "read_products,write_products,read_customers,write_customers"

# Full access (be careful!)
[access_scopes]
scopes = "read_products,write_products,read_orders,write_orders,read_customers,write_customers"
```

### Why No SHOPIFY\_SCOPES Environment Variable?

[](#why-no-shopify_scopes-environment-variable)

In traditional OAuth flows, you'd set scopes in `.env`:

```
SHOPIFY_SCOPES=read_products,write_products  # ❌ Don't do this with Shopify Managed Installation
```

With Shopify Managed Installation:

- Scopes are **only** defined in `shopify.app.toml`
- Shopify CLI reads the TOML file during deployment
- Your Laravel app **never needs to know** what scopes are configured
- This prevents scope drift between your TOML and your code

Quick Start
-----------

[](#quick-start)

A query/mutation is a small class implementing a contract with three methods: `query()` (the GraphQL string), `variables()` (the variables array), and `mapFromResponse()` (turn the response into whatever you want to return). `mapFromResponse()` receives a `Shopify\App\Types\GQLResult`; read the parsed payload from `$response->data`.

#### Creating a Query

[](#creating-a-query)

```
