How do I make an admin api call from react?

I am trying to figure out what I am doing wrong here.

According to this direct api access I should be able to make a fetch call to https://${my?.shop}/admin/api/2026-01/graphql.json

However, I get no response from what I have laid out here:

import prisma from "app/db.server";
import { LoaderFunctionArgs } from "react-router";

export const loader = async ({ request }: LoaderFunctionArgs) => {
    const url = new URL(request.url);
    const key = url.searchParams.get('key') ?? ''
  
    const prismaSession = await prisma.session.findFirst()
    const smsVerification = await prisma.verification.findFirst({
        where: {
            key
        }
    })

    const smsUpdate = await prisma.verification.update({
        where: {
            id: smsVerification?.id
        },
        data: {
            complete: true
        }
    })

    console.log(smsUpdate)
    console.log(prismaSession?.shop)
    
    const verificationUpdate = await fetch(`https://${prismaSession?.shop}/admin/api/2026-01/graphql.json`, {
        method: 'POST',
        body: JSON.stringify({
            query: `
                mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
                    metafieldsSet(metafields: $metafields) {
                        metafields {
                            key
                            namespace
                            value
                            createdAt
                            updatedAt
                        }
                        userErrors {
                            field
                            message
                            code
                        }
                    }
                }`,
            variables : {
                "metafields": [
                    {
                        "key": "sms_verification",
                        "namespace": "custom",
                        "ownerId": smsUpdate.order,
                        "type": "boolean",
                        "value": "true",
                    }
                ]
            }
        })
    })

    console.log('graphQL:')
    const { data } = await verificationUpdate.json()
    console.log(data)

    return new Response()
};


I don’t know what or how to call a response correctly?

You’re trying to make a server-side Admin API call from your Remix loader, which is the right place for it. The App Bridge direct API access documentation you linked is for client-side calls directly from a React component running in the browser, where App Bridge handles injecting the authentication automatically.

For a server-side call like this, you need to explicitly include the access token in your request headers. Your prismaSession object should contain the accessToken that was granted during the OAuth flow.

Modify your fetch call to include the X-Shopify-Access-Token header:

    const verificationUpdate = await fetch(`https://${prismaSession?.shop}/admin/api/2026-01/graphql.json`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-Shopify-Access-Token': prismaSession?.accessToken // Make sure your session object has this
        },
        body: JSON.stringify({
            query: `
                mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
                    metafieldsSet(metafields: $metafields) {
                        metafields {
                            key
                            namespace
                            value
                            createdAt
                            updatedAt
                        }
                        userErrors {
                            field
                            message
                            code
                        }
                    }
                }`,
            variables : {
                "metafields": [
                    {
                        "key": "sms_verification",
                        "namespace": "custom",
                        "ownerId": `gid://shopify/Order/${smsUpdate.order}`, // This needs to be a GID
                        "type": "boolean",
                        "value": "true",
                    }
                ]
            }
        })
    })

Also, for setting metafields, the ownerId needs to be a Global ID (GID), not just a plain ID. So, if smsUpdate.order is a numeric ID, you’ll need to prefix it like gid://shopify/Order/${smsUpdate.order}.

Hope that helps!

Hi @Fred_Blueshoon

You are missing authentication headers and using the wrong ID format. The Shopify Admin API requires an Access Token and Global IDs (GIDs).

Try this out:

// 1. Ensure you have the token
if (!prismaSession?.accessToken) throw new Error("No access token found");

const verificationUpdate = await fetch(`https://${prismaSession.shop}/admin/api/2026-01/graphql.json`, {
    method: 'POST',
    headers: {
        "Content-Type": "application/json",
        "X-Shopify-Access-Token": prismaSession.accessToken // <--- CRITICAL FIX
    },
    body: JSON.stringify({
        query: `mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
            metafieldsSet(metafields: $metafields) {
                userErrors { field message }
            }
        }`,
        variables: {
            "metafields": [{
                "key": "sms_verification",
                "namespace": "custom",
                "ownerId": `gid://shopify/Order/${smsUpdate.order}`, // <--- CRITICAL FIX: Must be a GID
                "type": "boolean",
                "value": "true"
            }]
        }
    })
});

// 2. Check response properly
const responseJson = await verificationUpdate.json();
if (!verificationUpdate.ok || responseJson.data?.metafieldsSet?.userErrors?.length > 0) {
    console.error("Error:", responseJson);
} else {
    console.log("Success:", responseJson.data);
}```

Or alternatively, since you are using the Remix template, it is much safer to use the built-in client which handles headers automatically:

Or alternatively, since you are using the Remix template, it is much safer to use the built-in client which handles headers automatically:

import { shopify } from "../shopify.server";
// ...
const client = new shopify.clients.Graphql({ session: prismaSession });
const response = await client.request(query, { variables });