Download the PHP package craftcms/shopify without Composer
On this page you can find all versions of the php package craftcms/shopify. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download craftcms/shopify
More information about craftcms/shopify
Files in craftcms/shopify
Package shopify
Short Description Shopify for Craft CMS
License MIT
Homepage https://github.com/craftcms/shopify
Informations about the package shopify
Shopify for Craft CMS
Build a content-driven storefront by synchronizing Shopify products into Craft CMS.
[!IMPORTANT] Version 7.x of Shopify for Craft uses a new app-based authorization system. You must follow the upgrade instructions to get new credentials.
Topics
- :package: Installation: Set up the plugin and get connected to Shopify.
- :card_file_box: Working with Products: Learn what kind of data is available and how to access it.
- :bookmark_tabs: Templating: Tips and tricks for using products in Twig.
- :leaves: Upgrading: Take advantage of new features and performance improvements.
- :telescope: Advanced Features: Go further with your integration.
Installation
Shopify requires Craft CMS 4.15.0+ or 5.0.0+.
To install the plugin, visit the Plugin Store from your Craft project, or follow these instructions.
-
Navigate to your Craft project in a new terminal:
-
Require the package with Composer:
- In the Control Panel, go to Settings → Plugins and click the “Install” button for Shopify, or run:
Connect to Shopify
The plugin works with Shopify’s Dev Dashboard app system, and is split into two primary parts: performing authorization.
To install an app into a store, one of these statements must describe your account’s relationship with it:
- You are the owner of the store;
- You have been added as a collaborator on the store, with the App developer role (see screenshot, below);
- You are working with a dev store or client transfer store belonging to your Partner organization;
[!CAUTION] The new OAuth-based API connection requires that apps are created from an “organization” that has access to the Partner Dashboard. Standalone stores (like the one created when you sign up for a Shopify account) belong to their own organization.
- If you are working with a store or account that has never accessed a Partner Dashboard, you must create a Partner profile before proceeding.
- When working from an account that has access to multiple organizations, it is generally safest to access the new Dev Dashboard via the Partner Dashboard you want the app associated with.
Create an App
- Navigate to your Dev Dashboard:
- From a store, open the account context menu (upper-right corner) and select Dev Dashboard;
- From the Partner Dashboard, open the account context menu (upper-right corner) and select Dev Dashboard;
- In the Dev Dashboard, press Create app.
- In the first screen, pick an App name that identifies the integration, like Craft CMS.
-
Press Create, then fill out the following fields to create your first “version”:
- App URL: Retrieve the Shopify App Auth URL value from the plugin’s setting screen in the Craft control panel. (This will always be your project’s URL, followed by the cpTrigger, then the action
shopify/auth:https://my-project.com/admin/shopify/auth.) - Embed app in Shopify admin: Make sure this is unchecked, as the plugin does not support embedded apps.
-
Webhooks API Version: Choose
2026-01, and add the same string to your project’s.envfile: -
Access → Scopes: The following scopes are required for the plugin to function correctly:
read_inventoryread_product_listingsread_products- Shopify requires these to be in a comma-separated list:
- Do not enable the Use legacy install flow as it can result in mismatched scopes during installation.
- App URL: Retrieve the Shopify App Auth URL value from the plugin’s setting screen in the Craft control panel. (This will always be your project’s URL, followed by the cpTrigger, then the action
- Press Release to deploy the configuration. You may give it a name and description, or let Shopify tag it with an incrementing number.
- Switch to the Settings screen of the new app, and copy the credentials into your
.envfile:
Next, you’ll configure the app’s distribution scheme.
- From the new app’s Home screen in the Dev Dashboard, follow the Select distribution method link, within the Distribution widget.
- The Partner Dashboard will open, with your app selected. Choose Custom distribution, press Select, then confirm in the dialog box.
-
Locate your store’s hostname (see screenshot, below), and paste it into the Store domain field, then press Generate link.
- Once you choose a hostname, the app is permanently locked to that store. If you do not provide the correct hostname at this stage, you’ll need to delete the app and start over.
- If you want to use the same connection across multiple related stores, check Allow multi-store install for one Plus organization.
- Take this opportunity to add the hostname to your
.envfile:
- Return to the Distribution screen and press Copy link.
You should now have a total of four SHOPIFY_* variables in your .env file:
In the Craft control panel, navigate to Shopify → Settings to configure the plugin:
- API Version:
$SHOPIFY_WEBHOOK_VERSION - Client ID:
$SHOPIFY_CLIENT_ID - Client Secret Key:
$SHOPIFY_CLIENT_SECRET - Host Name:
$SHOPIFY_HOSTNAME
Use these literal strings in the corresponding fields.
As you type the $-prefixed value into an input, Craft will suggest matching variables.
Press Save to commit the settings to project config.
[!TIP] You may see a warning below the read-only Shopify App Auth URL field. This is expected, until you’ve completed the OAuth flow!
Install in a Store
In this step, we’ll perform the authorization code grant or OAuth flow, during which Craft and Shopify negotiate a long-lived access token.
[!TIP] Whoever installs the app must be able to access to the store and the Craft project from the same browser. Shopify does not need to directly contact the Craft, so you may do this from your local development machine!
- Visit the installation URL you copied from the Distribution screen in the Partner Dashboard. You must be logged in to a Shopify account with access to the target store (but it does not need to be the same account that created the app).
- Select the store in Shopify’s context picker.
- On the Install app screen within the store’s admin, review the permissions and press Install.
[!WARNING] If you do not see a blue banner confirming This app is exclusive to your store, do not proceed! A banner saying This app can’t be installed on this store (or landing on a generic Shopify error page) usually means that the hostname is not valid for the distribution.
- You will be redirected to the Craft control panel “auth” URL you used when creating the Shopify app. (If you were not already logged in, Craft will ask for your username and password; your user must have the Access Shopify permission or be an administrator to complete the authorization flow.)
- Press Authorize in the dialog.
- Craft and Shopify will perform the OAuth handshake, and you should land on a confirmation screen in the Craft control panel saying Your Shopify app has been successfully authorized.
🎊 Congratulations! Your Craft project can now communicate with the Shopify API. Let’s take it for a spin by importing your store’s products.
Set up Webhooks
A new Webhooks tab will appear in the Shopify section of the control panel once you’ve completed the authorization flow.
Click Create webhooks on the Webhooks screen to add the required webhooks to Shopify. The plugin will use your newly-issued access token to perform this operation, so this also serves as an initial communication test.
[!WARNING] You must add webhooks for every environment you deploy the plugin to; webhooks are tied to the specific, registered URL. Be aware that Shopify will continue to attempt delivery to your development environment’s subscriptions, which may impact the statistics you see in the Dev Dashboard. See Cleanup below for help culling unused webhook subscriptions.
Testing Webhooks
Development environments are not typically exposed to the public internet, which means Shopify won’t be able to deliver webhooks.
To test synchronization in development, we recommend using ngrok to create a tunnel to your local environment.
DDEV makes this simple, with the ddev share command.
[!TIP] Use the
SHOPIFY_PUBLIC_DEV_URLenvironment variable to override your project’s base URL when creating webhooks; this allows you to continue using your regular DDEV site URL for control panel and front-end access, rather than overriding the entire project or site’s base URL.This setting may not work if you have set a custom
cpBaseUrl!
Cleanup
Each time you open an ngrok tunnel, you get a new public URL, and Shopify will be unable to deliver webhooks.
This means that you may accumulate broken subscriptions over the course of development.
In the control panel, we only display the webhooks relevant to the current environment—or, more accurately, those with a uri matching the resolved webhook URL (which can be influenced by the SHOPIFY_PUBLIC_DEV_URL variable).
You can delete individual webhooks from the control panel, or by using the CLI GraphQL playground…
…substituting a known subscription GID.
Discover orphaned subscriptions using the webhookSubscriptions() query.
Upgrading
This version (7.x) is primarily concerned with Shopify API compatibility, but the described above.
Due to significant shifts in Shopify’s developer ecosystem, many of the front-end cart management techniques we have recommended (like the JS Buy SDK and Buy Button JS) are no longer viable.
[!TIP] We strongly recommend reviewing this same section on the 6.x branch, as there were a number of breaking changes and deprecations during the upgrade from 5.x. The changelog contains specific information about the classes and methods that have been added, removed, or deprecated.
After the upgrade, you must delete and re-create webhooks for each environment. Webhooks are registered and delivered with a specific version, and a mismatch will result in errors.
Your “legacy custom app” can be left as-is or deleted, once all your environments have been migrated to the Dev Dashboard connection. While this plugin has no need for those credentials, confirm with the store owner that no other external services depend on them!
Credentials
At the beginning of 2026, Shopify overhauled how “apps” are created, moving them to the new Dev Dashboard.
You should be able to install it using the new OAuth mechanism, without disruption to product synchronization.
Publishing and Status
Shopify has eliminated sales channels for custom apps, and therefore the publishedOnCurrentPublication field is no longer available in Product queries.
This means that there is no official way to “publish” products to the Craft integration, but we cover some alternatives in the sales channel emulation section.
Product Field Layouts
The product element editor has received a major overhaul. You can now choose exactly where Shopify data is placed, within the field layout.
Front-End SDKs
Shopify has retired many of its pre-built client-side frameworks, in favor of directly communicating with the generic Storefront GraphQL API. You will need to revise how you query and mutate data, if your front-end currently depends on the JS Buy SDK or Buy Button JS.
Product Element
Products from your Shopify store are represented in Craft as product elements, and can be found by going to Shopify → Products in the control panel.
Synchronization
Once connected to Shopify, you can perform an initial synchronization of all products, from the control panel (via Utilities → Shopify Sync) or the command line:
This adds a bulk operation to the plugin’s internal queue. Once Shopify has gathered the data, it will issue a webhook to your project, and the plugin will download and process the payload.
Going forward, your products are automatically kept in sync via webhooks. You can view a history of synchronization operations by visiting the Shopify Sync utility.
[!WARNING] We do our best to capture native Shopify resources that are attached to a product (like variants, media, and options), but cannot dynamically discover relationships with other content via
Metafields, or data from third-party apps. Additional fields can be captured by listening events in a custom module.
Native Attributes
In addition to the standard element attributes like id, title, and status, each Shopify product element contains direct accessors for these canonical Shopify Product attributes:
| Attribute | Description | Type |
|---|---|---|
shopifyId |
The integer product ID from Shopify. | Integer |
shopifyGid |
The unique resource identifier (“GID”) from Shopify. This should always be the shopifyId, prepended with gid://shopify/Product/. |
String |
shopifyStatus |
The status of the product in Shopify. Values can be active, draft, or archived. |
String |
handle |
The product’s “URL handle” in Shopify, equivalent to a “slug” in Craft. For existing products, this is visible under the Search engine listing section of the edit screen. | String |
productType |
The product type of the product in your Shopify store. | String |
descriptionHtml |
Product description. Output with the \|raw Twig filter—but only if the content is trusted. This was previously called bodyHtml. |
String |
tags |
Tags associated with the product in Shopify. | Array |
templateSuffix |
Liquid template suffix used for the product page in Shopify. | String |
vendor |
Vendor of the product. | String |
data |
The raw API response data from Shopify. (See below) | Array |
metaFields |
Metafields associated with the product. | Array |
images |
Images (or “Media”) attached to the product in Shopify. The complete MediaImage objects are stored in Craft. | Array |
options |
ProductOption objects, as configured in Shopify. Each option has a name, position, and an array of in-use values. |
Array |
defaultVariant (and cheapestVariant) |
The first known (or cheapest) variant belonging to the product. This is one of the few ancillary resources that we make available as a model (craft\shopify\models\Variant). |
Variant |
createdAt |
When the product was created in your Shopify store. (This will almost always be different from the element’s native dateCreated property.) |
DateTime |
publishedAt |
When the product was published in your Shopify store. | DateTime |
updatedAt |
When the product was last updated in your Shopify store. (This will almost always be different from the element’s native dateUpdated property.) |
DateTime |
All of these properties are available when working with a product element in your templates.
Yii and Twig also allow you to access some values via magic getters—any method beginning with get (like product.getDefaultVariant()) can also be treated like a property (product.defaultVariant).
[!IMPORTANT] See the Shopify documentation on the product resource for more information about what kinds of values to expect from these properties. The nature of GraphQL (and API versioning) means that we may not be capturing 100% of the available data. To select additional fields, you can intercept the event emitted just before a product GraphQL query is sent.
A complete copy of the requested Shopify API data used to populate a Product element is available under its data property. Wherever possible, we have used Shopify’s native property names—but by virtue of fetching products via GraphQL, there may be differences between the structure of this object and the API documentation, especially as it relates to nested objects. Use the following methods to access related or nested data!
Methods
The product element has a few methods you might find useful in your templates.
Product::getVariants()
Returns an array of variants belonging to the product. Variants are not elements (just regular models), but you can use the same dot notation to access their properties:
You can product query’s .withVariants() method.
Product::getDefaultVariant()
Shortcut for getting the first/default variant belonging to the product.
Product::getCheapestVariant()
Shortcut for getting the lowest-priced variant belonging to the product.
Note that this does not factor in contextual pricing.
Product::getShopifyUrl()
This has limited utility if you are displaying products on-site (rather than linking back to a Shopify storefront). To get the URL of a product within your Craft project, use product.url.
Product::getShopifyEditUrl()
For administrators, you can even link directly to the Shopify admin:
Custom Fields
Products synchronized from Shopify have a dedicated field layout, which means they support Craft’s full array of content tools. In addition, you may place these read-only native fields anywhere in the layout to customize your authoring experience:
- Variants: A static table with variants’ names, SKUs, and prices.
- Options: A list of defined options, their options, and whether any variants exist
- Meta fields: A static table displaying product meta fields as key-value pairs.
- Media: Displays a list of images attached to the product.
The product field layout can be edited by going to Shopify → Settings → Products.
Fields are accessible from any product element, by their handle:
Variants and other nested records do not support custom fields.
Routing
You can give synchronized products their own on-site URLs. To set up the URI format (and the template that will be loaded when a product URL is requested), go to Shopify → Settings → Products. A URI format that emulates Shopify’s default would look something like this:
Any custom field handle, or other base element property can be used in this template to construct a URL.
Product elements’ slugs are automatically synchronized with the handle set in Shopify, so {slug} (as you might use in an entry’s URI format) is equivalent to {handle}.
If you would prefer your customers to view individual products on Shopify, clear out the Product URI Format field on the settings page, and use product.shopifyUrl instead of product.url in your templates.
Product Status
A product’s status in Craft is a combination of its shopifyStatus attribute ('active', 'draft', or 'archived') and its enabled state. The former can only be changed from Shopify; the latter is set in the Craft control panel.
[!NOTE] Statuses in Craft are often a synthesis of multiple properties. For example, an entry with the Pending status just means it is
enabledand has apostDatein the future.
In most cases, you’ll only want to display “Live” products, or those which are Active in Shopify and Enabled in Craft:
| Status | Shopify | Craft |
|---|---|---|
live |
Active | Enabled |
shopifyDraft |
Draft | Enabled |
shopifyArchived |
Archived | Enabled |
disabled |
Any | Disabled |
This is the default behavior when querying for products, but you can pass one of the custom Status options above to the .status() param to override it.
Querying Products
Products can be queried like any other element type in Craft.
A new query begins with the craft.shopifyProducts factory function:
The plugin automatically loads the relevant product when its Product fields also return product queries.
Query Parameters
The following element query parameters are supported, in addition to Craft’s standard set.
[!NOTE] Fields stored as JSON (like field layout.
shopifyId
Filter by legacy numeric Shopify product IDs.
shopifyGid
Filter by Shopify GIDs.
This is equivalent to .shopifyId(123456789), but may be simpler if you are combining data from client-side queries.
shopifyStatus
Directly query against the product’s status in Shopify.
Use the regular .status() param if you'd prefer to query against the synthesized product status values.
[!WARNING] Note that
.shopifyStatus()does not override conditions applied by the.status()param (including the defaults). You may need to call.status(null)to unset them, or use.status('shopifyDraft'), directly.
handle
Query by the product’s handle, in Shopify.
[!WARNING] This is not a reliable means to fetch a specific product, as the value may change during a synchronization. If you want to store a permanent reference to a product, consider using the Shopify product field to relate it by element ID.
productType
Find products by their “type” in Shopify.
tags
Tags are stored as a JSON array, which may complicate direct comparisons. You may see better results using the .search() param.
options
Options are stored as a JSON array, which may complicate direct comparisons. You may see better results using the .search() param.
vendor
Filter by the vendor information from Shopify.
Eager-loading
Variants (ProductVariants), images (MediaImages), and meta fields (Metafields) attached to product elements are not elements themselves, and must be explicitly eager-loaded to avoid performance issues when displaying data in a loop:
[!TIP] The shorthand
.withAll()is a future-proof means of eager-loading each additional type of nested record.
You can still access product.variants, product.images, and product.metafields without eager-loading—but it may result in an additional query for each kind of content. Once you’ve retrieved variants, for example, they are memoized on the product element instance for the duration of the request.
GraphQL
Product elements are also exposed via Craft’s GraphQL API.
You can fetch products (and any content added via custom fields) using the shopifyProducts() query:
Products can be retrieved one at a time with the shopifyProduct (singular) query.
You might use this in a headless front-end to resolve a product by its slug, based on your routing:
It’s important to note that the GraphQL schema is not the same as directly accessing the Shopify API, and that the built-in documentation only reflects what is accessible via Craft. You’ll encounter many familiar objects, but only a subset of the types and fields are available. Only the data retrieved during synchronization (plus your custom fields and other native element properties) will be present in the API.
Arguments are also radically different: Shopify’s filtering is primarily accomplished via the single, generic query param.
Craft uses dedicated field names and types for argument inputs, so the above single- and multi-product queries accept any combination of criteria, including references to custom fields.
[!TIP] Use the GraphiQL IDE in Craft’s control panel to explore the self-documenting API!
Extending
GraphQL is inherently strictly “typed,” which means that the arbitrary shape of Products’ data attribute is not selectable or navigable.
If you wish to expose additional fields you have synchronized from the API, they must be added as Craft builds the product element schema:
As long as you keep your “resolver” in sync with your additional selections, you should be able to pass through those values.
The same strategy can be used to decorate other built-in types for images, metafields, options, and variants.
You can alter multiple types in a single EVENT_DEFINE_GQL_TYPE_FIELDS handler:
[!WARNING] The fields we’ve added are not dynamically fetched from Shopify at runtime! This method just exposes additional fields that have already been synchronized from Shopify.
Templating
Product Data
Products behave just like any other element, in Twig. Once you’ve loaded a product via a custom field data.
[!NOTE] Some attributes are stored as JSON, which limits nested properties’s types. As a result, dates may be slightly more difficult to work with.
Variants and Pricing
Products don’t have a price, despite what the Shopify UI might imply—instead, every product has at least one Variant.
You can get an array (or, more accurately, a collection) of variant objects for a product by accessing product.variants or calling cheapest variants.
- Variants are represented by a model (
craft\shopify\models\Variant), not an element. - Their native attributes reflect most of what is available via their corresponding API object; additional fields may be available within their
dataattribute. - Like products, a
metafieldsattribute provides access to additional store-defined data;
Once you have a reference to a variant, you can output any of its properties:
[!NOTE] The
currencyfilter is provided by Craft (not the Shopify plugin). You must pass a three-digit ISO 4217 code to properly format a currency value.
Contextual Pricing
If you are using the contextualPricingCountries setting to sync market- or currency-specific prices from the API, you may need to reach for the appropriate amount and currencyCode within the variant’s raw data.
Both the price and compareAtPrice are available for each country, under a key following this format:
Each country’s object retains the shape described in the API:
It’s up to you how markets are mapped to sites. Our original pricing output example might be made dynamic, like this:
Using Options
Options are Shopify’s way of distinguishing variants in multiple dimensions. When you add product options, Shopify typically creates a variant for each combination of their possible values.
If you want to let customers pick from options instead of directly select from a list of variants, you will need to resolve which variant a given combination of options points to.