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

Ready to learn Shopify development?Short, interactive lessons where you write real Liquid against a live storefront and watch it change as you type. Free, and genuinely fun.

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:

Python
import osfrom shopify_app import ShopifyApp shopify = ShopifyApp(    client_id=os.getenv("SHOPIFY_API_KEY"),    client_secret=os.getenv("SHOPIFY_API_SECRET"),)
PHP
<?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_req for the embedded app home, verify_app_proxy_req for storefront requests through an App Proxy, and verify_webhook_req for 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_exchange and exchange_using_client_credentials for getting access tokens, plus refresh_token_exchanged_access_token which decides for itself whether a refresh is needed.
  • admin_graphql_request for 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'tDo insteadWhy it matters
Parse the shop out of the ID token or request yourselfresult.shopIt is already parsed per request type and verified against spoofing. The shop decides which store's token gets used.
Build your own token-refresh pageapp_home_patch_id_tokenCustom pages that render request values are a common injection risk.
Render App Home without the response headersCopy result.response.headers onto your responseThose are the required iframe-protection headers, including CSP frame-ancestors.
Craft your own response when verification failsReturn result.response as-isIt already has the correct status and security headers.
Alter headers, query string or body before verifyingPass the raw request through unchangedVerification depends on the exact bytes Shopify sent.
Put the ID token in the URL to keep navigation authenticatedWire the token-refresh routeSame-origin and full-page navigations arrive without a session token. The route lets App Bridge re-request one.
Shopify's own "common misuses to avoid". None of these is language-specific.
Shopify writing down what goes wrong in real apps. Five of the six are reachable in any language, including the JavaScript stack.

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 — free

Should 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.

Start your first lesson

Free · No credit card · Your first win in minutes

app-developmentPlatformsecurity

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