Skip to main content

Admin
object

Contains methods for authenticating and interacting with the Admin API.

This function can handle requests for apps embedded in the Admin, Admin extensions, or non-embedded apps.

Authenticates requests coming from the Shopify admin.

The shape of the returned object changes depending on the isEmbeddedApp config.

Request
required

Promise<<Config, Resources>>
Was this section helpful?

Authenticate, run API mutation, and redirect

/app/routes/**.ts

import {type ActionFunctionArgs, json} from '@remix-run/node';
import {GraphqlQueryError} from '@shopify/shopify-api';

import {authenticate} from '../shopify.server';

export const action = async ({request}: ActionFunctionArgs) => {
const {admin, redirect} = await authenticate.admin(request);

try {
await admin.graphql(
`#graphql
mutation updateProductTitle($input: ProductInput!) {
productUpdate(input: $input) {
product {
id
}
}
}`,
{
variables: {
input: {id: '123', title: 'New title'},
},
},
);

return redirect('/app/product-updated');
} catch (error) {
if (error instanceof GraphqlQueryError) {
return json({errors: error.body?.errors}, {status: 500});
}

return new Response('Failed to update product title', {status: 500});
}
};

Anchor to example-setting-cors-headers-for-a-admin-requestSetting CORS headers for a admin request

Use the cors helper to ensure your app can respond to requests from admin extensions.

Was this section helpful?

Setting CORS headers for a admin request

/app/routes/admin/my-route.ts

import { LoaderFunctionArgs, json } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import { getMyAppData } from "~/db/model.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const { session, cors } = await authenticate.admin(request);
return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));
};

Use the redirect helper to safely redirect between pages.

Anchor to example-redirecting-to-a-page-in-the-shopify-adminRedirecting to a page in the Shopify Admin

Redirects to a product page in the Shopify admin. Pass in a target option of _top or _parent to navigate in the current window, or _blank to open a new tab.

Anchor to example-redirecting-outside-of-the-admin-embedded-app-pageRedirecting outside of the Admin embedded app page

Pass in a target option of _top or _parent to navigate in the current window, or _blank to open a new tab.

Was this section helpful?

Redirecting to an app route

/app/routes/admin/my-route.ts

import { LoaderFunctionArgs, json } from "@remix-run/node";
import { authenticate } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const { session, redirect } = await authenticate.admin(request);
return redirect("/");
};

Get your app's shop-specific data using an offline session.

Get your app's user-specific data using an online session.

Was this section helpful?

Using offline sessions

import { LoaderFunctionArgs, json } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import { getMyAppData } from "~/db/model.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const { session } = await authenticate.admin(request);
return json(await getMyAppData({shop: session.shop));
};

Get user-specific data using the sessionToken object.

Was this section helpful?

Using the decoded session token

import { LoaderFunctionArgs, json } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import { getMyAppData } from "~/db/model.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const { sessionToken } = await authenticate.admin(
request
);
return json(await getMyAppData({user: sessionToken.sub}));
};

Use admin.graphql to make query / mutation requests.

Catch GraphqlQueryError errors to see error messages from the API.

Was this section helpful?

Querying the GraphQL API

import { ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";

export const action = async ({ request }: ActionFunctionArgs) => {
const { admin } = await authenticate.admin(request);

const response = await admin.graphql(
`#graphql
mutation populateProduct($input: ProductInput!) {
productCreate(input: $input) {
product {
id
}
}
}`,
{
variables: {
input: { title: "Product Name" },
},
},
);

const productData = await response.json();
return json({
productId: productData.data?.productCreate?.product?.id,
});
}

Use the billing.cancel function to cancel an active subscription with the id returned from billing.require.

Was this section helpful?

Cancelling a subscription

import { LoaderFunctionArgs } from "@remix-run/node";
import { authenticate, MONTHLY_PLAN } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const { billing } = await authenticate.admin(request);
const billingCheck = await billing.require({
plans: [MONTHLY_PLAN],
onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),
});

const subscription = billingCheck.appSubscriptions[0];
const cancelledSubscription = await billing.cancel({
subscriptionId: subscription.id,
isTest: true,
prorate: true,
});

// App logic
};

Anchor to example-check-what-billing-plans-a-merchant-is-subscribed-toCheck what billing plans a merchant is subscribed to

Use billing.check if you want to determine which plans are in use. Unlike require, check does notthrow an error if no active billing plans are present.

Anchor to example-check-for-payments-without-filteringCheck for payments without filtering

Use billing.check to see if any payments exist for the store, regardless of whether it's a test ormatches one or more plans.

Was this section helpful?

Check what billing plans a merchant is subscribed to

import { LoaderFunctionArgs } from "@remix-run/node";
import { authenticate, MONTHLY_PLAN } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const { billing } = await authenticate.admin(request);
const { hasActivePayment, appSubscriptions } = await billing.check({
plans: [MONTHLY_PLAN],
isTest: false,
});
console.log(hasActivePayment);
console.log(appSubscriptions);
};

Create a usage record for the active usage billing plan

Was this section helpful?

Creating a usage record

import { ActionFunctionArgs } from "@remix-run/node";
import { authenticate, MONTHLY_PLAN } from "../shopify.server";

export const action = async ({ request }: ActionFunctionArgs) => {
const { billing } = await authenticate.admin(request);

const chargeBilling = await billing.createUsageRecord({
description: "Usage record for product creation",
price: {
amount: 1,
currencyCode: "USD",
},
isTest: true,
});
console.log(chargeBilling);

// App logic
};

Change where the merchant is returned to after approving the purchase using the returnUrl option.

Customize the plan for a merchant when requesting billing. Any fields from the plan can be overridden, as long as the billing interval for line items matches the config.

Was this section helpful?

Using a custom return URL

import { LoaderFunctionArgs } from "@remix-run/node";
import { authenticate, MONTHLY_PLAN } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const { billing } = await authenticate.admin(request);
await billing.require({
plans: [MONTHLY_PLAN],
onFailure: async () => billing.request({
plan: MONTHLY_PLAN,
isTest: true,
returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',
}),
});

// App logic
};

Call billing.request in the onFailure callback to immediately redirect to the Shopify page to request payment.

When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.

Was this section helpful?

Requesting billing right away

import { LoaderFunctionArgs } from "@remix-run/node";
import { authenticate, MONTHLY_PLAN } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const { billing } = await authenticate.admin(request);
await billing.require({
plans: [MONTHLY_PLAN],
isTest: true,
onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),
});

// App logic
};

Anchor to example-updating-the-capped-amount-for-a-usage-billing-planUpdating the capped amount for a usage billing plan

Update the capped amount for the usage billing plan specified by subscriptionLineItemId.

Was this section helpful?

Updating the capped amount for a usage billing plan

import { ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";

export const action = async ({ request }: ActionFunctionArgs) => {
const { billing } = await authenticate.admin(request);

await billing.updateUsageCappedAmount({
subscriptionLineItemId: "gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1",
cappedAmount: {
amount: 10,
currencyCode: "USD"
},
});

// App logic
};

Call scopes.query to get scope details.

Was this section helpful?

Query for granted scopes

/app._index.tsx

import type { LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { authenticate } from "../shopify.server";
import { json } from "@remix-run/node";

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

const scopesDetail = await scopes.query();

return json({
hasWriteProducts: scopesDetail.granted.includes('write_products'),
});
};

export default function Index() {
const {hasWriteProducts} = useLoaderData<typeof loader>();

...
}

Was this section helpful?

Request consent from the merchant to grant the provided scopes for this app

/app/routes/app.request.tsx

import type { ActionFunctionArgs } from "@remix-run/node";
import { useFetcher } from "@remix-run/react";
import { authenticate } from "../shopify.server";
import { json } from "@remix-run/node";

// Example of an action to POST a request to for optional scopes
export const action = async ({ request }: ActionFunctionArgs) => {
const { scopes } = await authenticate.admin(request);

const body = await request.formData();
const scopesToRequest = body.getAll("scopes") as string[];

// If the scopes are not already granted, a full page redirect to the request URL occurs
await scopes.request(scopesToRequest);
// otherwise return an empty response
return json({});
};

export default function Index() {
const fetcher = useFetcher<typeof action>();

const handleRequest = () => {
fetcher.submit({scopes: ["write_products"]}, {
method: "POST",
});
};

...
}

Call scopes.revoke to revoke optional scopes.

Was this section helpful?

Revoke optional scopes

/app._index.tsx

import type { ActionFunctionArgs } from "@remix-run/node";
import { useFetcher } from "@remix-run/react";
import { authenticate } from "../shopify.server";
import { json } from "@remix-run/node";

// Example of an action to POST optional scopes to revoke
export const action = async ({ request }: ActionFunctionArgs) => {
const { scopes } = await authenticate.admin(request);

const body = await request.formData();
const scopesToRevoke = body.getAll("scopes") as string[];

const revokedResponse = await scopes.revoke(scopesToRevoke);

return json(revokedResponse);
};

export default function Index() {
const fetcher = useFetcher<typeof action>();

const handleRevoke = () => {
fetcher.submit({scopes: ["write_products"]}, {
method: "POST",
});
};

...
}