--- title: Admin description: >- Contains functions 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. api_version: v2 api_name: shopify-app-remix source_url: html: 'https://shopify.dev/docs/api/shopify-app-remix/v2/authenticate/admin' md: 'https://shopify.dev/docs/api/shopify-app-remix/v2/authenticate/admin.md' --- # Admin Contains functions 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. ## authenticate.​admin(**[request](#authenticateadmin-propertydetail-request)**​) Authenticates requests coming from the Shopify admin. The shape of the returned object changes depending on the `isEmbeddedApp` config. ### Parameters * **request** **Request** **required** ### Returns * **Promise\>** ### AdminContext ```ts Config['isEmbeddedApp'] extends false ? NonEmbeddedAdminContext : EmbeddedAdminContext ``` ### NonEmbeddedAdminContext * session The session for the user who made the request. This comes from the session storage which \`shopifyApp\` uses to store sessions in your database of choice. Use this to get shop or user-specific data. ```ts Session ``` * admin Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request. ```ts AdminApiContext ``` * billing Billing methods for this store, based on the plans defined in the \`billing\` config option. ```ts BillingContext ``` * cors A function that ensures the CORS headers are set correctly for the response. ```ts EnsureCORSFunction ``` ### Session Stores App information from logged in merchants so they can make authenticated requests to the Admin API. * id The unique identifier for the session. ```ts string ``` * shop The Shopify shop domain, such as \`example.myshopify.com\`. ```ts string ``` * state The state of the session. Used for the OAuth authentication code flow. ```ts string ``` * isOnline Whether the access token in the session is online or offline. ```ts boolean ``` * scope The desired scopes for the access token, at the time the session was created. ```ts string ``` * expires The date the access token expires. ```ts Date ``` * accessToken The access token for the session. ```ts string ``` * onlineAccessInfo Information on the user for the session. Only present for online sessions. ```ts OnlineAccessInfo ``` * isActive Whether the session is active. Active sessions have an access token that is not expired, and has the given scopes. ```ts (scopes: string | string[] | AuthScopes) => boolean ``` * isScopeChanged Whether the access token has the given scopes. ```ts (scopes: string | string[] | AuthScopes) => boolean ``` * isExpired Whether the access token is expired. ```ts (withinMillisecondsOfExpiry?: number) => boolean ``` * toObject Converts an object with data into a Session. ```ts () => SessionParams ``` * equals Checks whether the given session is equal to this session. ```ts (other: Session) => boolean ``` * toPropertyArray Converts the session into an array of key-value pairs. ```ts (returnUserData?: boolean) => [string, string | number | boolean][] ``` ### OnlineAccessInfo * expires\_in How long the access token is valid for, in seconds. ```ts number ``` * associated\_user\_scope The effective set of scopes for the session. ```ts string ``` * associated\_user The user associated with the access token. ```ts OnlineAccessUser ``` ### OnlineAccessUser * id The user's ID. ```ts number ``` * first\_name The user's first name. ```ts string ``` * last\_name The user's last name. ```ts string ``` * email The user's email address. ```ts string ``` * email\_verified Whether the user has verified their email address. ```ts boolean ``` * account\_owner Whether the user is the account owner. ```ts boolean ``` * locale The user's locale. ```ts string ``` * collaborator Whether the user is a collaborator. ```ts boolean ``` ### AuthScopes A class that represents a set of access token scopes. * has Checks whether the current set of scopes includes the given one. ```ts (scope: string | string[] | AuthScopes) => boolean ``` * equals Checks whether the current set of scopes equals the given one. ```ts (otherScopes: string | string[] | AuthScopes) => boolean ``` * toString Returns a comma-separated string with the current set of scopes. ```ts () => string ``` * toArray Returns an array with the current set of scopes. ```ts () => any[] ``` ### SessionParams * \[key: string] ```ts any ``` * id The unique identifier for the session. ```ts string ``` * shop The Shopify shop domain. ```ts string ``` * state The state of the session. Used for the OAuth authentication code flow. ```ts string ``` * isOnline Whether the access token in the session is online or offline. ```ts boolean ``` * scope The scopes for the access token. ```ts string ``` * expires The date the access token expires. ```ts Date ``` * accessToken The access token for the session. ```ts string ``` * onlineAccessInfo Information on the user for the session. Only present for online sessions. ```ts OnlineAccessInfo | StoredOnlineAccessInfo ``` ### StoredOnlineAccessInfo ```ts Omit & { associated_user: Partial; } ``` ### AdminApiContext * rest Methods for interacting with the Shopify Admin REST API There are methods for interacting with individual REST resources. You can also make \`GET\`, \`POST\`, \`PUT\` and \`DELETE\` requests should the REST resources not meet your needs. ```ts RestClientWithResources ``` * graphql Methods for interacting with the Shopify Admin GraphQL API ```ts GraphQLClient ``` ### RestClientWithResources ```ts RemixRestClient & {resources: Resources} ``` ### RemixRestClient * session ```ts Session ``` * get Performs a GET request on the given path. ```ts (params: GetRequestParams) => Promise ``` * post Performs a POST request on the given path. ```ts (params: PostRequestParams) => Promise ``` * put Performs a PUT request on the given path. ```ts (params: PostRequestParams) => Promise ``` * delete Performs a DELETE request on the given path. ```ts (params: GetRequestParams) => Promise ``` ### GetRequestParams * path The path to the resource, relative to the API version root. ```ts string ``` * type The type of data expected in the response. ```ts DataType ``` * data The request body. ```ts string | Record ``` * query Query parameters to be sent with the request. ```ts SearchParams ``` * extraHeaders Additional headers to be sent with the request. ```ts HeaderParams ``` * tries The maximum number of times the request can be made if it fails with a throttling or server error. ```ts number ``` ### DataType * JSON ```ts application/json ``` * GraphQL ```ts application/graphql ``` * URLEncoded ```ts application/x-www-form-urlencoded ``` ### HeaderParams Headers to be sent with the request. ### PostRequestParams ```ts GetRequestParams & { data: Record | string; } ``` ### GraphQLClient * query ```ts Operation extends keyof Operations ``` * options ```ts GraphQLQueryOptions ``` interface Promise\ { /\*\* \* Attaches callbacks for the resolution and/or rejection of the Promise. \* @param onfulfilled The callback to execute when the Promise is resolved. \* @param onrejected The callback to execute when the Promise is rejected. \* @returns A Promise for the completion of which ever callback is executed. \*/ then\(onfulfilled?: ((value: T) => TResult1 | PromiseLike\) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike\) | undefined | null): Promise\; /\*\* \* Attaches a callback for only the rejection of the Promise. \* @param onrejected The callback to execute when the Promise is rejected. \* @returns A Promise for the completion of the callback. \*/ catch\(onrejected?: ((reason: any) => TResult | PromiseLike\) | undefined | null): Promise\; }, interface Promise\ {}, Promise: PromiseConstructor, interface Promise\ { readonly \[Symbol.toStringTag]: string; }, interface Promise\ { /\*\* \* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The \* resolved value cannot be modified from the callback. \* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). \* @returns A Promise for the completion of the callback. \*/ finally(onfinally?: (() => void) | undefined | null): Promise\; } ```ts interface Promise { /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */ then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise; /** * Attaches a callback for only the rejection of the Promise. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of the callback. */ catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise; }, interface Promise {}, Promise: PromiseConstructor, interface Promise { readonly [Symbol.toStringTag]: string; }, interface Promise { /** * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The * resolved value cannot be modified from the callback. * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). * @returns A Promise for the completion of the callback. */ finally(onfinally?: (() => void) | undefined | null): Promise; } ``` ### GraphQLQueryOptions * variables The variables to pass to the operation. ```ts ApiClientRequestOptions["variables"] ``` * apiVersion The version of the API to use for the request. ```ts ApiVersion ``` * headers Additional headers to include in the request. ```ts Record ``` * tries The total number of times to try the request if it fails. ```ts number ``` ### ApiVersion * October22 ```ts 2022-10 ``` * January23 ```ts 2023-01 ``` * April23 ```ts 2023-04 ``` * July23 ```ts 2023-07 ``` * October23 ```ts 2023-10 ``` * January24 ```ts 2024-01 ``` * April24 ```ts 2024-04 ``` * Unstable ```ts unstable ``` ### BillingContext * require Checks if the shop has an active payment for any plan defined in the \`billing\` config option. ```ts (options: RequireBillingOptions) => Promise ``` * check Checks if the shop has an active payment for any plan defined in the \`billing\` config option. ```ts (options: CheckBillingOptions) => Promise ``` * request Requests payment for the plan. ```ts (options: RequestBillingOptions) => Promise ``` * cancel Cancels an ongoing subscription, given its ID. ```ts (options: CancelBillingOptions) => Promise ``` ### RequireBillingOptions * plans The plans to check for. Must be one of the values defined in the \`billing\` config option. ```ts (keyof Config["billing"])[] ``` * onFailure How to handle the request if the shop doesn't have an active payment for any plan. ```ts (error: any) => Promise ``` * isTest Whether to consider test purchases. ```ts boolean ``` ### BillingCheckResponseObject * hasActivePayment Whether the user has an active payment method. ```ts boolean ``` * oneTimePurchases The one-time purchases the shop has. ```ts OneTimePurchase[] ``` * appSubscriptions The active subscriptions the shop has. ```ts AppSubscription[] ``` ### OneTimePurchase * id The ID of the one-time purchase. ```ts string ``` * name The name of the purchased plan. ```ts string ``` * test Whether this is a test purchase. ```ts boolean ``` * status The status of the one-time purchase. ```ts string ``` ### AppSubscription * id The ID of the app subscription. ```ts string ``` * name The name of the purchased plan. ```ts string ``` * test Whether this is a test subscription. ```ts boolean ``` * lineItems ```ts ActiveSubscriptionLineItem[] ``` ### ActiveSubscriptionLineItem * id ```ts string ``` * plan ```ts AppPlan ``` ### AppPlan * pricingDetails ```ts RecurringAppPlan | UsageAppPlan ``` ### RecurringAppPlan * interval ```ts BillingInterval.Every30Days | BillingInterval.Annual ``` * price ```ts Money ``` * discount ```ts AppPlanDiscount ``` ### BillingInterval * OneTime ```ts ONE_TIME ``` * Every30Days ```ts EVERY_30_DAYS ``` * Annual ```ts ANNUAL ``` * Usage ```ts USAGE ``` ### Money * amount ```ts number ``` * currencyCode ```ts string ``` ### AppPlanDiscount * durationLimitInIntervals ```ts number ``` * remainingDurationInIntervals ```ts number ``` * priceAfterDiscount ```ts Money ``` * value ```ts AppPlanDiscountAmount ``` ### AppPlanDiscountAmount ```ts BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage ``` ### BillingConfigSubscriptionPlanDiscountAmount * amount The amount to discount. Cannot be set if \`percentage\` is set. ```ts number ``` * percentage The percentage to discount. Cannot be set if \`amount\` is set. ```ts never ``` ### BillingConfigSubscriptionPlanDiscountPercentage * amount The amount to discount. Cannot be set if \`percentage\` is set. ```ts never ``` * percentage The percentage to discount. Cannot be set if \`amount\` is set. ```ts number ``` ### UsageAppPlan * balanceUsed ```ts Money ``` * cappedAmount ```ts Money ``` * terms ```ts string ``` ### CheckBillingOptions * plans The plans to check for. Must be one of the values defined in the \`billing\` config option. ```ts (keyof Config["billing"])[] ``` * isTest Whether to consider test purchases. ```ts boolean ``` ### RequestBillingOptions * plan The plan to request. Must be one of the values defined in the \`billing\` config option. ```ts keyof Config["billing"] ``` * isTest Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged. ```ts boolean ``` * returnUrl The URL to return to after the merchant approves the payment. ```ts string ``` ### CancelBillingOptions * subscriptionId The ID of the subscription to cancel. ```ts string ``` * prorate Whether to prorate the cancellation. ```ts boolean ``` * isTest ```ts boolean ``` ### EnsureCORSFunction ### EmbeddedAdminContext * sessionToken The decoded and validated session token for the request. Returned only if \`isEmbeddedApp\` is \`true\`. ```ts JwtPayload ``` * redirect A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded apps. Returned only if \`isEmbeddedApp\` is \`true\`. ```ts RedirectFunction ``` * session The session for the user who made the request. This comes from the session storage which \`shopifyApp\` uses to store sessions in your database of choice. Use this to get shop or user-specific data. ```ts Session ``` * admin Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request. ```ts AdminApiContext ``` * billing Billing methods for this store, based on the plans defined in the \`billing\` config option. ```ts BillingContext ``` * cors A function that ensures the CORS headers are set correctly for the response. ```ts EnsureCORSFunction ``` ### JwtPayload * iss The shop's admin domain. ```ts string ``` * dest The shop's domain. ```ts string ``` * aud The client ID of the receiving app. ```ts string ``` * sub The User that the session token is intended for. ```ts string ``` * exp When the session token expires. ```ts number ``` * nbf When the session token activates. ```ts number ``` * iat When the session token was issued. ```ts number ``` * jti A secure random UUID. ```ts string ``` * sid A unique session ID per user and app. ```ts string ``` ### RedirectFunction * url ```ts string ``` * init ```ts RedirectInit ``` TypedResponse\ ```ts TypedResponse ``` ### RedirectInit ```ts number | (ResponseInit & {target?: RedirectTarget}) ``` ### RedirectTarget ```ts '_self' | '_parent' | '_top' ``` Examples ### Examples * #### Authenticate, run API mutation, and redirect ##### Description Authenticate, run API mutation, and redirect ##### /app/routes/\*\*.ts ```typescript 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}); } }; ``` * #### Using the decoded session token ##### Description Get user-specific data using the \`sessionToken\` object. ##### /app/routes/\*\*\\/\*.ts ```typescript 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})); }; ``` ##### shopify.server.ts ```typescript import { shopifyApp } from "@shopify/shopify-app-remix/server"; const shopify = shopifyApp({ // ...etc useOnlineTokens: true, }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Redirecting to an app route ##### Description Use the \`redirect\` helper to safely redirect between pages. ##### /app/routes/admin/my-route.ts ```typescript 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("/"); }; ``` * #### Redirecting outside of Shopify admin ##### Description Pass in a \`target\` option of \`\_top\` or \`\_parent\` to go to an external URL. ##### /app/routes/admin/my-route.ts ```typescript 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("/", { target: '_parent' }); }; ``` * #### Using offline sessions ##### Description Get your app's shop-specific data using an offline session. ##### /app/routes/\*\*\\/\*.ts ```typescript 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)); }; ``` ##### shopify.server.ts ```typescript import { shopifyApp } from "@shopify/shopify-app-remix/server"; const shopify = shopifyApp({ // ...etc }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Using online sessions ##### Description Get your app's user-specific data using an online session. ##### /app/routes/\*\*\\/\*.ts ```typescript 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({user: session.onlineAccessInfo!.id})); }; ``` ##### shopify.server.ts ```typescript import { shopifyApp } from "@shopify/shopify-app-remix/server"; const shopify = shopifyApp({ // ...etc useOnlineTokens: true, }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Setting CORS headers for a admin request ##### Description Use the \`cors\` helper to ensure your app can respond to requests from admin extensions. ##### /app/routes/admin/my-route.ts ```typescript 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}))); }; ``` * #### Using REST resources ##### Description Getting the number of orders in a store using REST resources. Visit the \[Admin REST API references]\(/docs/api/admin-rest) for examples on using each resource. ##### /app/routes/\*\*\\/\*.ts ```typescript import { LoaderFunctionArgs, json } from "@remix-run/node"; import { authenticate } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { admin, session, } = await authenticate.admin(request); return json( admin.rest.resources.Order.count({ session }), ); }; ``` ##### /app/shopify.server.ts ```typescript import { shopifyApp } from "@shopify/shopify-app-remix/server"; import { restResources } from "@shopify/shopify-api/rest/admin/2023-07"; const shopify = shopifyApp({ restResources, // ...etc }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Performing a GET request to the REST API ##### Description Use \`admin.rest.get\` to make custom requests to make a request to to the \`customer/count\` endpoint ##### /app/routes/\*\*\\/\*.ts ```typescript import { LoaderFunctionArgs, json } from "@remix-run/node"; import { authenticate } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { admin, session, } = await authenticate.admin(request); const response = await admin.rest.get({ path: "/customers/count.json", }); const customers = await response.json(); return json({ customers }); }; ``` ##### /app/shopify.server.ts ```typescript import { shopifyApp } from "@shopify/shopify-app-remix/server"; import { restResources } from "@shopify/shopify-api/rest/admin/2023-04"; const shopify = shopifyApp({ restResources, // ...etc }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Performing a POST request to the REST API ##### Description Use \`admin.rest.post\` to make custom requests to make a request to to the \`customers.json\` endpoint to send a welcome email ##### /app/routes/\*\*\\/\*.ts ```typescript import { LoaderFunctionArgs, json } from "@remix-run/node"; import { authenticate } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { admin, session, } = await authenticate.admin(request); const response = admin.rest.post({ path: "customers/7392136888625/send_invite.json", body: { customer_invite: { to: "new_test_email@shopify.com", from: "j.limited@example.com", bcc: ["j.limited@example.com"], subject: "Welcome to my new shop", custom_message: "My awesome new store", }, }, }); const customerInvite = await response.json(); return json({ customerInvite }); }; ``` ##### /app/shopify.server.ts ```typescript import { shopifyApp } from "@shopify/shopify-app-remix/server"; import { restResources } from "@shopify/shopify-api/rest/admin/2023-04"; const shopify = shopifyApp({ restResources, // ...etc }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Querying the GraphQL API ##### Description Use \`admin.graphql\` to make query / mutation requests. ##### /app/routes/\*\*\\/\*.ts ```typescript 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, }); } ``` ##### /app/shopify.server.ts ```typescript import { shopifyApp } from "@shopify/shopify-app-remix/server"; const shopify = shopifyApp({ // ... }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Handling GraphQL errors ##### Description Catch \`GraphqlQueryError\` errors to see error messages from the API. ##### /app/routes/\*\*\\/\*.ts ```typescript import { ActionFunctionArgs } from "@remix-run/node"; import { authenticate } from "../shopify.server"; export const action = async ({ request }: ActionFunctionArgs) => { const { admin } = await authenticate.admin(request); try { const response = await admin.graphql( `#graphql query incorrectQuery { products(first: 10) { nodes { not_a_field } } }`, ); return json({ data: await response.json() }); } catch (error) { if (error instanceof GraphqlQueryError) { // error.body.errors: // { graphQLErrors: [ // { message: "Field 'not_a_field' doesn't exist on type 'Product'" } // ] } return json({ errors: error.body?.errors }, { status: 500 }); } return json({ message: "An error occurred" }, { status: 500 }); } } ``` ##### /app/shopify.server.ts ```typescript import { shopifyApp } from "@shopify/shopify-app-remix/server"; const shopify = shopifyApp({ // ... }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Requesting billing right away ##### Description Call \`billing.request\` in the \`onFailure\` callback to immediately redirect to the Shopify page to request payment. ##### /app/routes/\*\*\\/\*.ts ```typescript 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 }; ``` ##### shopify.server.ts ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Redirect to a plan selection page ##### Description 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. ##### /app/routes/\*\*\\/\*.ts ```typescript import { LoaderFunctionArgs, redirect } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); const billingCheck = await billing.require({ plans: [MONTHLY_PLAN, ANNUAL_PLAN], isTest: true, onFailure: () => redirect('/select-plan'), }); const subscription = billingCheck.appSubscriptions[0]; console.log(`Shop is on ${subscription.name} (id ${subscription.id})`); // App logic }; ``` ##### shopify.server.ts ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Requesting billing with line items ##### Description Call \`billing.request\` with the \`v3\_lineItemBilling\` future flag enabled ##### /app/routes/\*\*\\/\*.ts ```typescript 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 }; ``` ##### shopify.server.ts ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { lineItems: [ { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, { amount: 1, currencyCode: 'USD', interval: BillingInterval.Usage. terms: '1 dollar per 1000 emails', }, ], }, } future: {v3_lineItemBilling: true} }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Check what billing plans a merchant is subscribed to ##### Description 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. ##### /app/routes/\*\*\\/\*.ts ```typescript 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) }; ``` ##### shopify.server.ts ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Using a custom return URL ##### Description Change where the merchant is returned to after approving the purchase using the \`returnUrl\` option. ##### /app/routes/\*\*\\/\*.ts ```typescript 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 }; ``` ##### shopify.server.ts ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` * #### Cancelling a subscription ##### Description Use the \`billing.cancel\` function to cancel an active subscription with the id returned from \`billing.require\`. ##### /app/routes/cancel-subscription.ts ```typescript 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 }; ``` ##### shopify.server.ts ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` ## Related [Interact with the Admin API. - API context](https://shopify.dev/docs/api/shopify-app-remix/apis/admin-api) [Bill merchants for your app using the Admin API. - Billing context](https://shopify.dev/docs/api/shopify-app-remix/apis/billing)