Create Shopify App with Python/Django

In this post, we'll cover how to create a public Shopify app using the Django web framework.
The main purpose of this article is to provide you with the general concept about creating a Shopify app. Before we start, tt would be better if you have a basic understanding of Django and DRF. Also, you can find the source code at the end of this post where you can explore the project or start your own Shopify app easily with this starter pack.
How does it work?
Creating a Shopify app is really simple since Shopify allows to display the original app inside an iframe. So, you don't need to create any specific environment for the development of the Shopify app.
However, Shopify requires to use its own Oauth while installing the app to validate the user and also asks for store permissions such as "read and write" access for products and categories, depending on what the app will utilize. At this point, we only need to set up OAuth flow to authenticate users from Shopify and allow interaction with our Django app.
If your app includes in-app purchases then you have to process payments through Shopify billing. For more information please check the official documentation.
Create Shopify App
To create a Shopify app, you have to register (free) from Shopify Partners where you'll be able to set up your stores, apps and other related functionalities.
First, you need to create a development store. I would highly recommend watching the video below about how to set up a development store in Shopify which will guide your entire process:
Once you set up the development store, click "Apps" in Partners Dashboard and click "Create app". Next, choose the type of app which is "Public app".
It will toggle a form element under the card which expects the name of the app, main URL, and list of redirections that will be used while OAuth process.
- App name -
testapp
- App URL - It's the main URL of our app where Shopify redirects once the user clicks the "install" button. We're going to create a specific endpoint that will receive requests from Shopify for the authorization process.
http://127.0.0.1:8000/account/oauth/shopify
- Redirection URLs - Comma separated URLs that will be whitelisted by Shopify to use while OAuth flow. There will be a few redirections to complete the authentication process.
http://127.0.0.1:8000/account/oauth/shopify
http://127.0.0.1:8000/account/oauth/shopify/authorize
After you set up the items above, click the "Create app" button to get API credentials that will be used for interacting with Shopify API.
OAuth Flow

Now we have our initial app, so it's time to explore the OAuth process. The image above was taken from official documentation which demonstrates the OAuth flow of Shopify.
In total, we'll have 2 main endpoints in our Django app for the entire OAuth flow:
oauth/shopify/
- Validates the installation request and redirects to grant screen.
oauth/shopify/authorize/
- Creates a new user by using fetched data from Shopify API.
Let's break it down by implementing functionalities to understand each step in detail.
User installs app
This is the starting point where the user clicks the "install" button to make the first request for installation. Shopify will redirect to a specific URL (App URL - http://127.0.0.1:8000/account/oauth/shopify
) provided in settings of the app by you which is the actual endpoint to receive installation calls.
You can test it by selecting a development store from the "Test your app" section which locates inside the app settings page.

