Admin
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 config.
Anchor to authenticate.admin-parametersParameters
- Anchor to requestrequestrequestRequestRequestrequiredrequired
AdminContext
EmbeddedTypedAdminContext<Config> & ScopesContextEmbeddedTypedAdminContext
Config['isEmbeddedApp'] extends false
? NonEmbeddedAdminContext<Config>
: EmbeddedAdminContext<Config>NonEmbeddedAdminContext
- admin
Methods for interacting with the GraphQL Admin API for the store that made the request.
AdminApiContext - billing
Billing methods for this store, based on the plans defined in the `billing` config option.
BillingContext<Config> - cors
A function that ensures the CORS headers are set correctly for the response.
EnsureCORSFunction - 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.
Session
AdminApiContext
Provides utilities that apps can use to make requests to the Admin API.
- graphql
Methods for interacting with the Shopify Admin GraphQL API
GraphQLClient<AdminOperations>
GraphQLClient
- query
Operation extends keyof Operations - options
GraphQLQueryOptions<Operation, Operations>
interface Promise<T> {
/**
* 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<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
/**
* 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<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}, interface Promise<T> {}, Promise: PromiseConstructor, interface Promise<T> {
readonly [Symbol.toStringTag]: string;
}, interface Promise<T> {
/**
* 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<T>;
}GraphQLQueryOptions
- apiVersion
The version of the API to use for the request.
ApiVersion - headers
Additional headers to include in the request.
Record<string, any> - signal
An optional AbortSignal to cancel the request.
AbortSignal - tries
The total number of times to try the request if it fails.
number - variables
The variables to pass to the operation.
ApiClientRequestOptions<Operation, Operations>["variables"]
ApiVersion
- October24
2024-10 - January25
2025-01 - April25
2025-04 - July25
2025-07 - October25
2025-10 - January26
2026-01 - April26
2026-04 - July26
2026-07 - October26
2026-10 - Unstable
unstable
BillingContext
Provides utilities that apps can use to request billing for the app using the Admin API.
- cancel
Cancels an ongoing subscription, given its ID.
(options: CancelBillingOptions) => Promise<AppSubscription> - check
Checks if the shop has an active payment for any plan defined in the `billing` config option.
<Options extends CheckBillingOptions<Config>>(options?: Options) => Promise<BillingCheckResponseObject> - createUsageRecord
Creates a usage record for an app subscription.
(options: CreateUsageRecordOptions) => Promise<UsageRecord> - request
Requests payment for the plan.
(options: RequestBillingOptions<Config>) => Promise<never> - require
Checks if the shop has an active payment for any plan defined in the `billing` config option.
(options: RequireBillingOptions<Config>) => Promise<BillingCheckResponseObject> - updateUsageCappedAmount
Updates the capped amount for a usage billing plan.
(options: UpdateUsageCappedAmountOptions) => Promise<never>
CancelBillingOptions
- isTest
Whether to use the test mode. This prevents the credit card from being charged.
boolean - prorate
Whether to issue prorated credits for the unused portion of the app subscription. There will be a corresponding deduction (based on revenue share) to your Partner account. For example, if a $10.00 app subscription (with 0% revenue share) is cancelled and prorated half way through the billing cycle, then the merchant will be credited $5.00 and that amount will be deducted from your Partner account.
boolean - subscriptionId
The ID of the subscription to cancel.
string
Options
- layout
Whether to use the shop's theme layout around the Liquid content.
boolean
CheckBillingOptions
- isTest
Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.
boolean - plans
The plans to check for. Must be one of the values defined in the `billing` config option.
(keyof Config["billing"])[]
CreateUsageRecordOptions
- description
The description of the app usage record.
string - idempotencyKey
string - isTest
Whether to use the test mode. This prevents the credit card from being charged.
boolean - price
The price of the app usage record.
{ amount: number; currencyCode: string; } - subscriptionLineItemId
string
RequestBillingOptions
- amount
Amount to charge for this plan.
number - currencyCode
Currency code for this plan.
string - interval
Interval for this plan. Must be set to `OneTime`.
BillingInterval.OneTime - isTest
Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.
boolean - lineItems
The line items for this plan.
({ interval?: BillingInterval.Every30Days | BillingInterval.Annual; discount?: { durationLimitInIntervals?: number; value?: { amount?: number; percentage?: never; } | { amount?: never; percentage?: number; }; }; amount?: number; currencyCode?: string; } | { interval?: BillingInterval.Usage; amount?: number; terms?: string; currencyCode?: string; })[] - plan
The plan to request. Must be one of the values defined in the `billing` config option.
keyof Config["billing"] - replacementBehavior
The replacement behavior to use for this plan.
BillingReplacementBehavior - returnUrl
The URL to return to after the merchant approves the payment.
string - trialDays
How many trial days to give before charging for this plan.
number
BillingInterval
- OneTime
ONE_TIME - Every30Days
EVERY_30_DAYS - Annual
ANNUAL - Usage
USAGE
BillingReplacementBehavior
- ApplyImmediately
APPLY_IMMEDIATELY - ApplyOnNextBillingCycle
APPLY_ON_NEXT_BILLING_CYCLE - Standard
STANDARD
RequireBillingOptions
- isTest
Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.
boolean - onFailure
How to handle the request if the shop doesn't have an active payment for any plan.
(error: any) => Promise<Response> - plans
The plans to check for. Must be one of the values defined in the `billing` config option.
(keyof Config["billing"])[]
UpdateUsageCappedAmountOptions
- cappedAmount
The maximum charge for the usage billing plan.
{ amount: number; currencyCode: string; } - subscriptionLineItemId
The subscription line item ID to update.
string
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.
string - shop
The Shopify shop domain, such as `example.myshopify.com`.
string - state
The state of the session. Used for the OAuth authentication code flow.
string - isOnline
Whether the access token in the session is online or offline.
boolean - scope
The desired scopes for the access token, at the time the session was created.
string - expires
The date the access token expires.
Date - accessToken
The access token for the session.
string - refreshToken
The refresh token for the session.
string - refreshTokenExpires
The date the refresh token expires.
Date - onlineAccessInfo
Information on the user for the session. Only present for online sessions.
OnlineAccessInfo - isActive
Whether the session is active. Active sessions have an access token that is not expired, and has has the given scopes if scopes is equal to a truthy value.
(scopes: string | string[] | AuthScopes, withinMillisecondsOfExpiry?: number) => boolean - isScopeChanged
Whether the access token includes the given scopes if they are provided.
(scopes: string | string[] | AuthScopes) => boolean - isScopeIncluded
Whether the access token includes the given scopes.
(scopes: string | string[] | AuthScopes) => boolean - isExpired
Whether the access token is expired.
(withinMillisecondsOfExpiry?: number) => boolean - toObject
Converts an object with data into a Session.
() => SessionParams - equals
Checks whether the given session is equal to this session.
(other: Session) => boolean - toPropertyArray
Converts the session into an array of key-value pairs.
(returnUserData?: boolean) => [string, string | number | boolean][]
OnlineAccessInfo
- associated_user
The user associated with the access token.
OnlineAccessUser - associated_user_scope
The effective set of scopes for the session.
string - expires_in
How long the access token is valid for, in seconds.
number
OnlineAccessUser
- account_owner
Whether the user is the account owner.
boolean - collaborator
Whether the user is a collaborator.
boolean - email
The user's email address.
string - email_verified
Whether the user has verified their email address.
boolean - first_name
The user's first name.
string - id
The user's ID.
number - last_name
The user's last name.
string - locale
The user's locale.
string
AuthScopes
A class that represents a set of access token scopes.
- has
Checks whether the current set of scopes includes the given one.
(scope: string | string[] | AuthScopes) => boolean - equals
Checks whether the current set of scopes equals the given one.
(otherScopes: string | string[] | AuthScopes) => boolean - toString
Returns a comma-separated string with the current set of scopes.
() => string - toArray
Returns an array with the current set of scopes.
(returnOriginalScopes?: boolean) => any[]
SessionParams
- [key: string]
any - accessToken
The access token for the session.
string - expires
The date the access token expires.
Date - id
The unique identifier for the session.
string - isOnline
Whether the access token in the session is online or offline.
boolean - onlineAccessInfo
Information on the user for the session. Only present for online sessions.
OnlineAccessInfo | StoredOnlineAccessInfo - refreshToken
The refresh token for the session.
string - refreshTokenExpires
The date the refresh token expires.
Date - scope
The scopes for the access token.
string - shop
The Shopify shop domain.
string - state
The state of the session. Used for the OAuth authentication code flow.
string
StoredOnlineAccessInfo
Omit<OnlineAccessInfo, 'associated_user'> & {
associated_user: Partial<OnlineAccessUser>;
}EmbeddedAdminContext
- admin
Methods for interacting with the GraphQL Admin API for the store that made the request.
AdminApiContext - billing
Billing methods for this store, based on the plans defined in the `billing` config option.
BillingContext<Config> - cors
A function that ensures the CORS headers are set correctly for the response.
EnsureCORSFunction - 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`. Request parameters (including `id_token`, `shop`, `host`, and `embedded`) are only copied to the destination when it resolves to the same origin as `appUrl`. This helper does not validate the destination URL — avoid passing unvalidated user input (for example, a raw `?next=` query parameter) and use an allow-list of paths instead.
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.
Session - sessionToken
The decoded and validated session token for the request. Returned only if `isEmbeddedApp` is `true`.
JwtPayload
RedirectFunction
- url
string - init
RedirectInit
TypedResponse<never>RedirectInit
number | (ResponseInit & {target?: RedirectTarget})RedirectTarget
'_self' | '_parent' | '_top' | '_blank'ScopesContext
- scopes
Methods to manage scopes for the store that made the request.
ScopesApiContext
ScopesApiContext
Provides utilities that apps can use to [manage scopes](https://shopify.dev/docs/apps/build/authentication-authorization/app-installation/manage-access-scopes) for the app using the Admin API.
- query
Queries Shopify to see what scopes have been granted
() => Promise<ScopesDetail> - request
Requests the merchant grant the provided scopes This method performs a redirect to the grant screen.
(scopes: string[]) => Promise<void> - revoke
Revokes the provided scopes Warning: This method throws an [error](https://shopify.dev/docs/api/admin-graphql/unstable/objects/AppRevokeAccessScopesAppRevokeScopeError) if the provided optional scopes contains a required scope.
(scopes: string[]) => Promise<ScopesRevokeResponse>
ScopesDetail
- granted
The scopes that have been granted on the shop for this app
string[] - optional
The optional scopes that the app has declared in its configuration
string[] - required
The required scopes that the app has declared in its configuration
string[]
ScopesRevokeResponse
- revoked
The scopes that have been revoked on the shop for this app
string[]
/app/routes/**.ts
Examples
Description
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}); } };Description
Use the `cors` helper to ensure your app can respond to requests from admin extensions.
/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}))); };Description
Use the `redirect` helper to safely redirect between pages.
/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("/"); };Description
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.
/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("shopify://admin/products/123456", { target: '_parent' }); };Description
Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.
/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("/", { target: '_parent' }); };Using offline sessions
Description
Get your app's shop-specific data using an offline session.
/app/routes/**\/*.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 } = await authenticate.admin(request); return json(await getMyAppData({shop: session.shop)); };shopify.server.ts
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
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
import { shopifyApp } from "@shopify/shopify-app-remix/server"; const shopify = shopifyApp({ // ...etc useOnlineTokens: true, }); export default shopify; export const authenticate = shopify.authenticate;Using the decoded session token
Description
Get user-specific data using the `sessionToken` object.
/app/routes/**\/*.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 { sessionToken } = await authenticate.admin( request ); return json(await getMyAppData({user: sessionToken.sub})); };shopify.server.ts
import { shopifyApp } from "@shopify/shopify-app-remix/server"; const shopify = shopifyApp({ // ...etc useOnlineTokens: true, }); export default shopify; export const authenticate = shopify.authenticate;Querying the GraphQL API
Description
Use `admin.graphql` to make query / mutation requests.
/app/routes/**\/*.ts
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
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
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
import { shopifyApp } from "@shopify/shopify-app-remix/server"; const shopify = shopifyApp({ // ... }); 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
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
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, } ], }, [ANNUAL_PLAN]: { lineItems: [ { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, } ], }, } }); 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
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
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, } ], }, [ANNUAL_PLAN]: { lineItems: [ { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, } ], }, } }); export default shopify; export const authenticate = shopify.authenticate;Description
Use billing.check to see if any payments exist for the store, regardless of whether it's a test ormatches one or more plans.
/app/routes/**\/*.ts
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(); // This will be true if any payment is found console.log(hasActivePayment); console.log(appSubscriptions); };Creating a usage record
Description
Create a usage record for the active usage billing plan
/app/routes/create-usage-record.ts
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 };shopify.server.ts
import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const USAGE_PLAN = 'Usage subscription'; const shopify = shopifyApp({ // ...etc billing: { [USAGE_PLAN]: { lineItems: [ { amount: 5, currencyCode: 'USD', interval: BillingInterval.Usage, } ], }, } }); 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
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
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, } ], }, [ANNUAL_PLAN]: { lineItems: [ { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, } ], }, } }); export default shopify; export const authenticate = shopify.authenticate;Overriding plan settings
Description
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.
/app/routes/**\/*.ts
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, trialDays: 14, lineItems: [ { interval: BillingInterval.Every30Days, discount: { value: { percentage: 0.1 } }, }, ], }), }); // App logic };shopify.server.ts
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, } ], }, [ANNUAL_PLAN]: { lineItems: [ { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, } ], }, } }); 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
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
import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { lineItems: [ { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, } ], }, } }); 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
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
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, } ], }, [ANNUAL_PLAN]: { lineItems: [ { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, } ], }, } }); export default shopify; export const authenticate = shopify.authenticate;Updating the capped amount for a usage billing plan
Description
Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.
/app/routes/**\/*.ts
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 };shopify.server.ts
import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const USAGE_PLAN = 'Usage subscription'; const shopify = shopifyApp({ // ...etc billing: { [USAGE_PLAN]: { lineItems: [ { amount: 5, currencyCode: 'USD', interval: BillingInterval.Usage, terms: "Usage based" } ], }, } }); export default shopify; export const authenticate = shopify.authenticate;Description
Call `scopes.query` to get scope details.
/app._index.tsx
import { authenticate } from "../shopify.server"; import { json } from "@remix-run/node"; export const loader = async ({ request }) => { const { scopes } = await authenticate.admin(request); const { required, optional, granted } = await scopes.query(); if (granted.includes("write_products")) { // do something } return null };Description
Call `scopes.request` to request optional scopes.
/app/routes/app.request.tsx
import { authenticate } from "../shopify.server"; // Example of an action to request optional scopes export const action = async ({ request }) => { const { scopes } = await authenticate.admin(request); const { granted } = await scopes.query(); if (!granted.includes("write_products")) { await scopes.request(["write_products"]); } return null };Description
Call `scopes.revoke` to revoke optional scopes.
/app._index.tsx
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 }) => { const { scopes } = await authenticate.admin(request); const { granted } = await scopes.query(); if (granted.includes("write_products")) { const revokedResponse = await scopes.revoke(["write_products"]); return json(revokedResponse); } return null };