A django app with all the tools required to make a Shopify app
Project description
django-shopify-app
A reusable Django package for building Shopify apps. Handles OAuth, token exchange, webhook management, session token validation (JWT), and Shopify API interactions.
Installation
pip install django-shopify-app
Add the app in settings.py:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'shopify_app',
'shops',
]
Settings
# Required
SHOPIFY_API_KEY = config('SHOPIFY_API_KEY')
SHOPIFY_API_SECRET = config('SHOPIFY_API_SECRET')
SHOPIFY_APP_HOST = 'https://your-app.com'
SHOPIFY_SHOP_MODEL = 'shops.Shop' # Must inherit from ShopBase
SHOPIFY_WEBHOOK_CALLBACK = 'shops.webhooks.webhook_entry'
SHOPIFY_GDPR_WEBHOOK_CALLBACK = 'shops.webhooks.gdpr_webhook_entry'
# Optional
SHOPIFY_APP_SCOPES = ['read_products', 'read_orders']
SHOPIFY_WEBHOOK_TOPICS = ['products/update', 'app/uninstalled']
SHOPIFY_WEBHOOK_HOST = 'https://your-app.com' # Defaults to SHOPIFY_APP_HOST
SHOPIFY_API_VERSION = '2025-04' # Defaults to '2022-04'
# Token exchange (recommended for embedded apps)
SHOPIFY_TOKEN_EXCHANGE = False # Default: False
SHOPIFY_DASHBOARD_PATH = '/dashboard' # Default: '/dashboard'
# Staff bypass
SHOPIFY_STAFF_BYPASS = False # Default: False
SHOPIFY_STAFF_BYPASS_METHODS = None # Default: None (all methods)
SHOPIFY_STAFF_SHOP_ATTR = 'admin_shop' # Default: 'admin_shop'
ShopBase model
Your app must define a Shop model that inherits from ShopBase:
from shopify_app.models import ShopBase
class Shop(ShopBase):
# Add your custom fields
plan_name = models.CharField(max_length=50, default='')
def installed(self, request=None):
"""Called when the app is installed on a shop for the first time."""
self.update_webhooks()
self.crm_on_install()
def on_user_login(self, user_data, request=None):
"""Called when a Shopify user opens the app.
user_data contains: id, first_name, last_name, email,
email_verified, account_owner, locale, collaborator
"""
self.crm_on_login(user_data)
Fields
| Field | Type | Description |
|---|---|---|
shopify_domain |
CharField | The shop's .myshopify.com domain |
shopify_token |
CharField | Offline access token |
access_scopes |
CharField | Granted OAuth scopes |
seen_users |
JSONField | Tracks user logins {user_id: {last_login: ...}} |
shopify_app_api_key_overwrite |
CharField | Per-shop API key override |
shopify_app_api_secret_overwrite |
CharField | Per-shop API secret override |
API methods
All REST methods replace api_version in the path automatically:
# REST API
shop.get('/admin/api/api_version/products.json')
shop.post('/admin/api/api_version/products.json', data={...})
shop.put('/admin/api/api_version/products/123.json', data={...})
shop.patch('/admin/api/api_version/products/123.json', data={...})
shop.delete_request('/admin/api/api_version/products/123.json')
# GraphQL
response = shop.graphql(query='{ shop { name } }')
response = shop.graphql(query=mutation, variables={...}, api_version='2025-04')
# GraphQL via shopify library (uses session context manager)
result = shop.graph(operation_name='GetShop', variables={}, operations_document=query)
Webhook management
shop.update_webhooks() # Deactivate all + reactivate from SHOPIFY_WEBHOOK_TOPICS
shop.activate_webhooks() # Alias for update_webhooks()
shop.deactivate_webhooks() # Remove all registered webhooks
CRM events
Built-in hooks for CRM lifecycle events (via django-crm-events):
shop.crm_on_install()
shop.crm_on_uninstall(users=[])
shop.crm_on_login(user_data)
shop.crm_on_billing_plan_change(plan_price)
Other properties
shop.shopify_app_api_key # Returns overwrite or global setting
shop.shopify_app_api_secret # Returns overwrite or global setting
shop.api_version # From SHOPIFY_API_VERSION setting
shop.host # Base64-encoded admin URL for App Bridge
shop.get_shop_data() # Fetch shop details from Shopify REST API
HMAC validation
shop.app_proxy_request_is_valid(request) # Validate app proxy HMAC
shop.request_shopify_hmac_is_valid(request) # Validate standard Shopify HMAC
Authorization
The package supports two authorization flows: token exchange (recommended for embedded apps) and authorization code grant (legacy / non-embedded apps).
Token exchange (recommended)
Token exchange eliminates OAuth redirects. The backend exchanges the session token from App Bridge for an access token via a server-side POST to Shopify. No page reloads or flicker.
Scopes are managed via shopify.app.toml and deployed with Shopify CLI (shopify app deploy). Shopify handles installation and scope updates automatically.
Add to settings.py:
SHOPIFY_TOKEN_EXCHANGE = True # Enable token exchange
SHOPIFY_DASHBOARD_PATH = '/dashboard' # Where to redirect from app root
Set up your URLs:
from django.urls import path, include
from shopify_app.views import AppRootView
urlpatterns = [
path('', AppRootView.as_view()),
path('shopify/', include('shopify_app.urls')),
# your dashboard urls...
]
When a merchant opens your app, AppRootView redirects to the dashboard. The first API request from the dashboard triggers token exchange automatically via ShopSessionMixin / shop_session, storing the access token for subsequent requests.
User login tracking is handled automatically: on the first request from a new user (or after 24 hours), an online token exchange fetches user details and fires shop.on_user_login().
Authorization code grant (legacy)
For non-embedded apps or apps that don't use Shopify managed installation.
from django.urls import path
from shopify_app.views import AppRootView, EndTokenRequestView
app_name = 'my_shopify_app'
urlpatterns = [
path(
'',
AppRootView.as_view(
redirect_path_name='my_shopify_app:end-token-request',
),
),
path(
'confirm/',
EndTokenRequestView.as_view(
redirect_path_name='embed_admin:dashboard',
),
name='end-token-request'
),
]
With SHOPIFY_TOKEN_EXCHANGE = False (default), AppRootView falls back to the OAuth authorization code grant flow.
Webhook URLs
Include the package URLs for webhook and GDPR endpoints:
from django.urls import path, include
urlpatterns = [
path('shopify/', include('shopify_app.urls')),
]
This registers:
shopify/webhooks— Main webhook receivershopify/gdpr-webhooks/customer-data-request— GDPR customer data requestshopify/gdpr-webhooks/customer-data-erasure— GDPR customer data erasureshopify/gdpr-webhooks/shop-data-erasure— GDPR shop data erasure
ShopSessionMixin
A mixin that authenticates requests against a valid Shopify shop session (JWT). Use it with any APIView or DRF generic view:
from rest_framework.views import APIView
from shopify_app.mixins import ShopSessionMixin
class MyView(ShopSessionMixin, APIView):
def get(self, request, *args, **kwargs):
shop = request.shop
...
Staff bypass
Staff users can skip Shopify JWT validation if they have a shop associated with their user model. Enable it globally in settings:
SHOPIFY_STAFF_BYPASS = True # Default: False
SHOPIFY_STAFF_BYPASS_METHODS = ['GET', 'HEAD', 'OPTIONS'] # Default: None (all methods)
SHOPIFY_STAFF_SHOP_ATTR = 'admin_shop' # Default: 'admin_shop'
SHOPIFY_STAFF_BYPASS_METHODS restricts which HTTP methods are allowed through the bypass. When None (default), all methods are allowed. When set, unlisted methods (e.g. POST, PUT, DELETE) will require Shopify JWT validation even for staff users.
Or per-view:
class MyView(ShopSessionMixin, APIView):
allow_staff_bypass = True # Overrides the global setting
When enabled, if the request user is authenticated, is staff, and has a truthy value on the configured attribute (admin_shop by default), the mixin sets request.shop from that attribute and skips JWT validation.
Decorators
@shop_session
Validates the Shopify session token (JWT) from the request header. Injects shop, shopify_domain, and shopify_user_id into the view kwargs:
from shopify_app.decorators import shop_session
@shop_session
def my_view(request, *args, **kwargs):
shop = kwargs['shop']
user_id = kwargs['shopify_user_id']
@shopify_embed
Adds Content-Security-Policy frame-ancestors header for embedded app views:
from shopify_app.decorators import shopify_embed
@shopify_embed
def my_view(request, **kwargs):
...
@known_shop_required
Validates that a shop query parameter is present and the shop exists in the database. Returns 401 if invalid:
from shopify_app.decorators import known_shop_required
@known_shop_required
def my_view(request, *args, **kwargs):
shop = kwargs['shop']
@app_proxy_view
Validates HMAC for Shopify app proxy requests:
from shopify_app.decorators import app_proxy_view
@app_proxy_view
def my_proxy_view(request, *args, **kwargs):
shop = kwargs['shop']
@latest_access_scopes_required
Checks if the shop's stored scopes match the configured scopes. Sets scope_changes_required=True in kwargs if they differ:
from shopify_app.decorators import latest_access_scopes_required
@shop_session
@latest_access_scopes_required
def my_view(request, *args, **kwargs):
if kwargs.get('scope_changes_required'):
# handle scope update
Utilities
from shopify_app.utils import (
get_shop_model, # Resolve shop model from SHOPIFY_SHOP_MODEL setting
get_shop, # Get shop by domain (raises DoesNotExist)
get_auth_shop, # Get shop or unsaved instance for auth lookup
webhook_request_is_valid, # Validate webhook HMAC
liquid_render, # Render template with Liquid content-type
app_proxy_url_builder, # Build full URL for app proxy requests
app_proxy_redirect, # Redirect to app proxy URL
)
Management commands
# Activate webhooks for a shop by subdomain
python manage.py activate_shop_webhooks <shopify_subdomain>
# e.g. activate_shop_webhooks example-store → targets example-store.myshopify.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_shopify_app-2.2.4.tar.gz.
File metadata
- Download URL: django_shopify_app-2.2.4.tar.gz
- Upload date:
- Size: 17.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65fd1bb61c62ad2f4a64065eace552e2e1bb57074068e7589d0bd7c903617896
|
|
| MD5 |
0ad3f48bce6894c4549f765a21560b36
|
|
| BLAKE2b-256 |
6a8cfe9f81e17d0ccaef1697576446838ec6073750a63351faae3f92ccb142d4
|
File details
Details for the file django_shopify_app-2.2.4-py3-none-any.whl.
File metadata
- Download URL: django_shopify_app-2.2.4-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
145a33f040a719016fbcf4d8e7c29b087e1e06ef0795003dc7dd8a869fcb80e8
|
|
| MD5 |
e6feb4935c030bc58861fcad5f0d1fb5
|
|
| BLAKE2b-256 |
e5b892cbbb3f8e58efd481ba3e8d5bac43d9ce06825b2236b23bcd26ff45d6c1
|