App development
Shopify shipped official PHP and Python app packages, and a list of the mistakes you were making
New first-party packages for building Shopify apps in PHP and Python. The interesting part isn't the languages, it's the 'common misuses' table shipped with them: Shopify documenting the auth mistakes it sees most.
Bas Lefeber
Founder, learnshopify.dev · August 29, 2026 · 4 min read
Shopify's app tooling has been JavaScript-first for years, with Ruby along for the ride. If you worked in PHP or Python you got a thin API client and were left to implement session tokens, request verification, and token exchange yourself. On August 26 that changed: both languages now have official, GA, first-party packages.
The headline is a fair bit less interesting than what is inside them. These packages ship an explicit "common misuses to avoid" table, which is Shopify writing down the security mistakes it sees most often in real apps. That list is worth reading whatever language you build in, because most of the entries are mistakes you can make just as easily in Remix.
What shipped
composer require shopify/shopify-app-php (PHP 8.2+) and pip install shopifyapp (Python 3.8+). Both are GA at 1.0 and framework-agnostic: Laravel, Symfony or vanilla PHP; Django, Flask or FastAPI. The older shopify-api-php and shopify_python_api libraries are now deprecated, though existing apps keep working and there is no forced migration.
One naming gotcha
The Python package is shopifyapp on PyPI but the module you import is shopify_app. So it is pip install shopifyapp then from shopify_app import ShopifyApp. Not a typo, and it will cost someone an afternoon.
What they actually give you
Initialisation is two credentials the Shopify CLI already provides:
import osfrom shopify_app import ShopifyApp shopify = ShopifyApp( client_id=os.getenv("SHOPIFY_API_KEY"), client_secret=os.getenv("SHOPIFY_API_SECRET"),)<?php use Shopify\App\ShopifyApp; $shopify = new ShopifyApp( clientId: getenv('SHOPIFY_API_KEY'), clientSecret: getenv('SHOPIFY_API_SECRET'),);From there you get one verification function per surface, rather than a single generic one you have to configure correctly:
verify_app_home_reqfor the embedded app home,verify_app_proxy_reqfor storefront requests through an App Proxy, andverify_webhook_reqfor webhooks.- One each for the extension surfaces:
verify_admin_ui_ext_req,verify_checkout_ui_ext_req,verify_customer_account_ui_ext_req,verify_pos_ui_ext_req,verify_flow_action_req. exchange_using_token_exchangeandexchange_using_client_credentialsfor getting access tokens, plusrefresh_token_exchanged_access_tokenwhich decides for itself whether a refresh is needed.admin_graphql_requestfor Admin API calls, with retry handling built in.
Splitting verification per surface is the right call. The surfaces genuinely differ in what they sign and how, and a single verify() with a mode flag is exactly the shape that leads to someone verifying a webhook as though it were an App Bridge request.
The part worth reading in any language
Here is the table, paraphrased from the package README. Read it as a list of the mistakes Shopify sees most often, because that is what it is.
| Don't | Do instead | Why it matters |
|---|---|---|
| Parse the shop out of the ID token or request yourself | result.shop | It is already parsed per request type and verified against spoofing. The shop decides which store's token gets used. |
| Build your own token-refresh page | app_home_patch_id_token | Custom pages that render request values are a common injection risk. |
| Render App Home without the response headers | Copy result.response.headers onto your response | Those are the required iframe-protection headers, including CSP frame-ancestors. |
| Craft your own response when verification fails | Return result.response as-is | It already has the correct status and security headers. |
| Alter headers, query string or body before verifying | Pass the raw request through unchanged | Verification depends on the exact bytes Shopify sent. |
| Put the ID token in the URL to keep navigation authenticated | Wire the token-refresh route | Same-origin and full-page navigations arrive without a session token. The route lets App Bridge re-request one. |
The last row is the one that catches good developers
Putting the ID token in the URL is usually not laziness, it is someone solving a real problem: a same-origin link or full-page navigation arrives without a session token, so the obvious fixes are to smuggle the token in the querystring or to give up and build a single-page app. The package's answer is a dedicated token-refresh route that App Bridge can bounce through, which means multi-page apps work without tokens in URLs. If you rewrote an app as an SPA purely to keep auth working, that was the wrong forced choice.
One design decision worth noticing
The two packages deliberately share an API. Shopify's own stated reasoning is that this "creates some interesting constraints, and sacrifices some idioms," but means a fix in one community benefits the other.
That is an unusual trade to make on purpose, and it explains a few things that will feel slightly un-Pythonic or un-PHPish when you use them. It is also why the Python package asks you to convert your framework's request into a plain dict rather than accepting a Django or FastAPI request directly: the shared surface cannot know about your framework. Slightly more boilerplate, one integration path to document, and no risk of the Django adapter and the Flask adapter drifting apart on something security-critical.
The packages are also explicit about scope: they target what most apps need most of the time, and skip less common cases such as non-embedded apps. Worth checking against your architecture before you commit.
Learn this properly · free lesson
App vs theme vs Function: which tool for which job
Before the language question comes the harder one: should this be an app at all, or a theme change, or a Function? Work through the tradeoffs on a real build. Free lesson, no signup.
Try this lesson — freeShould you use them?
Starting something new in PHP or Python: yes. The old libraries are deprecated, and hand-rolling session-token verification is exactly the kind of security-critical work you should not be doing yourself.
Running an existing app on the old libraries: no rush. Nothing is being switched off and the new packages are designed for incremental adoption, one route at a time. That is a real contrast with the script tag deprecation, which has hard dates and no opt-out. Migrate this at your own pace; do that one on Shopify's schedule.
Building in JavaScript: read the misuses table anyway. It costs two minutes and most of it applies to you. And if your app is mostly a frontend over Shopify's own data, a Static App may remove the question entirely by removing the server.
The wider read: Shopify is steadily converting tribal knowledge into shipped defaults. Events replaced payload-diffing, the analytics platform replaced everyone's home-grown dashboard stack, and now request verification stops being something each app reimplements slightly differently. The work that remains is the judgment about what to build, which is the part that was always worth your time.
Frequently asked questions
How do I install the official Shopify app package for Python?
Run pip install shopifyapp. Note that the PyPI package is named shopifyapp but the module you import is shopify_app, so initialisation looks like: from shopify_app import ShopifyApp. It requires Python 3.8 or later and works with Django, Flask and FastAPI.
How do I install the official Shopify app package for PHP?
Run composer require shopify/shopify-app-php. It requires PHP 8.2 or later and is framework-agnostic, working with Laravel, Symfony or vanilla PHP. Initialise it with new ShopifyApp(clientId: ..., clientSecret: ...) using the credentials the Shopify CLI provides.
Are shopify-api-php and shopify_python_api deprecated?
Yes. Both older libraries are deprecated in favour of the new first-party packages, which reached GA at version 1.0 in August 2026. Existing apps continue to work and there is no forced migration, and the new packages are explicitly designed for incremental adoption one route at a time.
What do the Shopify PHP and Python packages actually handle?
Request verification with a separate function per surface (app home, app proxy, webhooks, and the admin, checkout, customer account, POS and Flow extension surfaces), token exchange and client credentials including refresh, and an Admin GraphQL client with automatic retry handling. They also provide helpers for the App Bridge token-refresh page and for redirecting inside or outside the App Home iframe.
Can I use Django or Laravel with the official Shopify packages?
Yes. Both packages are deliberately framework-agnostic: the PHP one works with Laravel, Symfony or vanilla PHP, and the Python one with Django, Flask or FastAPI. Rather than shipping per-framework adapters, they ask you to convert your framework's request object into a plain structure, which keeps one integration path for a security-critical code path.
Why do the Shopify PHP and Python packages share the same API?
It is a deliberate design decision. Shopify's stated reasoning is that sharing an API across the two packages creates constraints and sacrifices some language idioms, but means a fix or improvement in one community benefits the other. It is also why the packages avoid framework-specific integrations that could drift apart.
Start free
Ready to become a Shopify developer?
You just read how it works. Now write it yourself: real tickets from a live store, in an editor where the storefront updates as you type. Module 1 is free, no card.
Free · No credit card · Your first win in minutes
About the author
Bas Lefeber, Founder, learnshopify.dev
Bas builds learnshopify.dev, where developers learn production-grade Shopify theme development against a live storefront. He writes about Liquid, theme architecture, and the parts of the job that still matter now that AI writes the code.
Keep going in the curriculum
