How to call the Admin API with GraphQL from App Frontend Index()? (Pagination)

Topic summary

Core Issue:
Developer building a Shopify Remix app needs to implement pagination for collections fetched via GraphQL Admin API. Initial data loads successfully in the loader() function (first 25-100 collections), but pagination breaks when trying to fetch subsequent pages from frontend click handlers.

Technical Problem:

  • The admin.graphql client is only accessible within the server-side loader() function through shopify.authenticate.admin(request)
  • Attempting to store the admin client in a global variable (gadmin) and call it from frontend onNext() function fails with “gadmin.graphql is not a function”
  • Apollo Client attempts were unsuccessful

Proposed Solution:
One participant suggests using query parameters to trigger server-side pagination:

  • Add cursor parameters (e.g., ?from=<endCursor>) to the URL
  • Extract these parameters in the loader() function using regex
  • Pass the cursor to GraphQL query’s after parameter
  • Use GET requests to reload the page with new parameters

Status:
Partial solution identified but developer expresses doubt about whether this URL parameter approach is the correct pattern. The discussion remains open regarding best practices for client-side pagination with Shopify’s Remix/GraphQL architecture.

Summarized with AI on November 13. AI used: claude-sonnet-4-5-20250929.

Hi there,

I’m developing a new custom App (Remix, GraphQL) for my shop. I’m using the Admin API to get the Collections and Publications via GraphQL - On App load I’m getting the first 25 or lets say 100 collections, based on which limit I set to the query. So far so good. I’m also added a pagination to my table like this one:


When I’m clicking on next, the onNext() function is called. And in that function I want to fetch the next 25 or 100 results, but I can’t access the GraphQL client anymore.

I tried to use the Apollo Client, but without success .

That here is my loader function:

export  async function loader({ request }) {
  const { admin, session } = await shopify.authenticate.admin(request)

  gadmin = admin

  const publications = await getPublications()
  let onlineShop = {}

  console.log(publications)
  publications.map(({ id, name }) => {
    if (name === 'Online Store') {
      onlineShop = {
        id,
        name
      }
    }
  }) || [];

  const collections = await getCollections(onlineShop.id, 1, null);
  console.log(collections)
  // const collections = {}

  return json({
    request,
    onlineShop,
    collections,
  });
}

That works on app load and I’m getting the results in my Index() to show them in a table.

In my onNext() function I’m trying this one:

async function onNext(){
    const {test} = await getPublications()
    console.log(test)
  }

and this is my getPublications():

async function getPublications() {

  const publicationResponse = await gadmin.graphql(GET_PUBLICATIONS)

  const {
    data: {
      publications: { nodes }
    }
  } = await publicationResponse.json();

  return nodes;
}

But when I’m clicking onto the pagination next button in the App admin frontend, I’m getting the following error message:

app._index.jsx:64 Uncaught (in promise) TypeError: gadmin.graphql is not a function
    at getPublications (app._index.jsx:64:44)
    at onNext (app._index.jsx:164:15)
    at HTMLUnknownElement.callCallback2 (react-dom.development.js:4164:14)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:16)
    at invokeGuardedCallback (react-dom.development.js:4277:31)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:25)
    at executeDispatch (react-dom.development.js:9041:3)
    at processDispatchQueueItemsInOrder (react-dom.development.js:9073:7)
    at processDispatchQueue (react-dom.development.js:9086:5)
    at dispatchEventsForPlugins (react-dom.development.js:9097:3)
getPublications @ app._index.jsx:64
onNext @ app._index.jsx:164
callCallback2 @ react-dom.development.js:4164
invokeGuardedCallbackDev @ react-dom.development.js:4213
invokeGuardedCallback @ react-dom.development.js:4277
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:4291
executeDispatch @ react-dom.development.js:9041
processDispatchQueueItemsInOrder @ react-dom.development.js:9073
processDispatchQueue @ react-dom.development.js:9086
dispatchEventsForPlugins @ react-dom.development.js:9097
(anonymous) @ react-dom.development.js:9288
batchedUpdates$1 @ react-dom.development.js:26140
batchedUpdates @ react-dom.development.js:3991
dispatchEventForPluginEventSystem @ react-dom.development.js:9287
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay @ react-dom.development.js:6465
dispatchEvent @ react-dom.development.js:6457
dispatchDiscreteEvent @ react-dom.development.js:6430
await in dispatchDiscreteEvent (async)
callCallback2 @ react-dom.development.js:4164
invokeGuardedCallbackDev @ react-dom.development.js:4213
invokeGuardedCallback @ react-dom.development.js:4277
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:4291
executeDispatch @ react-dom.development.js:9041
processDispatchQueueItemsInOrder @ react-dom.development.js:9073
processDispatchQueue @ react-dom.development.js:9086
dispatchEventsForPlugins @ react-dom.development.js:9097
(anonymous) @ react-dom.development.js:9288
batchedUpdates$1 @ react-dom.development.js:26140
batchedUpdates @ react-dom.development.js:3991
dispatchEventForPluginEventSystem @ react-dom.development.js:9287
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay @ react-dom.development.js:6465
dispatchEvent @ react-dom.development.js:6457
dispatchDiscreteEvent @ react-dom.development.js:6430

I think, that the problem is that I’m calling the Admin API from the Apps Frontend and maybe it’s not allowed, based on security issues, which makes sense to me. But how can I call it?

Any ideas, how I can solve this problem? The pagination is a required feature when working with shopify apps, but there are no working solutions or examples. And it’s not only a pagination problem -

Thanks, Jochen :wink:

I’m facing the same issue. I get the first N records but I don’t know how to get the next N. I tried the same you did with a global admin variable but doesn’t work. Have you figured out how to do this? or how to “call loader again” with the endCursor value?

I think I found a way. You have to GET the page again using query parameters. Something like this:

export const loader = async ({ request }) => {
  const { admin } = await authenticate.admin(request);

  const url = request.url;
  let from = null; 
  const regex = /\?.*?\bfrom=([^&]+)/;
  const match = url.match(regex);
  
  if (match) {
    from = match[1];
    console.log("from:", from);
  } else {
    console.log("from parameter not found");
  }
  const query = `
    query Customers($limit: Int, $from: String, $includeMetafields: Boolean!){
      customers(first:$limit, after:$from) {
        edges {
          node {
            id
            email
            firstName
            lastName
            metafields (first:10) @include(if: $includeMetafields) {
              metafield: nodes {
                  key
                  value
                  namespace
              }
            }
          }
        }
        pageInfo {
          hasPreviousPage
          hasNextPage
          startCursor
          endCursor
        }
      }
    }
  `;

  let variables = {
    "limit": 2,
    "includeMetafields": true
  }
  if (from) {
    variables.from = from;
  }  
  
  const response = await admin.graphql(query, {variables});
  const responseJson = await response.json();
  return responseJson.data;
}

But I doubt this is the right way!