I’m building a Shopify app using the Remix template (@shopify/shopify-app-remix). I have a Flow Action trigger that needs to make a GraphQL call to the Shopify Admin API from the backend action handler to fetch the shop’s email address (shop { email }).
The Flow action is triggered correctly, and I can authenticate the request using shopify.authenticate.flow(request). I can also successfully retrieve the shop’s offline session (which includes the accessToken) from my Prisma database using the sessionStorage provided by the library.
But I’m struggling to find the right way to instantiate an authenticated GraphQL client using this offline session to make the Admin API call.
Context:
Framework: Remix (using Shopify Remix App template)
Library: @Shopify_77 /shopify-app-remix (mention version if known, e.g., v3.x) / @Shopify_77 /shopify-api (mention version if known, e.g., v11.x)
Goal: Inside a Flow Action handler (a Remix action function), fetch the shop’s email using an offline token.
shopify.server.js Config:
// app/shopify.server.js (relevant parts)
import "@shopify/shopify-app-remix/adapters/node";
import {
ApiVersion,
AppDistribution,
shopifyApp,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "./db.server";
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
apiVersion: ApiVersion.January25, // Or your version
scopes: process.env.SCOPES?.split(","), // Includes 'read_shop'
appUrl: process.env.SHOPIFY_APP_URL || "",
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
future: {
unstable_newEmbeddedAuthStrategy: true,
removeRest: false, // Currently set to false for testing, but also failed with true
},
// ...
});
export default shopify;
export const authenticate = shopify.authenticate;
export const sessionStorage = shopify.sessionStorage;
// ...
Prisma Session Model:
// prisma/schema.prisma
model Session {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
firstName String?
lastName String?
email String?
accountOwner Boolean?
locale String?
collaborator Boolean?
emailVerified Boolean?
}
Problem:
Inside my Flow action handler (app/routes/api.flow.action.send-email.jsx), after successfully getting the shopDomain and fetching the offlineSession using sessionStorage.findSessionsByShop(shopDomain), I cannot create a working GraphQL client.
Attempts Made & Errors:
// Inside the action function, after getting shopDomain and offlineSession
// Method 1: Standard path via shopify.api.clients
try {
const client = new shopify.api.clients.Graphql({ session: offlineSession });
// FAILED with: TypeError: Cannot read properties of undefined (reading 'clients')
} catch (e) { console.error("Method 1 Failed", e); }
// Method 2: Using shopify.admin (doesn't seem correct for offline)
try {
const adminContext = await shopify.admin({ session: offlineSession });
// FAILED with: TypeError: shopify.admin is not a function
} catch (e) { console.error("Method 2 Failed", e); }
// Method 3: Direct import of GraphqlClient (only session)
try {
// import { GraphqlClient } from "@shopify/shopify-api";
const client = new GraphqlClient({ session: offlineSession });
// FAILED with: TypeError: Cannot read properties of undefined (reading 'isCustomStoreApp')
} catch (e) { console.error("Method 3 Failed", e); }
// Method 4: Direct import + specific config from main shopify object
try {
// import { GraphqlClient } from "@shopify/shopify-api";
// import shopify, { apiVersion } from "../shopify.server";
const client = new GraphqlClient({
config: shopify.config, // Also tried passing specific props like apiVersion, hostName etc.
session: offlineSession
});
// FAILED with: TypeError: Cannot read properties of undefined (reading 'isCustomStoreApp' or 'hostName')
} catch (e) { console.error("Method 4 Failed", e); }
// Method 5: Using admin client from authenticate.flow context
// const flowContext = await shopify.authenticate.flow(request);
// if (flowContext && flowContext.admin && typeof flowContext.admin.graphql === 'function') {
// const response = await flowContext.admin.graphql(SHOP_EMAIL_QUERY);
// // FAILED with: 401 Unauthorized (suggests this client isn't using the correct offline token)
// }
Question:
What is the correct way to instantiate or obtain an authenticated Shopify Admin API GraphQL client within a Remix backend route (like a Flow action handler), using an offline session token retrieved from sessionStorage, especially considering the potential impact of the removeRest: true future flag (even if currently disabled)?
I can successfully make the shop { email } query using the GraphiQL tool provided by shopify app dev, which confirms the offline session and read_shop scope are valid. I just can’t get the client working in the action handler code.
Any help or examples would be greatly appreciated!