How to make Shopify Admin API GraphQL call using offline session in Remix Flow Action handler?

Topic summary

A developer is building a Shopify app using the Remix template and needs to make a GraphQL call to the Admin API from a Flow Action handler to fetch the shop’s email address.

Current Status:

  • Flow action triggers successfully
  • Can authenticate requests using shopify.authenticate.flow(request)
  • Successfully retrieves offline session (with accessToken) from Prisma database
  • Query works in GraphiQL tool, confirming valid offline session and read_shop scope

The Problem:
Unable to instantiate an authenticated GraphQL client using the offline session token in the action handler code.

Attempted Methods (all failed):

  1. Creating new GraphQL client with new shopify.clients.Graphql() - resulted in errors
  2. Using shopify.unauthenticated.admin() - failed authentication
  3. Manual REST client with offline token - encountered issues
  4. Various client instantiation approaches - unsuccessful
  5. Using flowContext.admin.graphql - returned 401 Unauthorized

Environment:

  • Framework: Remix with Shopify Remix App template
  • Libraries: @shopify/shopify-app-remix, @shopify/shopify-api
  • Session storage: Prisma
  • API Version: January25

The developer is seeking guidance on the correct approach to use an offline session token for GraphQL queries in this specific context.

Summarized with AI on October 28. AI used: claude-sonnet-4-5-20250929.

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!