App reviews, troubleshooting, and recommendations
My app initially has limited access scopes. When I update the access scopes in the shopify.app.toml file and `.env` file and redeploy, the merchant sees the approval screen when they open my app. When they click on "update," the new access scopes are granted, but the createSession method in CustomSessionStorage is not invoked, so the access scopes for the merchant are not updated in the database. Additionally, the afterAuth hook is only triggered during app installation. Is there any hook, method, or API that is invoked when a merchant's access scopes are updated? This is crucial for me as I store access scopes in the database and need to run setup code or enable features based on the granted access scopes.
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
apiVersion: ApiVersion.April24,
scopes: process.env.SCOPES?.split(","),
appUrl: process.env.SHOPIFY_APP_URL || "",
authPathPrefix: "/auth",
sessionStorage: new CustomSessionStorage(),
distribution: AppDistribution.AppStore,
restResources,
future: {
unstable_newEmbeddedAuthStrategy: true,
},
...(process.env.SHOP_CUSTOM_DOMAIN
? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
: {}),
hooks: {
afterAuth(ctx) {
console.log("afterAuth", ctx);
},
},
isEmbeddedApp: true,
});
Ideally, the afterAuth name should be something `afterInstall` as it is very misleading because it is only invoked during app installation.
Here is my custom session storage class:
import type { SessionStorage } from "@shopify/shopify-app-session-storage";
import prisma from "./db.server";
import { Session } from "@shopify/shopify-api";
export default class CustomSessionStorage implements SessionStorage {
async deleteSession(id: string): Promise<boolean> {
await prisma.session.update({
where: { id: id },
data: { isDeleted: true },
});
return true;
}
async deleteSessions(ids: string[]): Promise<boolean> {
await prisma.session.updateMany({
where: { id: { in: ids } },
data: { isDeleted: true },
});
return true;
}
async findSessionsByShop(shop: string): Promise<Session[]> {
const sessions = await prisma.session.findMany({
where: { shop, isDeleted: false },
});
return sessions.map(
(session) =>
new Session({
id: session.id,
accessToken: session.accessToken,
state: session.state,
shop: session.shop,
isOnline: true,
}),
);
}
async loadSession(id: string): Promise<Session | undefined> {
const session = await prisma.session.findUnique({
where: { id, isDeleted: false },
});
if (!session) return undefined;
console.log(`Session loaded: ${session.id}`);
return new Session({
id: session.id,
accessToken: session.accessToken,
state: session.state,
shop: session.shop,
isOnline: true,
scope: session.scope ? session.scope : undefined,
});
}
async storeSession(session: Session): Promise<boolean> {
console.log(`Storing session: ${session.id}`, session);
await prisma.session.upsert({
where: { id: session.id },
update: {
accessToken: session.accessToken,
scope: session.scope,
isDeleted: false,
state: session.state,
},
create: {
id: session.id,
accessToken: session.accessToken!,
scope: session.scope,
isDeleted: false,
state: session.state,
shop: session.shop,
installedAt: new Date(),
},
});
console.log(`Session stored: ${session.id}`);
return true;
}
}
Here is my scopes
SCOPES=read_metaobject_definitions,read_metaobjects,read_products,unauthenticated_read_metaobjects,unauthenticated_read_product_listings,unauthenticated_read_product_tags,write_metaobject_definitions,write_metaobjects
Part of .toml file
[access_scopes]
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes
scopes = "read_metaobject_definitions,read_metaobjects,read_products,unauthenticated_read_metaobjects,unauthenticated_read_product_listings,unauthenticated_read_product_tags,write_metaobject_definitions,write_metaobjects"
Update
I'm not sure if this is the correct approach, but here’s my current solution:
I’ve added a new column named isScopeUpdated to the Session table. Whenever I deploy the app with updated scopes, I set isScopeUpdated to false for all stores. When fetching the session in loadSession, I only fetch sessions where isScopeUpdated is true. This forces Shopify to re-trigger token generation and fire the afterAuth hook.
Updated CustomSessionStorage
import type { SessionStorage } from "@shopify/shopify-app-session-storage";
import prisma from "./db.server";
import { Session } from "@shopify/shopify-api";
export default class CustomSessionStorage implements SessionStorage {
async deleteSession(id: string): Promise<boolean> {
await prisma.session.update({
where: { id: id },
data: { isDeleted: true },
});
return true;
}
async deleteSessions(ids: string[]): Promise<boolean> {
await prisma.session.updateMany({
where: { id: { in: ids } },
data: { isDeleted: true },
});
return true;
}
async findSessionsByShop(shop: string): Promise<Session[]> {
const sessions = await prisma.session.findMany({
where: { shop, isDeleted: false, isScopeUpdated: true },
});
return sessions.map(
(session) =>
new Session({
id: session.id,
accessToken: session.accessToken,
state: session.state,
shop: session.shop,
isOnline: true,
}),
);
}
async loadSession(id: string): Promise<Session | undefined> {
const session = await prisma.session.findUnique({
where: { id, isDeleted: false, isScopeUpdated: true },
});
if (!session) return undefined;
console.log(`Session loaded: ${session.id}`);
return new Session({
id: session.id,
accessToken: session.accessToken,
state: session.state,
shop: session.shop,
isOnline: true,
scope: session.scope ? session.scope : undefined,
});
}
async storeSession(session: Session): Promise<boolean> {
console.log(`Storing session: ${session.id}`, session);
await prisma.session.upsert({
where: { id: session.id },
update: {
accessToken: session.accessToken,
scope: session.scope,
isDeleted: false,
isScopeUpdated: true,
state: session.state,
},
create: {
id: session.id,
accessToken: session.accessToken!,
scope: session.scope,
isDeleted: false,
state: session.state,
shop: session.shop,
installedAt: new Date(),
isScopeUpdated: true,
},
});
console.log(`Session stored: ${session.id}`);
return true;
}
}
Discover how to increase customer engagement on your store with articles from Shopify A...
By Jacqui Apr 23, 2025Hey Community 👋 Did you know that March 15th is National Everything You Think Is W...
By JasonH Apr 1, 2025Discover how to increase the efficiency of commerce operations with Shopify Academy's l...
By Jacqui Mar 26, 2025