New Dev Dashboard flow (post-Jan 2026) — Fill in your values and they populate every command.
As of January 1, 2026, Shopify deprecated legacy custom apps. You can no longer create apps that show a static shpat_ token to copy-paste. The new Dev Dashboard gives you a Client ID and Client Secret which you exchange programmatically for a short-lived access token (24hr expiry).
Go to your store admin. Fill in your store name below — it populates every URL and command:
Click Develop apps
Click the Build apps in Dev Dashboard button (yellow banner at top)
This opens the Shopify Dev Dashboard at dev.shopify.com
Click Create an app
Give it a name (e.g. my-migration-tool)
You'll land on a Create version page
App URL: Leave as https://example.com — doesn't matter for API-only use.
Scopes: Click Select scopes or paste directly into the Scopes text box:
read_products,write_products,read_metaobjects,write_metaobjects,read_metaobject_definitions,write_metaobject_definitions,read_files,write_files,read_inventory,write_inventory,read_themes,write_themes,read_online_store_pages,write_online_store_pages,read_content,write_content
app_not_installed or shop_not_permitted.Click the Release button (bottom-right).
After release, look for an Install option
Confirm the permissions dialog and click Install
The app opens an embedded view showing example.com — ignore it, close the tab
Go back to the Dev Dashboard: dev.shopify.com
Open your app → Settings
Copy the Client ID (long hex string) and enter it below:
Reveal and copy the Client Secret (starts with shpss_) and enter it below:
shpss_ value is your Client Secret, not a Storefront token. It's only used to request a token.Exchange your credentials for a short-lived access token. This replaces the old copy-paste shpat_ flow.
{
"access_token": "f85632530bf277ec9ac6...",
"scope": "read_products,write_products,...",
"expires_in": 86399
}The access_token is your Admin API token. Use it in the X-Shopify-Access-Token header. Expires in 24 hours.
For scripts that run repeatedly, use a shared auth module that fetches and caches the token automatically. Both implementations below cache the token in memory and refresh on the next call after expiry. Pick whichever matches your project — they're functionally identical.
import 'dotenv/config';
import { URLSearchParams } from 'node:url';
const SHOP = process.env.SHOPIFY_SHOP;
const CLIENT_ID = process.env.SHOPIFY_CLIENT_ID;
const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET;
let token = null;
let tokenExpiresAt = 0;
export async function getToken() {
if (token && Date.now() < tokenExpiresAt - 60_000) return token;
const response = await fetch(
`https://${SHOP}.myshopify.com/admin/oauth/access_token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
}
);
const { access_token, expires_in } = await response.json();
token = access_token;
tokenExpiresAt = Date.now() + expires_in * 1000;
return token;
}import { getToken } from './auth.js';
const token = await getToken();
// Use in X-Shopify-Access-Token header{
"name": "shopify-tooling",
"type": "module",
"private": true,
"dependencies": {
"dotenv": "^16.4.0"
}
}Run npm install in your project folder, then node test-auth.js to verify.
import json
import os
import time
import urllib.request
from urllib.parse import urlencode
from dotenv import load_dotenv
load_dotenv()
SHOP = os.environ['SHOPIFY_SHOP']
CLIENT_ID = os.environ['SHOPIFY_CLIENT_ID']
CLIENT_SECRET = os.environ['SHOPIFY_CLIENT_SECRET']
_token = None
_expires_at = 0
def get_token():
global _token, _expires_at
if _token and time.time() < _expires_at - 60:
return _token
body = urlencode({
'grant_type': 'client_credentials',
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
}).encode()
req = urllib.request.Request(
f'https://{SHOP}.myshopify.com/admin/oauth/access_token',
data=body,
headers={'Content-Type': 'application/x-www-form-urlencoded'},
)
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
_token = data['access_token']
_expires_at = time.time() + data['expires_in']
return _tokenfrom auth import get_token token = get_token() # Use in X-Shopify-Access-Token header
python-dotenv>=1.0.0
Run pip install -r requirements.txt, then python test_auth.py to verify. Pure stdlib otherwise — works on Windows, macOS, and Linux without changes.
Your .myshopify.com subdomain doesn't match the store the app is installed on. Check Settings → Domains in your store admin for the correct subdomain.
The app and store are in different Shopify organizations. Client credentials only works within the same org.
The shpss_ is your Client Secret, not an access token. You must exchange it programmatically (see Part 3).
Tokens last 24 hours. Use the auto-refreshing auth module (Part 4) or re-run the token request.
shpat_Admin API access token from a Dev Dashboard / public app. Sent in the X-Shopify-Access-Token header.shpca_Admin API access token from a legacy Develop apps custom app. New ones can't be created after Jan 1, 2026; existing tokens still work.shppa_Admin API access token from a legacy private app (long deprecated).shpss_App's Client Secret (Shopify Shared Secret). Used to mint access tokens via client-credentials; never sent as a token itself.shptka_Theme Access password — scoped to write_themes only. Used by Shopify CLI / theme tooling, generated by the Theme Access app.(no prefix)Storefront API token — typically a 32-char hex string. Sent in X-Shopify-Storefront-Access-Token header. Different surface from the Admin API.Client IDPublic app identifier (hex string).X-Shopify-Access-TokenHTTP header for all Admin API requests.grant_type=client_credentialsOAuth grant type — literal string, don't replace it.