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.
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});
}
};
Authenticates requests coming from the Shopify admin. The shape of the returned object changes depending on the `isEmbeddedApp` config.
request: Request
export type AuthenticateAdmin< Config extends AppConfigArg, Resources extends ShopifyRestResources = ShopifyRestResources, > = (request: Request) => Promise<AdminContext<Config, Resources>>;
Config['isEmbeddedApp'] extends false ? NonEmbeddedAdminContext<Config, Resources> : EmbeddedAdminContext<Config, Resources>
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.
Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.
Billing methods for this store, based on the plans defined in the `billing` config option.
A function that ensures the CORS headers are set correctly for the response.
Stores App information from logged in merchants so they can make authenticated requests to the Admin API.
The unique identifier for the session.
The Shopify shop domain, such as `example.myshopify.com`.
The state of the session. Used for the OAuth authentication code flow.
Whether the access token in the session is online or offline.
The desired scopes for the access token, at the time the session was created.
The date the access token expires.
The access token for the session.
Information on the user for the session. Only present for online sessions.
Whether the session is active. Active sessions have an access token that is not expired, and has the given scopes.
Whether the access token has the given scopes.
Whether the access token is expired.
Converts an object with data into a Session.
Checks whether the given session is equal to this session.
Converts the session into an array of key-value pairs.
How long the access token is valid for, in seconds.
The effective set of scopes for the session.
The user associated with the access token.
The user's ID.
The user's first name.
The user's last name.
The user's email address.
Whether the user has verified their email address.
Whether the user is the account owner.
The user's locale.
Whether the user is a collaborator.
A class that represents a set of access token scopes.
Checks whether the current set of scopes includes the given one.
Checks whether the current set of scopes equals the given one.
Returns a comma-separated string with the current set of scopes.
Returns an array with the current set of scopes.
The unique identifier for the session.
The Shopify shop domain.
The state of the session. Used for the OAuth authentication code flow.
Whether the access token in the session is online or offline.
The scopes for the access token.
The date the access token expires.
The access token for the session.
Information on the user for the session. Only present for online sessions.
Omit<OnlineAccessInfo, 'associated_user'> & { associated_user: Partial<OnlineAccessUser>; }
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.
Methods for interacting with the Shopify Admin GraphQL API
RemixRestClient & {resources: Resources}
Performs a GET request on the given path.
Performs a POST request on the given path.
Performs a PUT request on the given path.
Performs a DELETE request on the given path.
The path to the resource, relative to the API version root.
The type of data expected in the response.
The request body.
Query parameters to be sent with the request.
Additional headers to be sent with the request.
The maximum number of times the request can be made if it fails with a throttling or server error.
Headers to be sent with the request.
Record<string, string | number | string[]>
GetRequestParams & { data: Record<string, any> | string; }
query: Operation extends keyof Operations
options: GraphQLQueryOptions<Operation, Operations>
export type GraphQLClient<Operations extends AllOperations> = < Operation extends keyof Operations, >( query: Operation, options?: GraphQLQueryOptions<Operation, Operations>, ) => Promise<GraphQLResponse<Operation, Operations>>;
The variables to pass to the operation.
The version of the API to use for the request.
Additional headers to include in the request.
The total number of times to try the request if it fails.
Checks if the shop has an active payment for any plan defined in the `billing` config option.
Checks if the shop has an active payment for any plan defined in the `billing` config option.
Requests payment for the plan.
Cancels an ongoing subscription, given its ID.
The plans to check for. Must be one of the values defined in the `billing` config option.
How to handle the request if the shop doesn't have an active payment for any plan.
Whether to consider test purchases.
Whether the user has an active payment method.
The one-time purchases the shop has.
The active subscriptions the shop has.
The ID of the one-time purchase.
The name of the purchased plan.
Whether this is a test purchase.
The status of the one-time purchase.
The ID of the app subscription.
The name of the purchased plan.
Whether this is a test subscription.
BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage
The amount to discount. Cannot be set if `percentage` is set.
The percentage to discount. Cannot be set if `amount` is set.
The amount to discount. Cannot be set if `percentage` is set.
The percentage to discount. Cannot be set if `amount` is set.
The plans to check for. Must be one of the values defined in the `billing` config option.
Whether to consider test purchases.
The plan to request. Must be one of the values defined in the `billing` config option.
Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.
The URL to return to after the merchant approves the payment.
The ID of the subscription to cancel.
Whether to prorate the cancellation.
export interface EnsureCORSFunction { (response: Response): Response; }
The decoded and validated session token for the request. Returned only if `isEmbeddedApp` is `true`.
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`.
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.
Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.
Billing methods for this store, based on the plans defined in the `billing` config option.
A function that ensures the CORS headers are set correctly for the response.
The shop's admin domain.
The shop's domain.
The client ID of the receiving app.
The User that the session token is intended for.
When the session token expires.
When the session token activates.
When the session token was issued.
A secure random UUID.
A unique session ID per user and app.
url: string
init: RedirectInit
export type RedirectFunction = ( url: string, init?: RedirectInit, ) => TypedResponse<never>;
number | (ResponseInit & {target?: RedirectTarget})
'_self' | '_parent' | '_top'
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.
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}));
};
import { shopifyApp } from "@shopify/shopify-app-remix/server";
const shopify = shopifyApp({
// ...etc
useOnlineTokens: true,
});
export default shopify;
export const authenticate = shopify.authenticate;
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("/");
};
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' });
};
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));
};
import { shopifyApp } from "@shopify/shopify-app-remix/server";
const shopify = shopifyApp({
// ...etc
});
export default shopify;
export const authenticate = shopify.authenticate;
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}));
};
import { shopifyApp } from "@shopify/shopify-app-remix/server";
const shopify = shopifyApp({
// ...etc
useOnlineTokens: true,
});
export default shopify;
export const authenticate = shopify.authenticate;
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})));
};
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 }),
);
};
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;
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 });
};
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;
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 });
};
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;
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,
});
}
import { shopifyApp } from "@shopify/shopify-app-remix/server";
const shopify = shopifyApp({
// ...
});
export default shopify;
export const authenticate = shopify.authenticate;
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 });
}
}
import { shopifyApp } from "@shopify/shopify-app-remix/server";
const shopify = shopifyApp({
// ...
});
export default shopify;
export const authenticate = shopify.authenticate;
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
};
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;
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
};
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;
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
};
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;
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)
};
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;
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
};
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;
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
};
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;