The installation will fail because we don't have any endpoints to handle incoming requests yet. You can test your app from this panel while developing the app.
Next, let's see what Shopify sends to the endpoint when a user installs the app:
GET '/account/oauth/shopify/?hmac=5480c2b47f7636fbceec315a34572831a9ea2e82a2257c7362361f639385cdf0&shop=codepylotdev.myshopify.com×tamp=1642957638'
It sends GET
request to our App URL
with extra query parameters that will be used in our app to authorize the request.
hmac
- the hashed output generated by Shopify by signing with API secret key (provided in app settings) with the message which isshop=codepylotdev.myshopify.com×tamp=1642957638
. We'll verify the signature with the API secret key to validate the request.shop
- the name of your shop.timestamp
- the created time of the request in Unix format.
Now let's start to implement this step by creating a new view and serializer inside account
app:
account/views.py
from django.http.response import HttpResponseRedirect
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from account.serializers import ShopifyOauthSerializer, ShopifyUserCreationSerializer
from account.utils.constants import ShopifyOauth
from account.utils.oauth_client import ShopifyOauthClient
class ShopifyOauthRedirectAPIView(GenericAPIView):
"""Redirects to Shopify to confirm permissions
"""
permission_classes = [AllowAny]
serializer_class = ShopifyOauthSerializer
def get(self, request):
serializer = self.get_serializer(data=request.query_params)
if serializer.is_valid(raise_exception=True):
oauth_client = ShopifyOauthClient(shop_name=request.query_params['shop'])
redirect_url = oauth_client.build_oauth_redirect_url(request.build_absolute_uri())
return HttpResponseRedirect(redirect_to=redirect_url)
We're processing request data with serializers and then validated data is going to be passed to ShopifyOauthClient
to build authorization redirect URL.
account/serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
from account.utils.constants import ShopifyOauth
from account.utils.helpers import search_string_match, verify_hash_signature
class ShopifyOauthSerializer(serializers.Serializer):
code = serializers.CharField(required=False)
hmac = serializers.CharField(required=True)
host = serializers.CharField(required=False)
shop = serializers.CharField(required=True)
timestamp = serializers.CharField(required=True)
def check_signature(self, attrs):
secret = ShopifyOauth.SECRET_KEY
if attrs.get('code'):
msg = (f"code={attrs['code']}&host={attrs['host']}"
f"&shop={attrs['shop']}×tamp={attrs['timestamp']}")
else:
msg = f"shop={attrs['shop']}×tamp={attrs['timestamp']}"
is_verified = verify_hash_signature(secret, msg, attrs['hmac'])
if not is_verified:
raise serializers.DjangoValidationError(
{'signature': ["Signature is not valid"]}
)
return attrs
def validate_shop_url(self, shop_url):
shop_name_regex = search_string_match(r'[^.\s]+\.myshopify\.com', shop_url)
if shop_name_regex != shop_url:
raise serializers.DjangoValidationError(
{'shop_name': ["Shop name does not end with 'myshopify.com'"]}
)
return shop_url
def validate(self, attrs):
self.check_signature(attrs)
return attrs
First, we are checking if the hashed signature ( hmac
) is valid by building the msg
from query params and using the result with SHA-256
algorithm for verification.
Then we also need to validate the shop name to make sure it ends with .myshopify.com
Redirecting to Grant Screen
In the first step, we successfully validated the request call from Shopify by checking the signature and shop name provided in query params.
Now, we're going to build a redirection URL to show the user OAuth grant screen with a list of permissions. Consider the structure and definition of the redirection URL below which is taken from documentation:
https://{shop}.myshopify.com/admin/oauth/authorize?client_id={api_key}&scope={scopes}&redirect_uri={redirect_uri}&state={nonce}&grant_options[]={access_mode}
Let's break it down:
- shop - The name of the merchant’s shop.
- api_key - The API key for the app.
- scopes - A comma-separated list of scopes. For example, to write orders and read customers, use
scope=write_orders,read_customers
. Any permission to write a resource includes the permission to read it. - redirect_uri - The URL to which a merchant is redirected after authorizing the app. The complete URL specified here must be added to your app as an allowed redirection URL, as defined in the Partner Dashboard.
- nonce - A randomly selected value provided by your app that is unique for each authorization request. During the OAuth callback, your app must check that this value matches the one you provided during authorization. This mechanism is important for the security of your app.
- access_mode - Sets the access mode. For online access mode, set to per-user. For offline access mode, set to value. If no access mode is defined, then it defaults to offline access mode.
If you want to make your app even more secure, then you should add a unique state or nonce as an extra query parameter to make sure redirections are not interrupted by malicious users.
You can use uuid
module to generate unique hex strings and write them to DB (NoSQL preferred). Try to create it while checking the validation of the installation request and then confirm the existence in the second redirection which is for authorization (after the user grants permissions). This step is not required but adds an extra security layer.
Now, let's remember what we have passed to ShopifyOauthClient
class after getting validated data from the serializer.
oauth_client = ShopifyOauthClient(shop_name=request.query_params['shop'])
redirect_url = oauth_client.build_oauth_redirect_url(request.build_absolute_uri())
ShopifyOauthClient
is initialized with shop_name
from validated data.
Let's take a look to the ShopifyOauthClient
class to understand how redirection URL is built and also consider other functions since we'll refer to them later in this post:
account/utils/oauth_client.py
import requests
from account.utils.constants import ShopifyOauth
from account.utils.helpers import get_host_url, response_to_dictionary
class ShopifyOauthClient:
def __init__(self, shop_name: str, access_token: str = ""):
self.shop_name = shop_name
self.access_token = access_token
def get_access_token(self, **data):
res = requests.post(
f"https://{self.shop_name}{ShopifyOauth.ACCESS_TOKEN_ENDPOINT}",
data,
timeout=60,
verify=False
)
self.access_token = response_to_dictionary(res.text)['access_token']
return self.access_token
def get_shop_details(self):
res = requests.get(
f"https://{self.shop_name}{ShopifyOauth.SHOP_DETAILS_ENDPOINT}",
headers={ShopifyOauth.ACCESS_TOKEN_HEADER: self.access_token}
)
shop_data = response_to_dictionary(res.text)['shop']
return shop_data['email'], shop_data['shop_owner']
def build_oauth_redirect_url(self, absolute_url):
host = get_host_url(absolute_url)
redirect_url = (
f"https://{self.shop_name}{ShopifyOauth.AUTHORIZE_ENDPOINT}?"
f"client_id={ShopifyOauth.API_KEY}&scope={ShopifyOauth.SCOPES}"
f"&redirect_uri={host}{ShopifyOauth.REDIRECT_ENDPOINT}"
f"&grant_options[]={ShopifyOauth.ACCESS_MODE}"
)
return redirect_url
In the constructor, we are initializing two main fields of class which are shop_name
and access_token
that will be used to interact with Shopify API.
build_oauth_redirect_url
builds redirection URL by taking pre-defined values from constants
file below:
account/utils/constants.py
from django.conf import settings
class ShopifyOauth:
API_KEY = settings.SHOPIFY_PUBLIC_APP_KEY
SECRET_KEY = settings.SHOPIFY_PUBLIC_APP_SECRET_KEY
SCOPES = (
"read_orders,write_orders,read_customers,write_customers,"
"read_products,write_products,read_content,write_content,"
"read_price_rules,write_price_rules,read_themes,write_themes"
)
ACCESS_MODE = "per_user"
REDIRECT_ENDPOINT = "/account/oauth/shopify/authorize"
ACCESS_TOKEN_HEADER = "X-Shopify-Access-Token"
ACCESS_TOKEN_ENDPOINT = "/admin/oauth/access_token"
AUTHORIZE_ENDPOINT = "/admin/oauth/authorize"
SHOP_DETAILS_ENDPOINT = f"/admin/api/2021-04/shop.json"
SHOPIFY_API_VERSION = "2021-04"
Once redirect URL is built, we are redirecting the user to grant screen from views:
account/views.py
return HttpResponseRedirect(redirect_to=redirect_url)
The grant screen will look like below:
At the end of the page, you should click "Install app" to continue the process.
Get a permanent access token
After the user grants permissions, Shopify makes another redirection to a URL which we assigned to redirect_uri
:
&redirect_uri={host}{ShopifyOauth.REDIRECT_ENDPOINT}
Consider the structure that concatenates host
and value of ShopifyOauth.REDIRECT_ENDPOINT
constant which produces the absolute URL of the authorization endpoint of our Django app. Since our app is running localhost the full URL will be like below:
http://127.0.0.1:8000/oauth/shopify/authorize
Now we're allowed to get merchants data by using Shopify API calls. Let's add another view to handle the authorization & user creation process:
account/views.py
class ShopifyUserCreationAPIView(GenericAPIView):
"""Creates new user for Shopify Public App
"""
permission_classes = [AllowAny]
serializer_class = ShopifyOauthSerializer
def get(self, request):
serializer = self.get_serializer(data=request.query_params)
if serializer.is_valid(raise_exception=True):
shop_name = serializer.validated_data['shop']
oauth_client = ShopifyOauthClient(shop_name)
token = oauth_client.get_access_token(
client_id=ShopifyOauth.API_KEY,
client_secret=ShopifyOauth.SECRET_KEY,
code=serializer.validated_data['code']
)
email, owner = oauth_client.get_shop_details()
serializer = ShopifyUserCreationSerializer(
data={
"email": email,
"full_name": owner,
"shop_name": shop_name,
'token': token,
'state': request.query_params.get("state")
}
)
if serializer.is_valid(raise_exception=True):
user = serializer.create(validated_data=serializer.validated_data)
bridge_url = f"https://{shop_name}/admin/apps/testapp"
return HttpResponseRedirect(redirect_to=bridge_url)
return Response({"message": "Authentication failed"})
The serializer class is same since we have to check the signature for each request from Shopify. In this time, the msg
will include new parameter named code
that was sent after user grants access so we have to include it for hash verification. It will be used to get access token
from Shopify.
So, we're initializing our ShopifyOauthClient
to get permanent access token
from the Shopify's endpoint below:
POST https://{shop}.myshopify.com/admin/oauth/access_token
In our request, {shop}
is the name of the merchant's shop and the following parameters must be provided in the request body:
- client_id - The API key for the app, as defined in the Partner Dashboard.
- client_secret - The API secret key for the app, as defined in the Partner Dashboard.
- code - The authorization code provided in the redirect.
So, we're passing the required data to get_access_token
function as shown above and let's take a look inside the function to see the rest of the process:
account/utils/oauth_client.py
def get_access_token(self, **data):
res = requests.post(
f"https://{self.shop_name}{ShopifyOauth.ACCESS_TOKEN_ENDPOINT}",
data,
timeout=60,
verify=False
)
self.access_token = response_to_dictionary(res.text)['access_token']
return self.access_token
After getting access token
from Shopify, we're ready to pass it as a header for interacting with the API.
Creating new user
The next step is getting the shop details of a merchant with get_shop_details
function. The response data will include the email and the full name of the owner where we can use it to create a new user.
Then these values are passed to the serializer to create a new user like below:
class ShopifyUserCreationSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
full_name = serializers.CharField(required=True, max_length=255)
shop_name = serializers.CharField(required=True, max_length=255)
token = serializers.CharField(required=True, max_length=255)
def create(self, validated_data):
user, _created = User.objects.get_or_create(
email=validated_data['email'],
)
if _created:
user.username = validated_data['email']
password = User.objects.make_random_password(length=10)
user.is_active = True
user.set_password(password)
user.first_name = validated_data['full_name']
user.save()
return user
Note that, you should also store access token
in DB since it's a permanent token and will be used in each request with Shopify API. For now, we're only creating a new user with validated data. Feel free to add your changes in case you have any extra data to be saved.
What's next?
We learned how to set up Shopify OAuth to create a new user with access token
that will be used for interaction with Shopify API. Now here is the list of what you should do after these steps:
- Make sure you're saving
access token
for the created user. - After the user is created, the final redirection should point to Shopify to display the embedded app.
https://{shop}.myshopify.com/admin/apps/{app_name}
. - Django should generate a JWT token for the created users to allow requests from the front-end side (React). You can create it inside
ShopifyUserCreationSerializer
. - Read about Shopify Bridge to see how you can make your app embedded.
- You'll need to run Ngrok to make your localhost publicly available so the app can show up inside Shopify.
Also, check the source code from the repository below:
Support 🌏
If you feel like you unlocked new skills, please share them with your friends and subscribe to the youtube channel to not miss any valuable information.