diff --git a/packages/apps/shopify-app-remix/docs/generated/generated_docs_data.json b/packages/apps/shopify-app-remix/docs/generated/generated_docs_data.json index fbd6a30178..691deaa6ea 100644 --- a/packages/apps/shopify-app-remix/docs/generated/generated_docs_data.json +++ b/packages/apps/shopify-app-remix/docs/generated/generated_docs_data.json @@ -129,7 +129,7 @@ }, { "name": "AppProxyForm", - "description": "Sets up a Remix `
` component that works when rendered behind an app proxy.\n\nSupports any properties accepted by the `` component.", + "description": "Sets up a Remix `` component that works when rendered on an app proxy page.\n\nSupports any properties accepted by the `` component.\n\nBecause Remix doesn't support URL rewriting, any route using this component should match the pathname of the proxy URL exactly, and end in a trailing slash (e.g., `https:///apps/proxy/`), or set the Remix Form prop `navigate` to `false`.", "category": "App proxy components", "type": "component", "isVisualComponent": false, @@ -157,8 +157,8 @@ "title": "Render a form element in a proxied route", "tabs": [ { - "title": "/app/routes/**\\/*.ts", - "code": "import {\n AppProxyProvider,\n AppProxyForm,\n} from \"@shopify/shopify-app-remix/react\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport async function loader({ request }) {\n await authenticate.public.appProxy(request);\n\n return json({ appUrl: process.env.SHOPIFY_APP_URL });\n}\n\nexport async function action({ request }) {\n await authenticate.public.appProxy(request);\n\n const formData = await request.formData();\n const field = formData.get(\"field\")?.toString();\n\n // Perform actions\n if (field) {\n console.log(\"Field:\", field);\n }\n\n // Return JSON to the client\n return json({ message: \"Success!\" });\n}\n\nexport default function App() {\n const { appUrl } = useLoaderData();\n const data = useActionData();\n\n return (\n <AppProxyProvider appUrl={appUrl}>\n <AppProxyForm action=\"/\">\n <input type=\"text\" name=\"field\" defaultValue={data?.field} />\n\n <input type=\"submit\" name=\"Submit\" />\n </AppProxyForm>\n </AppProxyProvider>\n );\n}", + "title": "/app/routes/apps/appProxy.ts", + "code": "import {\n AppProxyProvider,\n AppProxyForm,\n} from \"@shopify/shopify-app-remix/react\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport async function loader({ request }) {\n await authenticate.public.appProxy(request);\n\n return json({ appUrl: process.env.SHOPIFY_APP_URL });\n}\n\nexport async function action({ request }) {\n await authenticate.public.appProxy(request);\n\n const formData = await request.formData();\n const field = formData.get(\"field\")?.toString();\n\n // Perform actions\n if (field) {\n console.log(\"Field:\", field);\n }\n\n // Return JSON to the client\n return json({ message: \"Success!\" });\n}\n\nexport default function App() {\n const { appUrl } = useLoaderData();\n\n return (\n <AppProxyProvider appUrl={appUrl}>\n <AppProxyForm action=\"/apps/appProxy\" method=\"post\">\n <input type=\"text\" name=\"field\" />\n\n <input type=\"submit\" name=\"Submit\" />\n </AppProxyForm>\n </AppProxyProvider>\n );\n}", "language": "typescript" } ] @@ -254,13 +254,13 @@ { "name": "AppProxyForm", "subtitle": "Render form elements in proxies.", - "url": "/docs/api/shopify-app-remix/app-proxy-components/appproxy-form", + "url": "/docs/api/shopify-app-remix/app-proxy-components/appproxyform", "type": "remix" }, { "name": "AppProxyLink", "subtitle": "Render link elements in proxies.", - "url": "/docs/api/shopify-app-remix/app-proxy-components/appproxy-link", + "url": "/docs/api/shopify-app-remix/app-proxy-components/appproxylink", "type": "remix" } ], @@ -329,6 +329,15 @@ "name": "FutureFlags", "description": "", "members": [ + { + "filePath": "src/server/future/flags.ts", + "syntaxKind": "PropertySignature", + "name": "removeRest", + "value": "boolean", + "description": "When enabled, methods for interacting with the admin REST API will not be returned.\n\nThis affects:\n\n* `authenticate.admin(request)` * `authenticate.webhook(request)` * `authenticate.flow(request)` * `authenticate.appProxy(request)` * `authenticate.fulfillmentService(request)` * `unauthenticated.admin(shop)`\n\nIn a future release we will remove REST from the package completely.\n\nPlease see: [https://www.shopify.com/ca/partners/blog/all-in-on-graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)", + "isOptional": true, + "defaultValue": "false" + }, { "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", @@ -348,7 +357,7 @@ "defaultValue": "false" } ], - "value": "export interface FutureFlags {\n /**\n * When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).\n * This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n *\n * Learn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).\n *\n * @default false\n */\n unstable_newEmbeddedAuthStrategy?: boolean;\n\n /**\n * When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.\n *\n * @default false\n */\n wip_optionalScopesApi?: boolean;\n}" + "value": "export interface FutureFlags {\n /**\n * When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).\n * This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n *\n * Learn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).\n *\n * @default false\n */\n unstable_newEmbeddedAuthStrategy?: boolean;\n\n /**\n * When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.\n *\n * @default false\n */\n wip_optionalScopesApi?: boolean;\n\n /**\n * When enabled, methods for interacting with the admin REST API will not be returned.\n *\n * This affects:\n *\n * * `authenticate.admin(request)`\n * * `authenticate.webhook(request)`\n * * `authenticate.flow(request)`\n * * `authenticate.appProxy(request)`\n * * `authenticate.fulfillmentService(request)`\n * * `unauthenticated.admin(shop)`\n *\n * In a future release we will remove REST from the package completely.\n *\n * Please see: [https://www.shopify.com/ca/partners/blog/all-in-on-graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)\n *\n * @default false\n */\n removeRest?: boolean;\n}" }, "EmbeddedTypedAdminContext": { "filePath": "src/server/authenticate/admin/types.ts", @@ -366,7 +375,7 @@ "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "admin", - "value": "AdminApiContext", + "value": "AdminApiContext", "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." }, { @@ -650,6013 +659,626 @@ ], "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to issue prorated credits for the unused portion of the app subscription. There will be a corresponding\n * deduction (based on revenue share) to your Partner account. For example, if a $10.00 app subscription\n * (with 0% revenue share) is cancelled and prorated half way through the billing cycle, then the merchant will be\n * credited $5.00 and that amount will be deducted from your Partner account.\n */\n prorate?: boolean;\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest?: boolean;\n}" }, - "AppSubscription": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppSubscription", + "CheckBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CheckBillingOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the app subscription." + "name": "isTest", + "value": "boolean", + "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "lineItems", - "value": "ActiveSubscriptionLineItem[]", - "description": "", + "name": "plans", + "value": "(keyof Config[\"billing\"])[]", + "description": "The plans to check for. Must be one of the values defined in the `billing` config option.", "isOptional": true - }, + } + ], + "value": "export interface CheckBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans?: (keyof Config['billing'])[];\n}" + }, + "CreateUsageRecordOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CreateUsageRecordOptions", + "description": "", + "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "name", + "name": "description", "value": "string", - "description": "The name of the purchased plan." + "description": "The description of the app usage record." }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "status", + "name": "idempotencyKey", "value": "string", - "description": "" + "description": "", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "test", + "name": "isTest", "value": "boolean", - "description": "Whether this is a test subscription." - } - ], - "value": "export interface AppSubscription {\n /**\n * The ID of the app subscription.\n */\n id: string;\n /**\n * The name of the purchased plan.\n */\n name: string;\n /**\n * Whether this is a test subscription.\n */\n test: boolean;\n lineItems?: ActiveSubscriptionLineItem[];\n status: string;\n}" - }, - "ActiveSubscriptionLineItem": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "ActiveSubscriptionLineItem", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "" + "description": "Whether to use the test mode. This prevents the credit card from being charged." }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "plan", - "value": "AppPlan", - "description": "" - } - ], - "value": "export interface ActiveSubscriptionLineItem {\n id: string;\n plan: AppPlan;\n}" - }, - "AppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppPlan", - "description": "", - "members": [ + "name": "price", + "value": "{ amount: number; currencyCode: string; }", + "description": "The price of the app usage record." + }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "pricingDetails", - "value": "RecurringAppPlan | UsageAppPlan", - "description": "" + "name": "subscriptionLineItemId", + "value": "string", + "description": "", + "isOptional": true } ], - "value": "export interface AppPlan {\n pricingDetails: RecurringAppPlan | UsageAppPlan;\n}" + "value": "export interface CreateUsageRecordOptions {\n /**\n * The description of the app usage record.\n */\n description: string;\n /**\n * The price of the app usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest: boolean;\n /*\n * Defines the usage pricing plan the merchant is subscribed to.\n */\n subscriptionLineItemId?: string;\n /*\n * A unique key generated to avoid duplicate charges.\n */\n idempotencyKey?: string;\n}" }, - "RecurringAppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "RecurringAppPlan", + "RequestBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequestBillingOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "discount", - "value": "AppPlanDiscount", - "description": "" + "name": "isTest", + "value": "boolean", + "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "interval", - "value": "BillingInterval.Every30Days | BillingInterval.Annual", - "description": "" + "name": "plan", + "value": "keyof Config[\"billing\"]", + "description": "The plan to request. Must be one of the values defined in the `billing` config option." }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "price", - "value": "Money", - "description": "" + "name": "returnUrl", + "value": "string", + "description": "The URL to return to after the merchant approves the payment.", + "isOptional": true } ], - "value": "export interface RecurringAppPlan {\n interval: BillingInterval.Every30Days | BillingInterval.Annual;\n price: Money;\n discount: AppPlanDiscount;\n}" + "value": "export interface RequestBillingOptions\n extends Omit<\n BillingRequestParams>,\n 'session' | 'plan' | 'returnObject'\n > {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" }, - "AppPlanDiscount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppPlanDiscount", + "RequireBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequireBillingOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "durationLimitInIntervals", - "value": "number", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "priceAfterDiscount", - "value": "Money", - "description": "" + "name": "isTest", + "value": "boolean", + "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "remainingDurationInIntervals", - "value": "number", - "description": "" + "name": "onFailure", + "value": "(error: any) => Promise", + "description": "How to handle the request if the shop doesn't have an active payment for any plan." }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "value", - "value": "AppPlanDiscountAmount", - "description": "" + "name": "plans", + "value": "(keyof Config[\"billing\"])[]", + "description": "The plans to check for. Must be one of the values defined in the `billing` config option." } ], - "value": "export interface AppPlanDiscount {\n durationLimitInIntervals: number;\n remainingDurationInIntervals: number;\n priceAfterDiscount: Money;\n value: AppPlanDiscountAmount;\n}" + "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" }, - "Money": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "Money", + "UpdateUsageCappedAmountOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "UpdateUsageCappedAmountOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "" + "name": "cappedAmount", + "value": "{ amount: number; currencyCode: string; }", + "description": "The maximum charge for the usage billing plan." }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "currencyCode", + "name": "subscriptionLineItemId", "value": "string", - "description": "" + "description": "The subscription line item ID to update." } ], - "value": "export interface Money {\n amount: number;\n currencyCode: string;\n}" - }, - "AppPlanDiscountAmount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "AppPlanDiscountAmount", - "value": "BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage", - "description": "" + "value": "export interface UpdateUsageCappedAmountOptions {\n /**\n * The subscription line item ID to update.\n */\n subscriptionLineItemId: string;\n /**\n * The maximum charge for the usage billing plan.\n */\n cappedAmount: {\n /**\n * The amount to update.\n */\n amount: number;\n /**\n * The currency code to update.\n */\n currencyCode: string;\n };\n}" }, - "BillingConfigSubscriptionPlanDiscountAmount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionPlanDiscountAmount", + "EnsureCORSFunction": { + "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", + "name": "EnsureCORSFunction", "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "The amount to discount.\n\nCannot be set if `percentage` is set." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "percentage", - "value": "never", - "description": "The percentage to discount.\n\nCannot be set if `amount` is set.", - "isOptional": true - } - ], - "value": "export interface BillingConfigSubscriptionPlanDiscountAmount {\n /**\n * The amount to discount.\n *\n * Cannot be set if `percentage` is set.\n */\n amount: number;\n /**\n * The percentage to discount.\n *\n * Cannot be set if `amount` is set.\n */\n percentage?: never;\n}" + "members": [], + "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" }, - "BillingConfigSubscriptionPlanDiscountPercentage": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionPlanDiscountPercentage", + "EmbeddedAdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "EmbeddedAdminContext", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "amount", - "value": "never", - "description": "The amount to discount.\n\nCannot be set if `percentage` is set.", - "isOptional": true + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "percentage", - "value": "number", - "description": "The percentage to discount.\n\nCannot be set if `amount` is set." - } - ], - "value": "export interface BillingConfigSubscriptionPlanDiscountPercentage {\n /**\n * The amount to discount.\n *\n * Cannot be set if `percentage` is set.\n */\n amount?: never;\n /**\n * The percentage to discount.\n *\n * Cannot be set if `amount` is set.\n */\n percentage: number;\n}" - }, - "BillingInterval": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "BillingInterval", - "value": "export declare enum BillingInterval {\n OneTime = \"ONE_TIME\",\n Every30Days = \"EVERY_30_DAYS\",\n Annual = \"ANNUAL\",\n Usage = \"USAGE\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "OneTime", - "value": "ONE_TIME" + "name": "billing", + "value": "BillingContext", + "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" }, { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Every30Days", - "value": "EVERY_30_DAYS" + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", + "examples": [ + { + "title": "Setting CORS headers for a admin request", + "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Annual", - "value": "ANNUAL" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Usage", - "value": "USAGE" - } - ] - }, - "UsageAppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "UsageAppPlan", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "balanceUsed", - "value": "Money", - "description": "" + "name": "redirect", + "value": "RedirectFunction", + "description": "A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded apps.\n\nReturned only if `isEmbeddedApp` is `true`.", + "examples": [ + { + "title": "Redirecting to an app route", + "description": "Use the `redirect` helper to safely redirect between pages.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\");\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + }, + { + "title": "Redirecting outside of the Admin embedded app page", + "description": "Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\", { target: '_parent' });\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "cappedAmount", - "value": "Money", - "description": "" + "name": "session", + "value": "Session", + "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "examples": [ + { + "title": "Using offline sessions", + "description": "Get your app's shop-specific data using an offline session.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + }, + { + "title": "Using online sessions", + "description": "Get your app's user-specific data using an online session.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "terms", - "value": "string", - "description": "" + "name": "sessionToken", + "value": "JwtPayload", + "description": "The decoded and validated session token for the request.\n\nReturned only if `isEmbeddedApp` is `true`.\n\n\n\n\n", + "examples": [ + { + "title": "Using the decoded session token", + "description": "Get user-specific data using the `sessionToken` object.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.admin(\n request\n );\n return json(await getMyAppData({user: sessionToken.sub}));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] } ], - "value": "export interface UsageAppPlan {\n balanceUsed: Money;\n cappedAmount: Money;\n terms: string;\n}" + "value": "export interface EmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {\n /**\n * The decoded and validated session token for the request.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload}\n *\n * @example\n * Using the decoded session token.\n * Get user-specific data using the `sessionToken` object.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.admin(\n * request\n * );\n * return json(await getMyAppData({user: sessionToken.sub}));\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * useOnlineTokens: true,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded\n * apps.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * @example\n * Redirecting to an app route.\n * Use the `redirect` helper to safely redirect between pages.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\");\n * };\n * ```\n *\n * @example\n * Redirecting outside of the Admin embedded app page.\n * Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\", { target: '_parent' });\n * };\n * ```\n */\n redirect: RedirectFunction;\n}" }, - "CheckBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "CheckBillingOptions", + "RedirectFunction": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "name": "RedirectFunction", "description": "", - "members": [ + "params": [ { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", - "isOptional": true + "name": "url", + "description": "", + "value": "string", + "filePath": "src/server/authenticate/admin/helpers/redirect.ts" }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plans", - "value": "(keyof Config[\"billing\"])[]", - "description": "The plans to check for. Must be one of the values defined in the `billing` config option.", - "isOptional": true + "name": "init", + "description": "", + "value": "RedirectInit", + "isOptional": true, + "filePath": "src/server/authenticate/admin/helpers/redirect.ts" } ], - "value": "export interface CheckBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans?: (keyof Config['billing'])[];\n}" + "returns": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "description": "", + "name": "TypedResponse", + "value": "TypedResponse" + }, + "value": "export type RedirectFunction = (\n url: string,\n init?: RedirectInit,\n) => TypedResponse;" }, - "BillingCheckResponseObject": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingCheckResponseObject", + "RedirectInit": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RedirectInit", + "value": "number | (ResponseInit & {target?: RedirectTarget})", + "description": "" + }, + "RedirectTarget": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RedirectTarget", + "value": "'_self' | '_parent' | '_top' | '_blank'", + "description": "" + }, + "ScopesContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "ScopesContext", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "appSubscriptions", - "value": "AppSubscription[]", - "description": "The active subscriptions the shop has." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "hasActivePayment", - "value": "boolean", - "description": "Whether the user has an active payment method." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "oneTimePurchases", - "value": "OneTimePurchase[]", - "description": "The one-time purchases the shop has." + "name": "scopes", + "value": "ScopesApiContext", + "description": "Methods to manage scopes for the store that made the request." } ], - "value": "export interface BillingCheckResponseObject {\n /**\n * Whether the user has an active payment method.\n */\n hasActivePayment: boolean;\n /**\n * The one-time purchases the shop has.\n */\n oneTimePurchases: OneTimePurchase[];\n /**\n * The active subscriptions the shop has.\n */\n appSubscriptions: AppSubscription[];\n}" + "value": "export interface ScopesContext {\n /**\n * Methods to manage scopes for the store that made the request.\n */\n scopes: ScopesApiContext;\n}" }, - "OneTimePurchase": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "OneTimePurchase", - "description": "", + "ScopesApiContext": { + "filePath": "src/server/authenticate/admin/scope/types.ts", + "name": "ScopesApiContext", + "description": "The Scopes API enables embedded apps and extensions to request merchant consent for access scopes.", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the one-time purchase." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "name", - "value": "string", - "description": "The name of the purchased plan." + "name": "query", + "value": "() => Promise", + "description": "Queries Shopify for the scopes for this app on this shop", + "examples": [ + { + "title": "Query for granted scopes", + "description": "Call `scopes.query` to get scope details.", + "tabs": [ + { + "code": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const scopesDetail = await scopes.query();\n\n return json({\n hasWriteProducts: scopesDetail.granted.includes('write_products'),\n });\n};\n\nexport default function Index() {\n const {hasWriteProducts} = useLoaderData();\n\n ...\n}", + "title": "/app._index.tsx" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "status", - "value": "string", - "description": "The status of the one-time purchase." + "name": "request", + "value": "(scopes: string[]) => Promise", + "description": "Requests the merchant to grant the provided scopes for this app on this shop\n\nWarning: This method performs a server-side redirect.", + "examples": [ + { + "title": "Request consent from the merchant to grant the provided scopes for this app", + "description": "Call `scopes.request` to request optional scopes.", + "tabs": [ + { + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST a request to for optional scopes\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRequest = body.getAll(\"scopes\") as string[];\n\n // If the scopes are not already granted, a full page redirect to the request URL occurs\n await scopes.request(scopesToRequest);\n // otherwise return an empty response\n return json({});\n};\n\nexport default function Index() {\n const fetcher = useFetcher();\n\n const handleRequest = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", + "title": "/app/routes/app.request.tsx" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "test", - "value": "boolean", - "description": "Whether this is a test purchase." + "name": "revoke", + "value": "(scopes: string[]) => Promise", + "description": "Revokes the provided scopes from this app on this shop\n\nWarning: This method throws an [error](https://shopify.dev/docs/api/admin-graphql/unstable/objects/AppRevokeAccessScopesAppRevokeScopeError) if the provided optional scopes contains a required scope.", + "examples": [ + { + "title": "Revoke optional scopes", + "description": "Call `scopes.revoke` to revoke optional scopes.", + "tabs": [ + { + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST optional scopes to revoke\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRevoke = body.getAll(\"scopes\") as string[];\n\n const revokedResponse = await scopes.revoke(scopesToRevoke);\n\n return json(revokedResponse);\n};\n\nexport default function Index() {\n const fetcher = useFetcher();\n\n const handleRevoke = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", + "title": "/app._index.tsx" + } + ] + } + ] } ], - "value": "export interface OneTimePurchase {\n /**\n * The ID of the one-time purchase.\n */\n id: string;\n /**\n * The name of the purchased plan.\n */\n name: string;\n /**\n * Whether this is a test purchase.\n */\n test: boolean;\n /**\n * The status of the one-time purchase.\n */\n status: string;\n}" + "value": "export interface ScopesApiContext {\n /**\n * Queries Shopify for the scopes for this app on this shop\n *\n * @returns {ScopesDetail} The scope details.\n *\n * @example\n * Query for granted scopes.\n * Call `scopes.query` to get scope details.\n * ```ts\n * // /app._index.tsx\n * import type { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { useLoaderData } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const scopesDetail = await scopes.query();\n *\n * return json({\n * hasWriteProducts: scopesDetail.granted.includes('write_products'),\n * });\n * };\n *\n * export default function Index() {\n * const {hasWriteProducts} = useLoaderData();\n *\n * ...\n * }\n * ```\n */\n query: () => Promise;\n\n /**\n * Requests the merchant to grant the provided scopes for this app on this shop\n *\n * Warning: This method performs a server-side redirect.\n *\n * @example\n * Request consent from the merchant to grant the provided scopes for this app.\n * Call `scopes.request` to request optional scopes.\n * ```ts\n * // /app/routes/app.request.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { useFetcher } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * // Example of an action to POST a request to for optional scopes\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const body = await request.formData();\n * const scopesToRequest = body.getAll(\"scopes\") as string[];\n *\n * // If the scopes are not already granted, a full page redirect to the request URL occurs\n * await scopes.request(scopesToRequest);\n * // otherwise return an empty response\n * return json({});\n * };\n *\n * export default function Index() {\n * const fetcher = useFetcher();\n *\n * const handleRequest = () => {\n * fetcher.submit({scopes: [\"write_products\"]}, {\n * method: \"POST\",\n * });\n * };\n *\n * ...\n * }\n * ```\n */\n request: (scopes: Scope[]) => Promise;\n\n /**\n * Revokes the provided scopes from this app on this shop\n *\n * 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.\n *\n * @example\n * Revoke optional scopes.\n * Call `scopes.revoke` to revoke optional scopes.\n * ```ts\n * // /app._index.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { useFetcher } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * // Example of an action to POST optional scopes to revoke\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const body = await request.formData();\n * const scopesToRevoke = body.getAll(\"scopes\") as string[];\n *\n * const revokedResponse = await scopes.revoke(scopesToRevoke);\n *\n * return json(revokedResponse);\n * };\n *\n * export default function Index() {\n * const fetcher = useFetcher();\n *\n * const handleRevoke = () => {\n * fetcher.submit({scopes: [\"write_products\"]}, {\n * method: \"POST\",\n * });\n * };\n *\n * ...\n * }\n * ```\n */\n revoke: (scopes: Scope[]) => Promise;\n}" }, - "CreateUsageRecordOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "CreateUsageRecordOptions", + "ScopesDetail": { + "filePath": "src/server/authenticate/admin/scope/types.ts", + "name": "ScopesDetail", "description": "", "members": [ { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "description", - "value": "string", - "description": "The description of the app usage record." + "name": "granted", + "value": "string[]", + "description": "The scopes that have been granted on the shop for this app" }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "idempotencyKey", - "value": "string", - "description": "", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "price", - "value": "{ amount: number; currencyCode: string; }", - "description": "The price of the app usage record." + "name": "optional", + "value": "string[]", + "description": "The optional scopes that the app has declared in its configuration" }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "subscriptionLineItemId", - "value": "string", - "description": "", - "isOptional": true + "name": "required", + "value": "string[]", + "description": "The required scopes that the app has declared in its configuration" } ], - "value": "export interface CreateUsageRecordOptions {\n /**\n * The description of the app usage record.\n */\n description: string;\n /**\n * The price of the app usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest: boolean;\n /*\n * Defines the usage pricing plan the merchant is subscribed to.\n */\n subscriptionLineItemId?: string;\n /*\n * A unique key generated to avoid duplicate charges.\n */\n idempotencyKey?: string;\n}" + "value": "export interface ScopesDetail {\n /**\n * The scopes that have been granted on the shop for this app\n */\n granted: Scope[];\n /**\n * The required scopes that the app has declared in its configuration\n */\n required: Scope[];\n /**\n * The optional scopes that the app has declared in its configuration\n */\n optional: Scope[];\n}" }, - "UsageRecord": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "UsageRecord", + "ScopesRevokeResponse": { + "filePath": "src/server/authenticate/admin/scope/types.ts", + "name": "ScopesRevokeResponse", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "description", - "value": "string", - "description": "The description of the usage record." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the usage record." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "idempotencyKey", - "value": "string", - "description": "The idempotency key for this request.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "plan", - "value": "ActiveSubscriptionLineItem", - "description": "The subscription line item associated with the usage record." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "price", - "value": "{ amount: number; currencyCode: string; }", - "description": "The price and currency of the usage record." + "name": "revoked", + "value": "string[]", + "description": "The scopes that have been revoked on the shop for this app" } ], - "value": "export interface UsageRecord {\n /**\n * The ID of the usage record.\n */\n id: string;\n /**\n * The description of the usage record.\n */\n description: string;\n /**\n * The price and currency of the usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * The subscription line item associated with the usage record.\n */\n plan: ActiveSubscriptionLineItem;\n /**\n * The idempotency key for this request.\n */\n idempotencyKey?: string;\n}" - }, - "RequestBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "RequestBillingOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plan", - "value": "keyof Config[\"billing\"]", - "description": "The plan to request. Must be one of the values defined in the `billing` config option." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "returnUrl", - "value": "string", - "description": "The URL to return to after the merchant approves the payment.", - "isOptional": true + "value": "export interface ScopesRevokeResponse {\n /**\n * The scopes that have been revoked on the shop for this app\n */\n revoked: Scope[];\n}" + } + } + } + ], + "defaultExample": { + "description": "Authenticate, run API mutation, and redirect", + "codeblock": { + "title": "Authenticate, run API mutation, and redirect", + "tabs": [ + { + "title": "/app/routes/**.ts", + "language": "typescript", + "code": "import {type ActionFunctionArgs, json} from '@remix-run/node';\nimport {GraphqlQueryError} from '@shopify/shopify-api';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {admin, redirect} = await authenticate.admin(request);\n\n try {\n await admin.graphql(\n `#graphql\n mutation updateProductTitle($input: ProductInput!) {\n productUpdate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: {id: '123', title: 'New title'},\n },\n },\n );\n\n return redirect('/app/product-updated');\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n return json({errors: error.body?.errors}, {status: 500});\n }\n\n return new Response('Failed to update product title', {status: 500});\n }\n};\n" + } + ] + } + }, + "jsDocTypeExamples": [ + "EmbeddedAdminContext", + "AdminApiContext", + "BillingContext", + "ScopesApiContext" + ], + "related": [ + { + "name": "API context", + "subtitle": "Interact with the Admin API.", + "url": "/docs/api/shopify-app-remix/apis/admin-api" + }, + { + "name": "Billing context", + "subtitle": "Bill merchants for your app using the Admin API.", + "url": "/docs/api/shopify-app-remix/apis/billing" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "cors", + "examples": [ + { + "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "codeblock": { + "title": "Setting CORS headers for a admin request", + "tabs": [ + { + "title": "/app/routes/admin/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", + "language": "typescript" + } + ] } - ], - "value": "export interface RequestBillingOptions\n extends Omit<\n BillingRequestParams>,\n 'session' | 'plan' | 'returnObject'\n > {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" - }, - "RequireBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "RequireBillingOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "onFailure", - "value": "(error: any) => Promise", - "description": "How to handle the request if the shop doesn't have an active payment for any plan." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plans", - "value": "(keyof Config[\"billing\"])[]", - "description": "The plans to check for. Must be one of the values defined in the `billing` config option." + } + ] + }, + { + "title": "redirect", + "examples": [ + { + "description": "Use the `redirect` helper to safely redirect between pages.", + "codeblock": { + "title": "Redirecting to an app route", + "tabs": [ + { + "title": "/app/routes/admin/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\");\n};", + "language": "typescript" + } + ] } - ], - "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" - }, - "UpdateUsageCappedAmountOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "UpdateUsageCappedAmountOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "cappedAmount", - "value": "{ amount: number; currencyCode: string; }", - "description": "The maximum charge for the usage billing plan." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "subscriptionLineItemId", - "value": "string", - "description": "The subscription line item ID to update." + }, + { + "description": "Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.", + "codeblock": { + "title": "Redirecting outside of the Admin embedded app page", + "tabs": [ + { + "title": "/app/routes/admin/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\", { target: '_parent' });\n};", + "language": "typescript" + } + ] } - ], - "value": "export interface UpdateUsageCappedAmountOptions {\n /**\n * The subscription line item ID to update.\n */\n subscriptionLineItemId: string;\n /**\n * The maximum charge for the usage billing plan.\n */\n cappedAmount: {\n /**\n * The amount to update.\n */\n amount: number;\n /**\n * The currency code to update.\n */\n currencyCode: string;\n };\n}" - }, - "EnsureCORSFunction": { - "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", - "name": "EnsureCORSFunction", - "description": "", - "members": [], - "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" - }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", - "value": "string", - "description": "The access token for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." - } - ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" - }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user_scope", - "value": "string", - "description": "The effective set of scopes for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires_in", - "value": "number", - "description": "How long the access token is valid for, in seconds." - } - ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" - }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email", - "value": "string", - "description": "The user's email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email_verified", - "value": "boolean", - "description": "Whether the user has verified their email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "number", - "description": "The user's ID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "last_name", - "value": "string", - "description": "The user's last name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "locale", - "value": "string", - "description": "The user's locale." - } - ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" - }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." - } - ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" - }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "accessToken", - "value": "string", - "description": "The access token for the session.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "scope", - "value": "string", - "description": "The scopes for the access token.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - } - ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" - }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" - }, - "EmbeddedAdminContext": { - "filePath": "src/server/authenticate/admin/types.ts", - "name": "EmbeddedAdminContext", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." - }, - { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "billing", - "value": "BillingContext", - "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" - }, - { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", - "examples": [ - { - "title": "Setting CORS headers for a admin request", - "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "redirect", - "value": "RedirectFunction", - "description": "A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded apps.\n\nReturned only if `isEmbeddedApp` is `true`.", - "examples": [ - { - "title": "Redirecting to an app route", - "description": "Use the `redirect` helper to safely redirect between pages.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\");\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - }, - { - "title": "Redirecting outside of the Admin embedded app page", - "description": "Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\", { target: '_parent' });\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", - "examples": [ - { - "title": "Using offline sessions", - "description": "Get your app's shop-specific data using an offline session.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - }, - { - "title": "Using online sessions", - "description": "Get your app's user-specific data using an online session.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "sessionToken", - "value": "JwtPayload", - "description": "The decoded and validated session token for the request.\n\nReturned only if `isEmbeddedApp` is `true`.\n\n\n\n\n", - "examples": [ - { - "title": "Using the decoded session token", - "description": "Get user-specific data using the `sessionToken` object.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.admin(\n request\n );\n return json(await getMyAppData({user: sessionToken.sub}));\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] - } - ], - "value": "export interface EmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {\n /**\n * The decoded and validated session token for the request.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload}\n *\n * @example\n * Using the decoded session token.\n * Get user-specific data using the `sessionToken` object.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.admin(\n * request\n * );\n * return json(await getMyAppData({user: sessionToken.sub}));\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * useOnlineTokens: true,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded\n * apps.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * @example\n * Redirecting to an app route.\n * Use the `redirect` helper to safely redirect between pages.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\");\n * };\n * ```\n *\n * @example\n * Redirecting outside of the Admin embedded app page.\n * Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\", { target: '_parent' });\n * };\n * ```\n */\n redirect: RedirectFunction;\n}" - }, - "RedirectFunction": { - "filePath": "src/server/authenticate/admin/helpers/redirect.ts", - "name": "RedirectFunction", - "description": "", - "params": [ - { - "name": "url", - "description": "", - "value": "string", - "filePath": "src/server/authenticate/admin/helpers/redirect.ts" - }, - { - "name": "init", - "description": "", - "value": "RedirectInit", - "isOptional": true, - "filePath": "src/server/authenticate/admin/helpers/redirect.ts" - } - ], - "returns": { - "filePath": "src/server/authenticate/admin/helpers/redirect.ts", - "description": "", - "name": "TypedResponse", - "value": "TypedResponse" - }, - "value": "export type RedirectFunction = (\n url: string,\n init?: RedirectInit,\n) => TypedResponse;" - }, - "RedirectInit": { - "filePath": "src/server/authenticate/admin/helpers/redirect.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RedirectInit", - "value": "number | (ResponseInit & {target?: RedirectTarget})", - "description": "" - }, - "RedirectTarget": { - "filePath": "src/server/authenticate/admin/helpers/redirect.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RedirectTarget", - "value": "'_self' | '_parent' | '_top' | '_blank'", - "description": "" - }, - "JwtPayload": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "JwtPayload", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "aud", - "value": "string", - "description": "The client ID of the receiving app." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "dest", - "value": "string", - "description": "The shop's domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "exp", - "value": "number", - "description": "When the session token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "iat", - "value": "number", - "description": "When the session token was issued." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "iss", - "value": "string", - "description": "The shop's admin domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "jti", - "value": "string", - "description": "A secure random UUID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "nbf", - "value": "number", - "description": "When the session token activates." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "sid", - "value": "string", - "description": "A unique session ID per user and app." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "sub", - "value": "string", - "description": "The User that the session token is intended for." - } - ], - "value": "export interface JwtPayload {\n /**\n * The shop's admin domain.\n */\n iss: string;\n /**\n * The shop's domain.\n */\n dest: string;\n /**\n * The client ID of the receiving app.\n */\n aud: string;\n /**\n * The User that the session token is intended for.\n */\n sub: string;\n /**\n * When the session token expires.\n */\n exp: number;\n /**\n * When the session token activates.\n */\n nbf: number;\n /**\n * When the session token was issued.\n */\n iat: number;\n /**\n * A secure random UUID.\n */\n jti: string;\n /**\n * A unique session ID per user and app.\n */\n sid: string;\n}" - }, - "ScopesContext": { - "filePath": "src/server/authenticate/admin/types.ts", - "name": "ScopesContext", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "scopes", - "value": "ScopesApiContext", - "description": "Methods to manage scopes for the store that made the request." - } - ], - "value": "export interface ScopesContext {\n /**\n * Methods to manage scopes for the store that made the request.\n */\n scopes: ScopesApiContext;\n}" - }, - "ScopesApiContext": { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "name": "ScopesApiContext", - "description": "The Scopes API enables embedded apps and extensions to request merchant consent for access scopes.", - "members": [ - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "query", - "value": "() => Promise", - "description": "Queries Shopify for the scopes for this app on this shop", - "examples": [ - { - "title": "Query for granted scopes", - "description": "Call `scopes.query` to get scope details.", - "tabs": [ - { - "code": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const scopesDetail = await scopes.query();\n\n return json({\n hasWriteProducts: scopesDetail.granted.includes('write_products'),\n });\n};\n\nexport default function Index() {\n const {hasWriteProducts} = useLoaderData();\n\n ...\n}", - "title": "/app._index.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "request", - "value": "(scopes: string[]) => Promise", - "description": "Requests the merchant to grant the provided scopes for this app on this shop\n\nWarning: This method performs a server-side redirect.", - "examples": [ - { - "title": "Request consent from the merchant to grant the provided scopes for this app", - "description": "Call `scopes.request` to request optional scopes.", - "tabs": [ - { - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST a request to for optional scopes\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRequest = body.getAll(\"scopes\") as string[];\n\n // If the scopes are not already granted, a full page redirect to the request URL occurs\n await scopes.request(scopesToRequest);\n // otherwise return an empty response\n return json({});\n};\n\nexport default function Index() {\n const fetcher = useFetcher();\n\n const handleRequest = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", - "title": "/app/routes/app.request.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "revoke", - "value": "(scopes: string[]) => Promise", - "description": "Revokes the provided scopes from this app on this shop\n\nWarning: This method throws an [error](https://shopify.dev/docs/api/admin-graphql/unstable/objects/AppRevokeAccessScopesAppRevokeScopeError) if the provided optional scopes contains a required scope.", - "examples": [ - { - "title": "Revoke optional scopes", - "description": "Call `scopes.revoke` to revoke optional scopes.", - "tabs": [ - { - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST optional scopes to revoke\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRevoke = body.getAll(\"scopes\") as string[];\n\n const revokedResponse = await scopes.revoke(scopesToRevoke);\n\n return json(revokedResponse);\n};\n\nexport default function Index() {\n const fetcher = useFetcher();\n\n const handleRevoke = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", - "title": "/app._index.tsx" - } - ] - } - ] - } - ], - "value": "export interface ScopesApiContext {\n /**\n * Queries Shopify for the scopes for this app on this shop\n *\n * @returns {ScopesDetail} The scope details.\n *\n * @example\n * Query for granted scopes.\n * Call `scopes.query` to get scope details.\n * ```ts\n * // /app._index.tsx\n * import type { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { useLoaderData } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const scopesDetail = await scopes.query();\n *\n * return json({\n * hasWriteProducts: scopesDetail.granted.includes('write_products'),\n * });\n * };\n *\n * export default function Index() {\n * const {hasWriteProducts} = useLoaderData();\n *\n * ...\n * }\n * ```\n */\n query: () => Promise;\n\n /**\n * Requests the merchant to grant the provided scopes for this app on this shop\n *\n * Warning: This method performs a server-side redirect.\n *\n * @example\n * Request consent from the merchant to grant the provided scopes for this app.\n * Call `scopes.request` to request optional scopes.\n * ```ts\n * // /app/routes/app.request.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { useFetcher } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * // Example of an action to POST a request to for optional scopes\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const body = await request.formData();\n * const scopesToRequest = body.getAll(\"scopes\") as string[];\n *\n * // If the scopes are not already granted, a full page redirect to the request URL occurs\n * await scopes.request(scopesToRequest);\n * // otherwise return an empty response\n * return json({});\n * };\n *\n * export default function Index() {\n * const fetcher = useFetcher();\n *\n * const handleRequest = () => {\n * fetcher.submit({scopes: [\"write_products\"]}, {\n * method: \"POST\",\n * });\n * };\n *\n * ...\n * }\n * ```\n */\n request: (scopes: Scope[]) => Promise;\n\n /**\n * Revokes the provided scopes from this app on this shop\n *\n * 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.\n *\n * @example\n * Revoke optional scopes.\n * Call `scopes.revoke` to revoke optional scopes.\n * ```ts\n * // /app._index.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { useFetcher } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * // Example of an action to POST optional scopes to revoke\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const body = await request.formData();\n * const scopesToRevoke = body.getAll(\"scopes\") as string[];\n *\n * const revokedResponse = await scopes.revoke(scopesToRevoke);\n *\n * return json(revokedResponse);\n * };\n *\n * export default function Index() {\n * const fetcher = useFetcher();\n *\n * const handleRevoke = () => {\n * fetcher.submit({scopes: [\"write_products\"]}, {\n * method: \"POST\",\n * });\n * };\n *\n * ...\n * }\n * ```\n */\n revoke: (scopes: Scope[]) => Promise;\n}" - }, - "ScopesDetail": { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "name": "ScopesDetail", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "granted", - "value": "string[]", - "description": "The scopes that have been granted on the shop for this app" - }, - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "optional", - "value": "string[]", - "description": "The optional scopes that the app has declared in its configuration" - }, - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "required", - "value": "string[]", - "description": "The required scopes that the app has declared in its configuration" - } - ], - "value": "export interface ScopesDetail {\n /**\n * The scopes that have been granted on the shop for this app\n */\n granted: Scope[];\n /**\n * The required scopes that the app has declared in its configuration\n */\n required: Scope[];\n /**\n * The optional scopes that the app has declared in its configuration\n */\n optional: Scope[];\n}" - }, - "ScopesRevokeResponse": { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "name": "ScopesRevokeResponse", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "revoked", - "value": "string[]", - "description": "The scopes that have been revoked on the shop for this app" - } - ], - "value": "export interface ScopesRevokeResponse {\n /**\n * The scopes that have been revoked on the shop for this app\n */\n revoked: Scope[];\n}" - } - } - } - ], - "defaultExample": { - "description": "Authenticate, run API mutation, and redirect", - "codeblock": { - "title": "Authenticate, run API mutation, and redirect", - "tabs": [ - { - "title": "/app/routes/**.ts", - "language": "typescript", - "code": "import {type ActionFunctionArgs, json} from '@remix-run/node';\nimport {GraphqlQueryError} from '@shopify/shopify-api';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {admin, redirect} = await authenticate.admin(request);\n\n try {\n await admin.graphql(\n `#graphql\n mutation updateProductTitle($input: ProductInput!) {\n productUpdate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: {id: '123', title: 'New title'},\n },\n },\n );\n\n return redirect('/app/product-updated');\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n return json({errors: error.body?.errors}, {status: 500});\n }\n\n return new Response('Failed to update product title', {status: 500});\n }\n};\n" - } - ] - } - }, - "jsDocTypeExamples": [ - "EmbeddedAdminContext", - "AdminApiContext", - "BillingContext", - "ScopesApiContext" - ], - "related": [ - { - "name": "API context", - "subtitle": "Interact with the Admin API.", - "url": "/docs/api/shopify-app-remix/apis/admin-api" - }, - { - "name": "Billing context", - "subtitle": "Bill merchants for your app using the Admin API.", - "url": "/docs/api/shopify-app-remix/apis/billing" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "cors", - "examples": [ - { - "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", - "codeblock": { - "title": "Setting CORS headers for a admin request", - "tabs": [ - { - "title": "/app/routes/admin/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "redirect", - "examples": [ - { - "description": "Use the `redirect` helper to safely redirect between pages.", - "codeblock": { - "title": "Redirecting to an app route", - "tabs": [ - { - "title": "/app/routes/admin/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\");\n};", - "language": "typescript" - } - ] - } - }, - { - "description": "Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.", - "codeblock": { - "title": "Redirecting outside of the Admin embedded app page", - "tabs": [ - { - "title": "/app/routes/admin/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\", { target: '_parent' });\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "session", - "examples": [ - { - "description": "Get your app's shop-specific data using an offline session.", - "codeblock": { - "title": "Using offline sessions", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "description": "Get your app's user-specific data using an online session.", - "codeblock": { - "title": "Using online sessions", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "sessionToken", - "examples": [ - { - "description": "Get user-specific data using the `sessionToken` object.", - "codeblock": { - "title": "Using the decoded session token", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.admin(\n request\n );\n return json(await getMyAppData({user: sessionToken.sub}));\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "graphql", - "examples": [ - { - "description": "Use `admin.graphql` to make query / mutation requests.", - "codeblock": { - "title": "Querying the GraphQL API", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" },\n },\n },\n );\n\n const productData = await response.json();\n return json({\n productId: productData.data?.productCreate?.product?.id,\n });\n}", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", - "codeblock": { - "title": "Handling GraphQL errors", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n try {\n const response = await admin.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // error.body.errors:\n // { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "rest", - "examples": [ - { - "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.", - "codeblock": { - "title": "Using REST resources", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n return json(\n admin.rest.resources.Order.count({ session }),\n );\n};", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", - "codeblock": { - "title": "Performing a GET request to the REST API", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n const response = await admin.rest.get({\n path: \"/customers/count.json\",\n });\n const customers = await response.json();\n\n return json({ customers });\n};", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", - "codeblock": { - "title": "Performing a POST request to the REST API", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n });\n\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "cancel", - "examples": [ - { - "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", - "codeblock": { - "title": "Cancelling a subscription", - "tabs": [ - { - "title": "/app/routes/cancel-subscription.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "check", - "examples": [ - { - "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.", - "codeblock": { - "title": "Check what billing plans a merchant is subscribed to", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check({\n plans: [MONTHLY_PLAN],\n isTest: false,\n });\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "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.", - "codeblock": { - "title": "Check for payments without filtering", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check();\n // This will be true if any payment is found\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "createUsageRecord", - "examples": [ - { - "description": "Create a usage record for the active usage billing plan", - "codeblock": { - "title": "Creating a usage record", - "tabs": [ - { - "title": "/app/routes/create-usage-record.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n const chargeBilling = await billing.createUsageRecord({\n description: \"Usage record for product creation\",\n price: {\n amount: 1,\n currencyCode: \"USD\",\n },\n isTest: true,\n });\n console.log(chargeBilling);\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "request", - "examples": [ - { - "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", - "codeblock": { - "title": "Using a custom return URL", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "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.", - "codeblock": { - "title": "Overriding plan settings", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n trialDays: 14,\n lineItems: [\n {\n interval: BillingInterval.Every30Days,\n discount: { value: { percentage: 0.1 } },\n },\n ],\n }),\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "require", - "examples": [ - { - "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", - "codeblock": { - "title": "Requesting billing right away", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "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.", - "codeblock": { - "title": "Redirect to a plan selection page", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "updateUsageCappedAmount", - "examples": [ - { - "description": "Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.", - "codeblock": { - "title": "Updating the capped amount for a usage billing plan", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n await billing.updateUsageCappedAmount({\n subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n cappedAmount: {\n amount: 10,\n currencyCode: \"USD\"\n },\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n terms: \"Usage based\"\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "query", - "examples": [ - { - "description": "Call `scopes.query` to get scope details.", - "codeblock": { - "title": "Query for granted scopes", - "tabs": [ - { - "title": "/app._index.tsx", - "code": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const scopesDetail = await scopes.query();\n\n return json({\n hasWriteProducts: scopesDetail.granted.includes('write_products'),\n });\n};\n\nexport default function Index() {\n const {hasWriteProducts} = useLoaderData<typeof loader>();\n\n ...\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "request", - "examples": [ - { - "description": "Call `scopes.request` to request optional scopes.", - "codeblock": { - "title": "Request consent from the merchant to grant the provided scopes for this app", - "tabs": [ - { - "title": "/app/routes/app.request.tsx", - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST a request to for optional scopes\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRequest = body.getAll(\"scopes\") as string[];\n\n // If the scopes are not already granted, a full page redirect to the request URL occurs\n await scopes.request(scopesToRequest);\n // otherwise return an empty response\n return json({});\n};\n\nexport default function Index() {\n const fetcher = useFetcher<typeof action>();\n\n const handleRequest = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "revoke", - "examples": [ - { - "description": "Call `scopes.revoke` to revoke optional scopes.", - "codeblock": { - "title": "Revoke optional scopes", - "tabs": [ - { - "title": "/app._index.tsx", - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST optional scopes to revoke\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRevoke = body.getAll(\"scopes\") as string[];\n\n const revokedResponse = await scopes.revoke(scopesToRevoke);\n\n return json(revokedResponse);\n};\n\nexport default function Index() {\n const fetcher = useFetcher<typeof action>();\n\n const handleRevoke = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Billing", - "description": "Contains function used to bill merchants for your app.\n\nThis object is returned on authenticated Admin requests.", - "category": "APIs", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "billing", - "description": "Provides utilities that apps can use to request billing for the app using the Admin API.", - "type": "BillingContext", - "typeDefinitions": { - "BillingContext": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "BillingContext", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "cancel", - "value": "(options: CancelBillingOptions) => Promise", - "description": "Cancels an ongoing subscription, given its ID.", - "examples": [ - { - "title": "Cancelling a subscription", - "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", - "title": "/app/routes/cancel-subscription.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "check", - "value": ">(options?: Options) => Promise", - "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", - "examples": [ - { - "title": "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.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check({\n plans: [MONTHLY_PLAN],\n isTest: false,\n });\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - }, - { - "title": "Check for payments without filtering", - "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.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check();\n // This will be true if any payment is found\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "createUsageRecord", - "value": "(options: CreateUsageRecordOptions) => Promise", - "description": "Creates a usage record for an app subscription.", - "examples": [ - { - "title": "Creating a usage record", - "description": "Create a usage record for the active usage billing plan", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n const chargeBilling = await billing.createUsageRecord({\n description: \"Usage record for product creation\",\n price: {\n amount: 1,\n currencyCode: \"USD\",\n },\n isTest: true,\n });\n console.log(chargeBilling);\n\n // App logic\n};", - "title": "/app/routes/create-usage-record.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "request", - "value": "(options: RequestBillingOptions) => Promise", - "description": "Requests payment for the plan.", - "examples": [ - { - "title": "Using a custom return URL", - "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - }, - { - "title": "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.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n trialDays: 14,\n lineItems: [\n {\n interval: BillingInterval.Every30Days,\n discount: { value: { percentage: 0.1 } },\n },\n ],\n }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "require", - "value": "(options: RequireBillingOptions) => Promise", - "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", - "examples": [ - { - "title": "Requesting billing right away", - "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - }, - { - "title": "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.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "updateUsageCappedAmount", - "value": "(options: UpdateUsageCappedAmountOptions) => Promise", - "description": "Updates the capped amount for a usage billing plan.", - "examples": [ - { - "title": "Updating the capped amount for a usage billing plan", - "description": "Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n await billing.updateUsageCappedAmount({\n subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n cappedAmount: {\n amount: 10,\n currencyCode: \"USD\"\n },\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n terms: \"Usage based\"\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] - } - ], - "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Redirect to a plan selection page.\n * 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.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Check what billing plans a merchant is subscribed to.\n * Use billing.check if you want to determine which plans are in use. Unlike `require`, `check` does not\n * throw an error if no active billing plans are present. \n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const { hasActivePayment, appSubscriptions } = await billing.check({\n * plans: [MONTHLY_PLAN],\n * isTest: false,\n * });\n * console.log(hasActivePayment);\n * console.log(appSubscriptions);\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Check for payments without filtering.\n * Use billing.check to see if any payments exist for the store, regardless of whether it's a test or\n * matches one or more plans.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const { hasActivePayment, appSubscriptions } = await billing.check();\n * // This will be true if any payment is found\n * console.log(hasActivePayment);\n * console.log(appSubscriptions);\n * };\n * ```\n */\n check: >(\n options?: Options,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Overriding plan settings.\n * 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.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * trialDays: 14,\n * lineItems: [\n * {\n * interval: BillingInterval.Every30Days,\n * discount: { value: { percentage: 0.1 } },\n * },\n * ],\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n\n /**\n * Creates a usage record for an app subscription.\n *\n * @returns Returns a usage record when one was created successfully.\n *\n * @example\n * Creating a usage record\n * Create a usage record for the active usage billing plan\n * ```ts\n * // /app/routes/create-usage-record.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n *\n * const chargeBilling = await billing.createUsageRecord({\n * description: \"Usage record for product creation\",\n * price: {\n * amount: 1,\n * currencyCode: \"USD\",\n * },\n * isTest: true,\n * });\n * console.log(chargeBilling);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const USAGE_PLAN = 'Usage subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [USAGE_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Usage,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n createUsageRecord: (\n options: CreateUsageRecordOptions,\n ) => Promise;\n\n /**\n * Updates the capped amount for a usage billing plan.\n *\n * @returns Redirects to a confirmation page to update the usage billing plan.\n *\n * @example\n * Updating the capped amount for a usage billing plan.\n * Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n *\n * await billing.updateUsageCappedAmount({\n * subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n * cappedAmount: {\n * amount: 10,\n * currencyCode: \"USD\"\n * },\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const USAGE_PLAN = 'Usage subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [USAGE_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Usage,\n * terms: \"Usage based\"\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n updateUsageCappedAmount: (\n options: UpdateUsageCappedAmountOptions,\n ) => Promise;\n}" - }, - "CancelBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "CancelBillingOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "prorate", - "value": "boolean", - "description": "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.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "subscriptionId", - "value": "string", - "description": "The ID of the subscription to cancel." - } - ], - "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to issue prorated credits for the unused portion of the app subscription. There will be a corresponding\n * deduction (based on revenue share) to your Partner account. For example, if a $10.00 app subscription\n * (with 0% revenue share) is cancelled and prorated half way through the billing cycle, then the merchant will be\n * credited $5.00 and that amount will be deducted from your Partner account.\n */\n prorate?: boolean;\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest?: boolean;\n}" - }, - "AppSubscription": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppSubscription", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the app subscription." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "lineItems", - "value": "ActiveSubscriptionLineItem[]", - "description": "", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "name", - "value": "string", - "description": "The name of the purchased plan." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "status", - "value": "string", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "test", - "value": "boolean", - "description": "Whether this is a test subscription." - } - ], - "value": "export interface AppSubscription {\n /**\n * The ID of the app subscription.\n */\n id: string;\n /**\n * The name of the purchased plan.\n */\n name: string;\n /**\n * Whether this is a test subscription.\n */\n test: boolean;\n lineItems?: ActiveSubscriptionLineItem[];\n status: string;\n}" - }, - "ActiveSubscriptionLineItem": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "ActiveSubscriptionLineItem", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "plan", - "value": "AppPlan", - "description": "" - } - ], - "value": "export interface ActiveSubscriptionLineItem {\n id: string;\n plan: AppPlan;\n}" - }, - "AppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppPlan", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "pricingDetails", - "value": "RecurringAppPlan | UsageAppPlan", - "description": "" - } - ], - "value": "export interface AppPlan {\n pricingDetails: RecurringAppPlan | UsageAppPlan;\n}" - }, - "RecurringAppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "RecurringAppPlan", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "discount", - "value": "AppPlanDiscount", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "interval", - "value": "BillingInterval.Every30Days | BillingInterval.Annual", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "price", - "value": "Money", - "description": "" - } - ], - "value": "export interface RecurringAppPlan {\n interval: BillingInterval.Every30Days | BillingInterval.Annual;\n price: Money;\n discount: AppPlanDiscount;\n}" - }, - "AppPlanDiscount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppPlanDiscount", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "durationLimitInIntervals", - "value": "number", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "priceAfterDiscount", - "value": "Money", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "remainingDurationInIntervals", - "value": "number", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "value", - "value": "AppPlanDiscountAmount", - "description": "" - } - ], - "value": "export interface AppPlanDiscount {\n durationLimitInIntervals: number;\n remainingDurationInIntervals: number;\n priceAfterDiscount: Money;\n value: AppPlanDiscountAmount;\n}" - }, - "Money": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "Money", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "currencyCode", - "value": "string", - "description": "" - } - ], - "value": "export interface Money {\n amount: number;\n currencyCode: string;\n}" - }, - "AppPlanDiscountAmount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "AppPlanDiscountAmount", - "value": "BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage", - "description": "" - }, - "BillingConfigSubscriptionPlanDiscountAmount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionPlanDiscountAmount", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "The amount to discount.\n\nCannot be set if `percentage` is set." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "percentage", - "value": "never", - "description": "The percentage to discount.\n\nCannot be set if `amount` is set.", - "isOptional": true - } - ], - "value": "export interface BillingConfigSubscriptionPlanDiscountAmount {\n /**\n * The amount to discount.\n *\n * Cannot be set if `percentage` is set.\n */\n amount: number;\n /**\n * The percentage to discount.\n *\n * Cannot be set if `amount` is set.\n */\n percentage?: never;\n}" - }, - "BillingConfigSubscriptionPlanDiscountPercentage": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionPlanDiscountPercentage", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "amount", - "value": "never", - "description": "The amount to discount.\n\nCannot be set if `percentage` is set.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "percentage", - "value": "number", - "description": "The percentage to discount.\n\nCannot be set if `amount` is set." - } - ], - "value": "export interface BillingConfigSubscriptionPlanDiscountPercentage {\n /**\n * The amount to discount.\n *\n * Cannot be set if `percentage` is set.\n */\n amount?: never;\n /**\n * The percentage to discount.\n *\n * Cannot be set if `amount` is set.\n */\n percentage: number;\n}" - }, - "BillingInterval": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "BillingInterval", - "value": "export declare enum BillingInterval {\n OneTime = \"ONE_TIME\",\n Every30Days = \"EVERY_30_DAYS\",\n Annual = \"ANNUAL\",\n Usage = \"USAGE\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "OneTime", - "value": "ONE_TIME" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Every30Days", - "value": "EVERY_30_DAYS" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Annual", - "value": "ANNUAL" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Usage", - "value": "USAGE" - } - ] - }, - "UsageAppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "UsageAppPlan", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "balanceUsed", - "value": "Money", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "cappedAmount", - "value": "Money", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "terms", - "value": "string", - "description": "" - } - ], - "value": "export interface UsageAppPlan {\n balanceUsed: Money;\n cappedAmount: Money;\n terms: string;\n}" - }, - "CheckBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "CheckBillingOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plans", - "value": "(keyof Config[\"billing\"])[]", - "description": "The plans to check for. Must be one of the values defined in the `billing` config option.", - "isOptional": true - } - ], - "value": "export interface CheckBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans?: (keyof Config['billing'])[];\n}" - }, - "BillingCheckResponseObject": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingCheckResponseObject", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "appSubscriptions", - "value": "AppSubscription[]", - "description": "The active subscriptions the shop has." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "hasActivePayment", - "value": "boolean", - "description": "Whether the user has an active payment method." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "oneTimePurchases", - "value": "OneTimePurchase[]", - "description": "The one-time purchases the shop has." - } - ], - "value": "export interface BillingCheckResponseObject {\n /**\n * Whether the user has an active payment method.\n */\n hasActivePayment: boolean;\n /**\n * The one-time purchases the shop has.\n */\n oneTimePurchases: OneTimePurchase[];\n /**\n * The active subscriptions the shop has.\n */\n appSubscriptions: AppSubscription[];\n}" - }, - "OneTimePurchase": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "OneTimePurchase", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the one-time purchase." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "name", - "value": "string", - "description": "The name of the purchased plan." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "status", - "value": "string", - "description": "The status of the one-time purchase." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "test", - "value": "boolean", - "description": "Whether this is a test purchase." - } - ], - "value": "export interface OneTimePurchase {\n /**\n * The ID of the one-time purchase.\n */\n id: string;\n /**\n * The name of the purchased plan.\n */\n name: string;\n /**\n * Whether this is a test purchase.\n */\n test: boolean;\n /**\n * The status of the one-time purchase.\n */\n status: string;\n}" - }, - "CreateUsageRecordOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "CreateUsageRecordOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "description", - "value": "string", - "description": "The description of the app usage record." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "idempotencyKey", - "value": "string", - "description": "", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "price", - "value": "{ amount: number; currencyCode: string; }", - "description": "The price of the app usage record." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "subscriptionLineItemId", - "value": "string", - "description": "", - "isOptional": true - } - ], - "value": "export interface CreateUsageRecordOptions {\n /**\n * The description of the app usage record.\n */\n description: string;\n /**\n * The price of the app usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest: boolean;\n /*\n * Defines the usage pricing plan the merchant is subscribed to.\n */\n subscriptionLineItemId?: string;\n /*\n * A unique key generated to avoid duplicate charges.\n */\n idempotencyKey?: string;\n}" - }, - "UsageRecord": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "UsageRecord", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "description", - "value": "string", - "description": "The description of the usage record." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the usage record." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "idempotencyKey", - "value": "string", - "description": "The idempotency key for this request.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "plan", - "value": "ActiveSubscriptionLineItem", - "description": "The subscription line item associated with the usage record." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "price", - "value": "{ amount: number; currencyCode: string; }", - "description": "The price and currency of the usage record." - } - ], - "value": "export interface UsageRecord {\n /**\n * The ID of the usage record.\n */\n id: string;\n /**\n * The description of the usage record.\n */\n description: string;\n /**\n * The price and currency of the usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * The subscription line item associated with the usage record.\n */\n plan: ActiveSubscriptionLineItem;\n /**\n * The idempotency key for this request.\n */\n idempotencyKey?: string;\n}" - }, - "RequestBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "RequestBillingOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plan", - "value": "keyof Config[\"billing\"]", - "description": "The plan to request. Must be one of the values defined in the `billing` config option." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "returnUrl", - "value": "string", - "description": "The URL to return to after the merchant approves the payment.", - "isOptional": true - } - ], - "value": "export interface RequestBillingOptions\n extends Omit<\n BillingRequestParams>,\n 'session' | 'plan' | 'returnObject'\n > {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" - }, - "RequireBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "RequireBillingOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "onFailure", - "value": "(error: any) => Promise", - "description": "How to handle the request if the shop doesn't have an active payment for any plan." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plans", - "value": "(keyof Config[\"billing\"])[]", - "description": "The plans to check for. Must be one of the values defined in the `billing` config option." - } - ], - "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" - }, - "UpdateUsageCappedAmountOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "UpdateUsageCappedAmountOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "cappedAmount", - "value": "{ amount: number; currencyCode: string; }", - "description": "The maximum charge for the usage billing plan." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "subscriptionLineItemId", - "value": "string", - "description": "The subscription line item ID to update." - } - ], - "value": "export interface UpdateUsageCappedAmountOptions {\n /**\n * The subscription line item ID to update.\n */\n subscriptionLineItemId: string;\n /**\n * The maximum charge for the usage billing plan.\n */\n cappedAmount: {\n /**\n * The amount to update.\n */\n amount: number;\n /**\n * The currency code to update.\n */\n currencyCode: string;\n };\n}" - } - } - } - ], - "jsDocTypeExamples": [ - "BillingContext" - ], - "related": [ - { - "name": "Admin context", - "subtitle": "Authenticate requests from Shopify Admin.", - "url": "/docs/api/shopify-app-remix/authenticate/admin" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "cancel", - "examples": [ - { - "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", - "codeblock": { - "title": "Cancelling a subscription", - "tabs": [ - { - "title": "/app/routes/cancel-subscription.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "check", - "examples": [ - { - "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.", - "codeblock": { - "title": "Check what billing plans a merchant is subscribed to", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check({\n plans: [MONTHLY_PLAN],\n isTest: false,\n });\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "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.", - "codeblock": { - "title": "Check for payments without filtering", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check();\n // This will be true if any payment is found\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "createUsageRecord", - "examples": [ - { - "description": "Create a usage record for the active usage billing plan", - "codeblock": { - "title": "Creating a usage record", - "tabs": [ - { - "title": "/app/routes/create-usage-record.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n const chargeBilling = await billing.createUsageRecord({\n description: \"Usage record for product creation\",\n price: {\n amount: 1,\n currencyCode: \"USD\",\n },\n isTest: true,\n });\n console.log(chargeBilling);\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "request", - "examples": [ - { - "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", - "codeblock": { - "title": "Using a custom return URL", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "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.", - "codeblock": { - "title": "Overriding plan settings", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n trialDays: 14,\n lineItems: [\n {\n interval: BillingInterval.Every30Days,\n discount: { value: { percentage: 0.1 } },\n },\n ],\n }),\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "require", - "examples": [ - { - "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", - "codeblock": { - "title": "Requesting billing right away", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "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.", - "codeblock": { - "title": "Redirect to a plan selection page", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "updateUsageCappedAmount", - "examples": [ - { - "description": "Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.", - "codeblock": { - "title": "Updating the capped amount for a usage billing plan", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n await billing.updateUsageCappedAmount({\n subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n cappedAmount: {\n amount: 10,\n currencyCode: \"USD\"\n },\n });\n\n // App logic\n};", - "language": "typescript" - }, - { - "title": "shopify.server.ts", - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n terms: \"Usage based\"\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Scopes", - "description": "Contains functions used to manage scopes for your app.\n\nThis object is returned on authenticated Admin requests.", - "category": "APIs", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "scopes", - "description": "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.", - "type": "ScopesApiContext", - "typeDefinitions": { - "ScopesApiContext": { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "name": "ScopesApiContext", - "description": "The Scopes API enables embedded apps and extensions to request merchant consent for access scopes.", - "members": [ - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "query", - "value": "() => Promise", - "description": "Queries Shopify for the scopes for this app on this shop", - "examples": [ - { - "title": "Query for granted scopes", - "description": "Call `scopes.query` to get scope details.", - "tabs": [ - { - "code": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const scopesDetail = await scopes.query();\n\n return json({\n hasWriteProducts: scopesDetail.granted.includes('write_products'),\n });\n};\n\nexport default function Index() {\n const {hasWriteProducts} = useLoaderData();\n\n ...\n}", - "title": "/app._index.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "request", - "value": "(scopes: string[]) => Promise", - "description": "Requests the merchant to grant the provided scopes for this app on this shop\n\nWarning: This method performs a server-side redirect.", - "examples": [ - { - "title": "Request consent from the merchant to grant the provided scopes for this app", - "description": "Call `scopes.request` to request optional scopes.", - "tabs": [ - { - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST a request to for optional scopes\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRequest = body.getAll(\"scopes\") as string[];\n\n // If the scopes are not already granted, a full page redirect to the request URL occurs\n await scopes.request(scopesToRequest);\n // otherwise return an empty response\n return json({});\n};\n\nexport default function Index() {\n const fetcher = useFetcher();\n\n const handleRequest = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", - "title": "/app/routes/app.request.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "revoke", - "value": "(scopes: string[]) => Promise", - "description": "Revokes the provided scopes from this app on this shop\n\nWarning: This method throws an [error](https://shopify.dev/docs/api/admin-graphql/unstable/objects/AppRevokeAccessScopesAppRevokeScopeError) if the provided optional scopes contains a required scope.", - "examples": [ - { - "title": "Revoke optional scopes", - "description": "Call `scopes.revoke` to revoke optional scopes.", - "tabs": [ - { - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST optional scopes to revoke\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRevoke = body.getAll(\"scopes\") as string[];\n\n const revokedResponse = await scopes.revoke(scopesToRevoke);\n\n return json(revokedResponse);\n};\n\nexport default function Index() {\n const fetcher = useFetcher();\n\n const handleRevoke = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", - "title": "/app._index.tsx" - } - ] - } - ] - } - ], - "value": "export interface ScopesApiContext {\n /**\n * Queries Shopify for the scopes for this app on this shop\n *\n * @returns {ScopesDetail} The scope details.\n *\n * @example\n * Query for granted scopes.\n * Call `scopes.query` to get scope details.\n * ```ts\n * // /app._index.tsx\n * import type { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { useLoaderData } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const scopesDetail = await scopes.query();\n *\n * return json({\n * hasWriteProducts: scopesDetail.granted.includes('write_products'),\n * });\n * };\n *\n * export default function Index() {\n * const {hasWriteProducts} = useLoaderData();\n *\n * ...\n * }\n * ```\n */\n query: () => Promise;\n\n /**\n * Requests the merchant to grant the provided scopes for this app on this shop\n *\n * Warning: This method performs a server-side redirect.\n *\n * @example\n * Request consent from the merchant to grant the provided scopes for this app.\n * Call `scopes.request` to request optional scopes.\n * ```ts\n * // /app/routes/app.request.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { useFetcher } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * // Example of an action to POST a request to for optional scopes\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const body = await request.formData();\n * const scopesToRequest = body.getAll(\"scopes\") as string[];\n *\n * // If the scopes are not already granted, a full page redirect to the request URL occurs\n * await scopes.request(scopesToRequest);\n * // otherwise return an empty response\n * return json({});\n * };\n *\n * export default function Index() {\n * const fetcher = useFetcher();\n *\n * const handleRequest = () => {\n * fetcher.submit({scopes: [\"write_products\"]}, {\n * method: \"POST\",\n * });\n * };\n *\n * ...\n * }\n * ```\n */\n request: (scopes: Scope[]) => Promise;\n\n /**\n * Revokes the provided scopes from this app on this shop\n *\n * 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.\n *\n * @example\n * Revoke optional scopes.\n * Call `scopes.revoke` to revoke optional scopes.\n * ```ts\n * // /app._index.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { useFetcher } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * // Example of an action to POST optional scopes to revoke\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const body = await request.formData();\n * const scopesToRevoke = body.getAll(\"scopes\") as string[];\n *\n * const revokedResponse = await scopes.revoke(scopesToRevoke);\n *\n * return json(revokedResponse);\n * };\n *\n * export default function Index() {\n * const fetcher = useFetcher();\n *\n * const handleRevoke = () => {\n * fetcher.submit({scopes: [\"write_products\"]}, {\n * method: \"POST\",\n * });\n * };\n *\n * ...\n * }\n * ```\n */\n revoke: (scopes: Scope[]) => Promise;\n}" - }, - "ScopesDetail": { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "name": "ScopesDetail", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "granted", - "value": "string[]", - "description": "The scopes that have been granted on the shop for this app" - }, - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "optional", - "value": "string[]", - "description": "The optional scopes that the app has declared in its configuration" - }, - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "required", - "value": "string[]", - "description": "The required scopes that the app has declared in its configuration" - } - ], - "value": "export interface ScopesDetail {\n /**\n * The scopes that have been granted on the shop for this app\n */\n granted: Scope[];\n /**\n * The required scopes that the app has declared in its configuration\n */\n required: Scope[];\n /**\n * The optional scopes that the app has declared in its configuration\n */\n optional: Scope[];\n}" - }, - "ScopesRevokeResponse": { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "name": "ScopesRevokeResponse", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/scope/types.ts", - "syntaxKind": "PropertySignature", - "name": "revoked", - "value": "string[]", - "description": "The scopes that have been revoked on the shop for this app" - } - ], - "value": "export interface ScopesRevokeResponse {\n /**\n * The scopes that have been revoked on the shop for this app\n */\n revoked: Scope[];\n}" - } - } - } - ], - "jsDocTypeExamples": [ - "ScopesApiContext" - ], - "related": [ - { - "name": "Admin context", - "subtitle": "Authenticate requests from Shopify Admin.", - "url": "/docs/api/shopify-app-remix/authenticate/admin" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "query", - "examples": [ - { - "description": "Call `scopes.query` to get scope details.", - "codeblock": { - "title": "Query for granted scopes", - "tabs": [ - { - "title": "/app._index.tsx", - "code": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const scopesDetail = await scopes.query();\n\n return json({\n hasWriteProducts: scopesDetail.granted.includes('write_products'),\n });\n};\n\nexport default function Index() {\n const {hasWriteProducts} = useLoaderData<typeof loader>();\n\n ...\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "request", - "examples": [ - { - "description": "Call `scopes.request` to request optional scopes.", - "codeblock": { - "title": "Request consent from the merchant to grant the provided scopes for this app", - "tabs": [ - { - "title": "/app/routes/app.request.tsx", - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST a request to for optional scopes\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRequest = body.getAll(\"scopes\") as string[];\n\n // If the scopes are not already granted, a full page redirect to the request URL occurs\n await scopes.request(scopesToRequest);\n // otherwise return an empty response\n return json({});\n};\n\nexport default function Index() {\n const fetcher = useFetcher<typeof action>();\n\n const handleRequest = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "revoke", - "examples": [ - { - "description": "Call `scopes.revoke` to revoke optional scopes.", - "codeblock": { - "title": "Revoke optional scopes", - "tabs": [ - { - "title": "/app._index.tsx", - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST optional scopes to revoke\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRevoke = body.getAll(\"scopes\") as string[];\n\n const revokedResponse = await scopes.revoke(scopesToRevoke);\n\n return json(revokedResponse);\n};\n\nexport default function Index() {\n const fetcher = useFetcher<typeof action>();\n\n const handleRevoke = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Flow", - "description": "Contains functions for verifying Shopify Flow extensions.\n\nSee the [Flow documentation](https://shopify.dev/docs/apps/flow/actions/endpoints) for more information.", - "category": "Authenticate", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "authenticate.flow", - "description": "Verifies requests coming from Shopify Flow extensions.", - "type": "AuthenticateFlow", - "typeDefinitions": { - "AuthenticateFlow": { - "filePath": "src/server/authenticate/flow/types.ts", - "name": "AuthenticateFlow", - "description": "", - "params": [ - { - "name": "request", - "description": "", - "value": "Request", - "filePath": "src/server/authenticate/flow/types.ts" - } - ], - "returns": { - "filePath": "src/server/authenticate/flow/types.ts", - "description": "", - "name": "Promise>", - "value": "Promise>" - }, - "value": "export type AuthenticateFlow<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" - }, - "FlowContext": { - "filePath": "src/server/authenticate/flow/types.ts", - "name": "FlowContext", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/flow/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "An admin context for the Flow request.\n\nReturned only if there is a session for the shop.", - "examples": [ - { - "title": "Flow admin context", - "description": "Use the `admin` object in the context to interact with the Admin API.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.flow(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "/app/routes/flow.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/flow/types.ts", - "syntaxKind": "PropertySignature", - "name": "payload", - "value": "any", - "description": "The payload from the Flow request.", - "examples": [ - { - "title": "Flow payload", - "description": "Get the request's POST payload.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.flow(request);\n return new Response();\n};", - "title": "/app/routes/flow.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/flow/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop.", - "examples": [ - { - "title": "Shopify session for the Flow request", - "description": "Use the session associated with this request to use REST resources.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.flow(request);\n\n const products = await admin?.rest.resources.Product.all({ session });\n // Use products\n\n return new Response();\n};", - "title": "/app/routes/flow.tsx" - } - ] - } - ] - } - ], - "value": "export interface FlowContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Shopify session for the Flow request.\n * Use the session associated with this request to use REST resources.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session, admin } = await authenticate.flow(request);\n *\n * const products = await admin?.rest.resources.Product.all({ session });\n * // Use products\n *\n * return new Response();\n * };\n * ```\n */\n session: Session;\n\n /**\n * The payload from the Flow request.\n *\n * @example\n * Flow payload.\n * Get the request's POST payload.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { payload } = await authenticate.flow(request);\n * return new Response();\n * };\n * ```\n */\n payload: any;\n\n /**\n * An admin context for the Flow request.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Flow admin context.\n * Use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.flow(request);\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n}" - }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", - "value": "string", - "description": "The access token for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." - } - ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" - }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user_scope", - "value": "string", - "description": "The effective set of scopes for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires_in", - "value": "number", - "description": "How long the access token is valid for, in seconds." - } - ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" - }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email", - "value": "string", - "description": "The user's email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email_verified", - "value": "boolean", - "description": "Whether the user has verified their email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "number", - "description": "The user's ID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "last_name", - "value": "string", - "description": "The user's last name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "locale", - "value": "string", - "description": "The user's locale." - } - ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" - }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." - } - ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" - }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "accessToken", - "value": "string", - "description": "The access token for the session.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "scope", - "value": "string", - "description": "The scopes for the access token.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - } - ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" - }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" - } - } - } - ], - "defaultExample": { - "description": "Handle a flow action call", - "codeblock": { - "title": "Set a metafield on a customer after a flow call", - "tabs": [ - { - "title": "/app/routes/**.ts", - "language": "typescript", - "code": "import {type ActionFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {admin, payload} = await authenticate.flow(request);\n\n const customerId = payload.properties.customer_id;\n\n const response = await admin.graphql(\n `#graphql\n mutation setMetafield($customerId: ID!, $time: String!) {\n metafieldsSet(metafields: {\n ownerId: $customerId\n namespace: \"my-app\",\n key: \"last_flow_update\",\n value: $time,\n type: \"string\",\n }) {\n metafields {\n key\n value\n }\n }\n }\n `,\n {\n variables: {\n customerId,\n time: new Date().toISOString(),\n },\n },\n );\n const body = await response.json();\n\n console.log('Updated value', body.data!.metafieldsSet!.metafields![0].value);\n\n return new Response();\n};\n" - } - ] - } - }, - "jsDocTypeExamples": [ - "FlowContext" - ], - "related": [ - { - "name": "Admin API context", - "subtitle": "Interact with the Admin API.", - "url": "/docs/api/shopify-app-remix/apis/admin-api" - }, - { - "name": "Flow action endpoints", - "subtitle": "Receive requests from Flow.", - "url": "/docs/apps/flow/actions/endpoints", - "type": "shopify" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "admin", - "examples": [ - { - "description": "Use the `admin` object in the context to interact with the Admin API.", - "codeblock": { - "title": "Flow admin context", - "tabs": [ - { - "title": "/app/routes/flow.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.flow(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "payload", - "examples": [ - { - "description": "Get the request's POST payload.", - "codeblock": { - "title": "Flow payload", - "tabs": [ - { - "title": "/app/routes/flow.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.flow(request);\n return new Response();\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "session", - "examples": [ - { - "description": "Use the session associated with this request to use REST resources.", - "codeblock": { - "title": "Shopify session for the Flow request", - "tabs": [ - { - "title": "/app/routes/flow.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.flow(request);\n\n const products = await admin?.rest.resources.Product.all({ session });\n // Use products\n\n return new Response();\n};", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Fulfillment Service", - "description": "Contains functions for verifying fulfillment service requests.\n\nSee the [fulfillment service documentation](https://shopify.dev/docs/apps/fulfillment/fulfillment-service-apps) for more information.", - "category": "Authenticate", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "authenticate.fulfillmentService", - "description": "Verifies requests coming from Shopify to fulfillment service apps", - "type": "AuthenticateFulfillmentService", - "typeDefinitions": { - "AuthenticateFulfillmentService": { - "filePath": "src/server/authenticate/fulfillment-service/types.ts", - "name": "AuthenticateFulfillmentService", - "description": "", - "params": [ - { - "name": "request", - "description": "", - "value": "Request", - "filePath": "src/server/authenticate/fulfillment-service/types.ts" - } - ], - "returns": { - "filePath": "src/server/authenticate/fulfillment-service/types.ts", - "description": "", - "name": "Promise>", - "value": "Promise>" - }, - "value": "export type AuthenticateFulfillmentService<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" - }, - "FulfillmentServiceContext": { - "filePath": "src/server/authenticate/fulfillment-service/types.ts", - "name": "FulfillmentServiceContext", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/fulfillment-service/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "\nAn admin context for the fulfillment service request.\n\nReturned only if there is a session for the shop.", - "examples": [ - { - "title": "Shopify session for the fulfillment service request", - "description": "Use the session associated with this request to use the Admin GraphQL API", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.fulfillmentService(request);\n const response = await admin?.graphql(\n `#graphql\n query {\n shop {\n assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {\n edges {\n node {\n id\n destination {\n firstName\n lastName\n }\n lineItems(first: 10) {\n edges {\n node {\n id\n productTitle\n sku\n remainingQuantity\n }\n }\n }\n merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {\n edges {\n node {\n message\n }\n }\n }\n }\n }\n }\n }\n}`);\n\n const fulfillments = await response.json();\n return json({ data: fulfillments.data });\n}", - "title": "/app/routes/fulfillment_order_notification.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/fulfillment-service/types.ts", - "syntaxKind": "PropertySignature", - "name": "payload", - "value": "FulfillmentServicePayload", - "description": "The payload from the fulfillment service request.", - "examples": [ - { - "title": "Fulfillment service request payload", - "description": "Get the request's POST payload.", - "tabs": [ - { - "code": "/app/routes/fulfillment_order_notification.ts\nimport { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.fulfillmentService(request);\n if(payload.kind === 'FULFILLMENT_REQUEST') {\n // handle fulfillment request\n } else if (payload.kind === 'CANCELLATION_REQUEST') {\n // handle cancellation request\n };\nreturn new Response();", - "title": "Example" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/fulfillment-service/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop.", - "examples": [ - { - "title": "Shopify session for the fulfillment service notification request", - "description": "Use the session associated with this request to use REST resources.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\n export const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.fulfillmentService(request);\n\n const products = await admin?.rest.resources.Product.all({ session });\n // Use products\n\n return new Response();\n};", - "title": "/app/routes/fulfillment_service_notification.tsx" - } - ] - } - ] - } - ], - "value": "export interface FulfillmentServiceContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n * @example\n * Shopify session for the fulfillment service notification request.\n * Use the session associated with this request to use REST resources.\n * ```ts\n * // /app/routes/fulfillment_service_notification.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session, admin } = await authenticate.fulfillmentService(request);\n *\n * const products = await admin?.rest.resources.Product.all({ session });\n * // Use products\n *\n * return new Response();\n * };\n * ```\n * */\n session: Session;\n /**\n *\n * An admin context for the fulfillment service request.\n *\n * Returned only if there is a session for the shop.\n * @example\n * Shopify session for the fulfillment service request.\n * Use the session associated with this request to use the Admin GraphQL API \n * ```ts\n * // /app/routes/fulfillment_order_notification.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.fulfillmentService(request);\n * const response = await admin?.graphql(\n * `#graphql\n * query {\n * shop {\n * assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {\n * edges {\n * node {\n * id\n * destination {\n * firstName\n * lastName\n * }\n * lineItems(first: 10) {\n * edges {\n * node {\n * id\n * productTitle\n * sku\n * remainingQuantity\n * }\n * }\n * }\n * merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {\n * edges {\n * node {\n * message\n * }\n * }\n * }\n * }\n * }\n * }\n * }\n * }`);\n *\n * const fulfillments = await response.json();\n * return json({ data: fulfillments.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * The payload from the fulfillment service request.\n *\n * @example\n * Fulfillment service request payload.\n * Get the request's POST payload.\n * ```ts\n * /app/routes/fulfillment_order_notification.ts\n * import { ActionFunction } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action: ActionFunction = async ({ request }) => {\n * const { payload } = await authenticate.fulfillmentService(request);\n * if(payload.kind === 'FULFILLMENT_REQUEST') {\n * // handle fulfillment request\n * } else if (payload.kind === 'CANCELLATION_REQUEST') {\n * // handle cancellation request\n * };\n * return new Response();\n * ```\n */\n payload: FulfillmentServicePayload;\n}" - }, - "FulfillmentServicePayload": { - "filePath": "src/server/authenticate/fulfillment-service/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "FulfillmentServicePayload", - "value": "Record & {\n kind: string;\n}", - "description": "" - }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", - "value": "string", - "description": "The access token for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." - } - ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" - }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user_scope", - "value": "string", - "description": "The effective set of scopes for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires_in", - "value": "number", - "description": "How long the access token is valid for, in seconds." - } - ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" - }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email", - "value": "string", - "description": "The user's email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email_verified", - "value": "boolean", - "description": "Whether the user has verified their email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "number", - "description": "The user's ID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "last_name", - "value": "string", - "description": "The user's last name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "locale", - "value": "string", - "description": "The user's locale." - } - ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" - }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." - } - ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" - }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "accessToken", - "value": "string", - "description": "The access token for the session.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "scope", - "value": "string", - "description": "The scopes for the access token.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - } - ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" - }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" - } - } - } - ], - "defaultExample": { - "description": "Handle a fulfillment service notification call", - "codeblock": { - "title": "Consume a fulfillment service notification request", - "tabs": [ - { - "title": "/app/routes/**.ts", - "language": "typescript", - "code": "import {type ActionFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {admin, payload} = await authenticate.fulfillmentService(request);\n\n const kind = payload.kind;\n\n if (kind === 'FULFILLMENT_REQUEST') {\n const response = await admin?.graphql(\n `#graphql\n query {\n shop {\n assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {\n edges {\n node {\n id\n destination {\n firstName\n lastName\n }\n lineItems(first: 10) {\n edges {\n node {\n id\n productTitle\n sku\n remainingQuantity\n }\n }\n }\n merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {\n edges {\n node {\n message\n }\n }\n }\n }\n }\n }\n }\n }`,\n );\n\n const fulfillments = await response.json();\n console.log(fulfillments);\n }\n\n return new Response();\n};\n" - } - ] - } - }, - "jsDocTypeExamples": [ - "FulfillmentServiceContext" - ], - "related": [ - { - "name": "Admin API context", - "subtitle": "Interact with the Admin API.", - "url": "/docs/api/shopify-app-remix/apis/admin-api" - }, - { - "name": "Manage Fulfillments", - "subtitle": "Receive fulfillment requests and cancellations.", - "url": "/docs/apps/fulfillment/fulfillment-service-apps/manage-fulfillments", - "type": "shopify" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "admin", - "examples": [ - { - "description": "Use the session associated with this request to use the Admin GraphQL API", - "codeblock": { - "title": "Shopify session for the fulfillment service request", - "tabs": [ - { - "title": "/app/routes/fulfillment_order_notification.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.fulfillmentService(request);\n const response = await admin?.graphql(\n `#graphql\n query {\n shop {\n assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {\n edges {\n node {\n id\n destination {\n firstName\n lastName\n }\n lineItems(first: 10) {\n edges {\n node {\n id\n productTitle\n sku\n remainingQuantity\n }\n }\n }\n merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {\n edges {\n node {\n message\n }\n }\n }\n }\n }\n }\n }\n}`);\n\n const fulfillments = await response.json();\n return json({ data: fulfillments.data });\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "payload", - "examples": [ - { - "description": "Get the request's POST payload.", - "codeblock": { - "title": "Fulfillment service request payload", - "tabs": [ - { - "title": "Example", - "code": "/app/routes/fulfillment_order_notification.ts\nimport { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.fulfillmentService(request);\n if(payload.kind === 'FULFILLMENT_REQUEST') {\n // handle fulfillment request\n } else if (payload.kind === 'CANCELLATION_REQUEST') {\n // handle cancellation request\n };\nreturn new Response();", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "session", - "examples": [ - { - "description": "Use the session associated with this request to use REST resources.", - "codeblock": { - "title": "Shopify session for the fulfillment service notification request", - "tabs": [ - { - "title": "/app/routes/fulfillment_service_notification.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\n export const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.fulfillmentService(request);\n\n const products = await admin?.rest.resources.Product.all({ session });\n // Use products\n\n return new Response();\n};", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "App proxy", - "description": "[App proxies](/docs/apps/online-store/app-proxies) take requests to Shopify links, and redirect them to external links.\nThe `authenticate.public.appProxy` function validates requests made to app proxies, and returns a context to enable querying Shopify APIs.\n\n> Note: If the store has not installed the app, store-related properties such as `admin` or `storefront` will be `undefined`", - "category": "Authenticate", - "subCategory": "Public", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "authenticate.public.appProxy", - "description": "Authenticates requests coming to the app from Shopify app proxies.", - "type": "AuthenticateAppProxy", - "typeDefinitions": { - "AuthenticateAppProxy": { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "name": "AuthenticateAppProxy", - "description": "", - "params": [ - { - "name": "request", - "description": "", - "value": "Request", - "filePath": "src/server/authenticate/public/appProxy/types.ts" - } - ], - "returns": { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "description": "", - "name": "Promise", - "value": "Promise" - }, - "value": "export type AuthenticateAppProxy = (\n request: Request,\n) => Promise;" - }, - "AppProxyContext": { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "name": "AppProxyContext", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "undefined", - "description": "No session is available for the shop that made this request. Therefore no methods for interacting with the GraphQL / REST Admin APIs are available." - }, - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "liquid", - "value": "LiquidResponseFunction", - "description": "A utility for creating a Liquid Response.", - "examples": [ - { - "title": "Rendering liquid content", - "description": "Use the `liquid` helper to render a `Response` with Liquid content using the shop's theme. See the [Liquid reference](https://shopify.dev/docs/api/liquid) for all the features it enables.", - "tabs": [ - { - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\");\n}", - "title": "/app/routes/**\\/*.ts" - } - ] - }, - { - "title": "Rendering liquid content without a layout", - "description": "Set the `layout` option to `false` to render the Liquid content without a theme.", - "tabs": [ - { - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\n \"Hello {{shop.name}}\",\n { layout: false }\n );\n}", - "title": "/app/routes/**\\/*.ts" - } - ] - }, - { - "title": "Rendering a form in a Liquid response", - "description": "Handle form submissions through an app proxy.", - "tabs": [ - { - "code": "import { redirect } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport async function loader({ request }) {\n const { liquid } = await authenticate.public.appProxy(request);\n\n return liquid(`\n \n \n \n \n `);\n}\n\nexport async function action({ request }) {\n await authenticate.public.appProxy(request);\n\n const formData = await request.formData();\n const field = formData.get(\"field\")?.toString();\n\n // Perform actions here\n if (field) {\n console.log(\"Field:\", field);\n }\n\n // Return to the form page\n return redirect(\"/apps/proxy/my-action\");\n}", - "title": "app/routes/apps.proxy.my-action.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "undefined", - "description": "No session is available for the shop that made this request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice." - }, - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "storefront", - "value": "undefined", - "description": "No session is available for the shop that made this request. Therefore no method for interacting with the Storefront API is available." - } - ], - "value": "export interface AppProxyContext extends Context {\n /**\n * No session is available for the shop that made this request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n */\n session: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no methods for interacting with the GraphQL / REST Admin APIs are available.\n */\n admin: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no method for interacting with the Storefront API is available.\n */\n storefront: undefined;\n}" - }, - "LiquidResponseFunction": { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "name": "LiquidResponseFunction", - "description": "", - "params": [ - { - "name": "body", - "description": "", - "value": "string", - "filePath": "src/server/authenticate/public/appProxy/types.ts" - }, - { - "name": "initAndOptions", - "description": "", - "value": "number | (ResponseInit & Options)", - "isOptional": true, - "filePath": "src/server/authenticate/public/appProxy/types.ts" - } - ], - "returns": { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "description": "", - "name": "Response", - "value": "Response" - }, - "value": "export type LiquidResponseFunction = (\n body: string,\n initAndOptions?: number | (ResponseInit & Options),\n) => Response;" - }, - "Options": { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "name": "Options", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "layout", - "value": "boolean", - "description": "Whether to use the shop's theme layout around the Liquid content.", - "isOptional": true - } - ], - "value": "interface Options {\n /**\n * Whether to use the shop's theme layout around the Liquid content.\n */\n layout?: boolean;\n}" - }, - "AppProxyContextWithSession": { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "name": "AppProxyContextWithSession", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.", - "examples": [ - { - "title": "Interacting with the Admin API", - "description": "Use the `admin` object to interact with the REST or GraphQL APIs.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" }\n }\n }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "liquid", - "value": "LiquidResponseFunction", - "description": "A utility for creating a Liquid Response.", - "examples": [ - { - "title": "Rendering liquid content", - "description": "Use the `liquid` helper to render a `Response` with Liquid content using the shop's theme. See the [Liquid reference](https://shopify.dev/docs/api/liquid) for all the features it enables.", - "tabs": [ - { - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\");\n}", - "title": "/app/routes/**\\/*.ts" - } - ] - }, - { - "title": "Rendering liquid content without a layout", - "description": "Set the `layout` option to `false` to render the Liquid content without a theme.", - "tabs": [ - { - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\n \"Hello {{shop.name}}\",\n { layout: false }\n );\n}", - "title": "/app/routes/**\\/*.ts" - } - ] - }, - { - "title": "Rendering a form in a Liquid response", - "description": "Handle form submissions through an app proxy.", - "tabs": [ - { - "code": "import { redirect } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport async function loader({ request }) {\n const { liquid } = await authenticate.public.appProxy(request);\n\n return liquid(`\n
\n \n \n
\n `);\n}\n\nexport async function action({ request }) {\n await authenticate.public.appProxy(request);\n\n const formData = await request.formData();\n const field = formData.get(\"field\")?.toString();\n\n // Perform actions here\n if (field) {\n console.log(\"Field:\", field);\n }\n\n // Return to the form page\n return redirect(\"/apps/proxy/my-action\");\n}", - "title": "app/routes/apps.proxy.my-action.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the shop that made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", - "examples": [ - { - "title": "Using the session object", - "description": "Get the session for the shop that initiated the request to the app proxy.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppModelData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }) => {\n // Get the session for the shop that initiated the request to the app proxy.\n const { session } =\n await authenticate.public.appProxy(request);\n\n // Use the session data to make to queries to your database or additional requests.\n return json(\n await getMyAppModelData({shop: session.shop})\n );\n};", - "title": "app/routes/**\\/.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/public/appProxy/types.ts", - "syntaxKind": "PropertySignature", - "name": "storefront", - "value": "StorefrontContext", - "description": "Method for interacting with the Shopify Storefront Graphql API for the store that made the request.", - "examples": [ - { - "title": "Interacting with the Storefront API", - "description": "Use the `storefront` object to interact with the GraphQL API.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(\n `#graphql\n query blogIds {\n blogs(first: 10) {\n edges {\n node {\n id\n }\n }\n }\n }`\n );\n\n return json(await response.json());\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] - } - ], - "value": "export interface AppProxyContextWithSession<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends Context {\n /**\n * The session for the shop that made the request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * Use this to get shop or user-specific data.\n *\n * @example\n * Using the session object.\n * Get the session for the shop that initiated the request to the app proxy.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppModelData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }) => {\n * // Get the session for the shop that initiated the request to the app proxy.\n * const { session } =\n * await authenticate.public.appProxy(request);\n *\n * // Use the session data to make to queries to your database or additional requests.\n * return json(\n * await getMyAppModelData({shop: session.shop})\n * );\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.\n *\n * @example\n * Interacting with the Admin API.\n * Use the `admin` object to interact with the REST or GraphQL APIs.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.public.appProxy(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * {\n * variables: {\n * input: { title: \"Product Name\" }\n * }\n * }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * Method for interacting with the Shopify Storefront Graphql API for the store that made the request.\n *\n * @example\n * Interacting with the Storefront API.\n * Use the `storefront` object to interact with the GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(\n * `#graphql\n * query blogIds {\n * blogs(first: 10) {\n * edges {\n * node {\n * id\n * }\n * }\n * }\n * }`\n * );\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: StorefrontContext;\n}" - }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", - "value": "string", - "description": "The access token for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." - } - ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" - }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user_scope", - "value": "string", - "description": "The effective set of scopes for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires_in", - "value": "number", - "description": "How long the access token is valid for, in seconds." - } - ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" - }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email", - "value": "string", - "description": "The user's email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email_verified", - "value": "boolean", - "description": "Whether the user has verified their email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "number", - "description": "The user's ID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "last_name", - "value": "string", - "description": "The user's last name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "locale", - "value": "string", - "description": "The user's locale." - } - ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" - }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." - } - ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" - }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "accessToken", - "value": "string", - "description": "The access token for the session.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "scope", - "value": "string", - "description": "The scopes for the access token.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - } - ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" - }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" - }, - "StorefrontContext": { - "filePath": "src/server/clients/storefront/types.ts", - "name": "StorefrontContext", - "description": "", - "members": [ - { - "filePath": "src/server/clients/storefront/types.ts", - "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Method for interacting with the Shopify Storefront GraphQL API\n\nIf you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n\n\n\n\n", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `storefront.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "title": "app/routes/**\\/.ts" - } - ] - }, - { - "title": "Handling GraphQL errors", - "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { storefront } = await authenticate.public.appProxy(request);\n\n try {\n const response = await storefront.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // { errors: { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] } }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - } - ] - } - ], - "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n *\n * @example\n * Handling GraphQL errors.\n * Catch `GraphqlQueryError` errors to see error messages from the API.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * try {\n * const response = await storefront.graphql(\n * `#graphql\n * query incorrectQuery {\n * products(first: 10) {\n * nodes {\n * not_a_field\n * }\n * }\n * }`,\n * );\n *\n * return json({ data: await response.json() });\n * } catch (error) {\n * if (error instanceof GraphqlQueryError) {\n * // { errors: { graphQLErrors: [\n * // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n * // ] } }\n * return json({ errors: error.body?.errors }, { status: 500 });\n * }\n * return json({ message: \"An error occurred\" }, { status: 500 });\n * }\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n graphql: GraphQLClient;\n}" - }, - "GraphQLClient": { - "filePath": "src/server/clients/types.ts", - "name": "GraphQLClient", - "description": "", - "params": [ - { - "name": "query", - "description": "", - "value": "Operation extends keyof Operations", - "filePath": "src/server/clients/types.ts" - }, - { - "name": "options", - "description": "", - "value": "GraphQLQueryOptions", - "isOptional": true, - "filePath": "src/server/clients/types.ts" - } - ], - "returns": { - "filePath": "src/server/clients/types.ts", - "description": "", - "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", - "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" - }, - "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise>;" - }, - "GraphQLQueryOptions": { - "filePath": "src/server/clients/types.ts", - "name": "GraphQLQueryOptions", - "description": "", - "members": [ - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "ApiVersion", - "description": "The version of the API to use for the request.", - "isOptional": true - }, - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "headers", - "value": "Record", - "description": "Additional headers to include in the request.", - "isOptional": true - }, - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "tries", - "value": "number", - "description": "The total number of times to try the request if it fails.", - "isOptional": true - }, - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "variables", - "value": "ApiClientRequestOptions[\"variables\"]", - "description": "The variables to pass to the operation.", - "isOptional": true - } - ], - "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n /**\n * The variables to pass to the operation.\n */\n variables?: ApiClientRequestOptions['variables'];\n /**\n * The version of the API to use for the request.\n */\n apiVersion?: ApiVersion;\n /**\n * Additional headers to include in the request.\n */\n headers?: Record;\n /**\n * The total number of times to try the request if it fails.\n */\n tries?: number;\n}" - }, - "ApiVersion": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "ApiVersion", - "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n April24 = \"2024-04\",\n July24 = \"2024-07\",\n October24 = \"2024-10\",\n Unstable = \"unstable\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October22", - "value": "2022-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January23", - "value": "2023-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April23", - "value": "2023-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July23", - "value": "2023-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October23", - "value": "2023-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January24", - "value": "2024-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April24", - "value": "2024-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July24", - "value": "2024-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October24", - "value": "2024-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Unstable", - "value": "unstable" - } - ] - } - } - } - ], - "defaultExample": { - "description": "Authenticate and fetch product information", - "codeblock": { - "title": "Authenticate and fetch product information", - "tabs": [ - { - "title": "/app/routes/**.ts", - "language": "typescript", - "code": "import type {LoaderFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n const {storefront, liquid} = await authenticate.public.appProxy(request);\n\n if (!storefront) {\n return new Response();\n }\n\n const response = await storefront.graphql(\n `#graphql\n query productTitle {\n products(first: 1) {\n nodes {\n title\n }\n }\n }`,\n );\n const body = await response.json();\n\n const title = body.data.products.nodes[0].title;\n\n return liquid(`Found product ${title} from {{shop.name}}`);\n};\n" - } - ] - } - }, - "jsDocTypeExamples": [ - "AppProxyContextWithSession" - ], - "related": [ - { - "name": "Admin API context", - "subtitle": "Interact with the Admin API.", - "url": "/docs/api/shopify-app-remix/apis/admin-api" - }, - { - "name": "Storefront API context", - "subtitle": "Interact with the Storefront API.", - "url": "/docs/api/shopify-app-remix/apis/storefront-api" - }, - { - "name": "Liquid reference", - "subtitle": "Use the shop's theme to render a template.", - "url": "/docs/api/liquid", - "type": "liquid" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "admin", - "examples": [ - { - "description": "Use the `admin` object to interact with the REST or GraphQL APIs.", - "codeblock": { - "title": "Interacting with the Admin API", - "tabs": [ - { - "title": "app/routes/**\\/.ts", - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" }\n }\n }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "liquid", - "examples": [ - { - "description": "Use the `liquid` helper to render a `Response` with Liquid content using the shop's theme. See the [Liquid reference](https://shopify.dev/docs/api/liquid) for all the features it enables.", - "codeblock": { - "title": "Rendering liquid content", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\");\n}", - "language": "typescript" - } - ] - } - }, - { - "description": "Set the `layout` option to `false` to render the Liquid content without a theme.", - "codeblock": { - "title": "Rendering liquid content without a layout", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\n \"Hello {{shop.name}}\",\n { layout: false }\n );\n}", - "language": "typescript" - } - ] - } - }, - { - "description": "Handle form submissions through an app proxy.", - "codeblock": { - "title": "Rendering a form in a Liquid response", - "tabs": [ - { - "title": "app/routes/apps.proxy.my-action.tsx", - "code": "import { redirect } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport async function loader({ request }) {\n const { liquid } = await authenticate.public.appProxy(request);\n\n return liquid(`\n <form method=\"post\" action=\"/apps/proxy/my-action\">\n <input type=\"text\" name=\"field\" />\n <button type=\"submit\">Submit</button>\n </form>\n `);\n}\n\nexport async function action({ request }) {\n await authenticate.public.appProxy(request);\n\n const formData = await request.formData();\n const field = formData.get(\"field\")?.toString();\n\n // Perform actions here\n if (field) {\n console.log(\"Field:\", field);\n }\n\n // Return to the form page\n return redirect(\"/apps/proxy/my-action\");\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "session", - "examples": [ - { - "description": "Get the session for the shop that initiated the request to the app proxy.", - "codeblock": { - "title": "Using the session object", - "tabs": [ - { - "title": "app/routes/**\\/.ts", - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppModelData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }) => {\n // Get the session for the shop that initiated the request to the app proxy.\n const { session } =\n await authenticate.public.appProxy(request);\n\n // Use the session data to make to queries to your database or additional requests.\n return json(\n await getMyAppModelData({shop: session.shop})\n );\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "storefront", - "examples": [ - { - "description": "Use the `storefront` object to interact with the GraphQL API.", - "codeblock": { - "title": "Interacting with the Storefront API", - "tabs": [ - { - "title": "app/routes/**\\/.ts", - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(\n `#graphql\n query blogIds {\n blogs(first: 10) {\n edges {\n node {\n id\n }\n }\n }\n }`\n );\n\n return json(await response.json());\n}", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Checkout", - "description": "The `authenticate.public.checkout` function ensures that checkout extension requests are coming from Shopify, and returns helpers to respond with the correct headers.", - "category": "Authenticate", - "subCategory": "Public", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "authenticate.public.checkout", - "description": "Authenticates requests coming from Shopify checkout extensions.", - "type": "AuthenticateCheckout", - "typeDefinitions": { - "AuthenticateCheckout": { - "filePath": "src/server/authenticate/public/checkout/types.ts", - "name": "AuthenticateCheckout", - "description": "", - "params": [ - { - "name": "request", - "description": "", - "value": "Request", - "filePath": "src/server/authenticate/public/checkout/types.ts" - }, - { - "name": "options", - "description": "", - "value": "AuthenticateCheckoutOptions", - "isOptional": true, - "filePath": "src/server/authenticate/public/checkout/types.ts" - } - ], - "returns": { - "filePath": "src/server/authenticate/public/checkout/types.ts", - "description": "", - "name": "Promise", - "value": "Promise" - }, - "value": "export type AuthenticateCheckout = (\n request: Request,\n options?: AuthenticateCheckoutOptions,\n) => Promise;" - }, - "AuthenticateCheckoutOptions": { - "filePath": "src/server/authenticate/public/checkout/types.ts", - "name": "AuthenticateCheckoutOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/public/checkout/types.ts", - "syntaxKind": "PropertySignature", - "name": "corsHeaders", - "value": "string[]", - "description": "", - "isOptional": true - } - ], - "value": "export interface AuthenticateCheckoutOptions {\n corsHeaders?: string[];\n}" - }, - "CheckoutContext": { - "filePath": "src/server/authenticate/public/checkout/types.ts", - "name": "CheckoutContext", - "description": "Authenticated Context for a checkout request", - "members": [ - { - "filePath": "src/server/authenticate/public/checkout/types.ts", - "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", - "examples": [ - { - "title": "Setting CORS headers for a public request", - "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", - "title": "app/routes/public/my-route.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/public/checkout/types.ts", - "syntaxKind": "PropertySignature", - "name": "sessionToken", - "value": "JwtPayload", - "description": "The decoded and validated session token for the request\n\nRefer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).", - "examples": [ - { - "title": "Using the decoded session token", - "description": "Get store-specific data using the `sessionToken` object.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", - "title": "app/routes/public/my-route.ts" - } - ] - } - ] - } - ], - "value": "export interface CheckoutContext {\n /**\n * The decoded and validated session token for the request\n *\n * Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).\n *\n * @example\n * Using the decoded session token.\n * Get store-specific data using the `sessionToken` object.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({shop: sessionToken.dest}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that ensures the CORS headers are set correctly for the response.\n *\n * @example\n * Setting CORS headers for a public request.\n * Use the `cors` helper to ensure your app can respond to checkout extension requests.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * { corsHeaders: [\"X-My-Custom-Header\"] }\n * );\n * const data = await getMyAppData({shop: sessionToken.dest});\n * return cors(json(data));\n * };\n * ```\n */\n cors: EnsureCORSFunction;\n}" - }, - "EnsureCORSFunction": { - "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", - "name": "EnsureCORSFunction", - "description": "", - "members": [], - "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" - }, - "JwtPayload": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "JwtPayload", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "aud", - "value": "string", - "description": "The client ID of the receiving app." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "dest", - "value": "string", - "description": "The shop's domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "exp", - "value": "number", - "description": "When the session token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "iat", - "value": "number", - "description": "When the session token was issued." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "iss", - "value": "string", - "description": "The shop's admin domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "jti", - "value": "string", - "description": "A secure random UUID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "nbf", - "value": "number", - "description": "When the session token activates." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "sid", - "value": "string", - "description": "A unique session ID per user and app." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "sub", - "value": "string", - "description": "The User that the session token is intended for." - } - ], - "value": "export interface JwtPayload {\n /**\n * The shop's admin domain.\n */\n iss: string;\n /**\n * The shop's domain.\n */\n dest: string;\n /**\n * The client ID of the receiving app.\n */\n aud: string;\n /**\n * The User that the session token is intended for.\n */\n sub: string;\n /**\n * When the session token expires.\n */\n exp: number;\n /**\n * When the session token activates.\n */\n nbf: number;\n /**\n * When the session token was issued.\n */\n iat: number;\n /**\n * A secure random UUID.\n */\n jti: string;\n /**\n * A unique session ID per user and app.\n */\n sid: string;\n}" - } - } - } - ], - "defaultExample": { - "description": "Authenticate and return offers for the shop", - "codeblock": { - "title": "Authenticate and return offers for the shop", - "tabs": [ - { - "title": "/app/routes/**.ts", - "language": "typescript", - "code": "import type {ActionFunctionArgs, LoaderFunctionArgs} from '@remix-run/node';\nimport {json} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\nimport {getOffers} from '../offers.server';\n\n// The loader responds to preflight requests from Shopify\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n await authenticate.public.checkout(request);\n};\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {cors, sessionToken} = await authenticate.public.checkout(request);\n\n const offers = getOffers(sessionToken.dest);\n return cors(json({offers}));\n};\n" - }, - { - "title": "/app/offers.server.ts", - "language": "typescript", - "code": "// Most apps would load this from their database\nexport function getOffers(shop: string) {\n const offers: Record<any, any[]> = {\n 'shop.com': [\n {\n id: '1',\n title: '10% off',\n price: 10,\n type: 'percentage',\n },\n {\n id: '2',\n title: 'Free shipping',\n price: 0,\n type: 'shipping',\n },\n ],\n };\n\n return offers[shop];\n}\n" - } - ] - } - }, - "jsDocTypeExamples": [ - "CheckoutContext" - ], - "related": [ - { - "name": "Session token API", - "subtitle": "Checkout UI extension API for interacting with session tokens.", - "url": "/docs/api/checkout-ui-extensions/latest/apis/session-token", - "type": "shopify" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "cors", - "examples": [ - { - "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", - "codeblock": { - "title": "Setting CORS headers for a public request", - "tabs": [ - { - "title": "app/routes/public/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "sessionToken", - "examples": [ - { - "description": "Get store-specific data using the `sessionToken` object.", - "codeblock": { - "title": "Using the decoded session token", - "tabs": [ - { - "title": "app/routes/public/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Customer account", - "description": "The `authenticate.public.customerAccount` function ensures that customer account extension requests are coming from Shopify, and returns helpers to respond with the correct headers.", - "category": "Authenticate", - "subCategory": "Public", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "authenticate.public.customerAccount", - "description": "Authenticates requests coming from Shopify customer account extensions.", - "type": "AuthenticateCustomerAccount", - "typeDefinitions": { - "AuthenticateCustomerAccount": { - "filePath": "src/server/authenticate/public/customer-account/types.ts", - "name": "AuthenticateCustomerAccount", - "description": "", - "params": [ - { - "name": "request", - "description": "", - "value": "Request", - "filePath": "src/server/authenticate/public/customer-account/types.ts" - }, - { - "name": "options", - "description": "", - "value": "AuthenticateCustomerAccountOptions", - "isOptional": true, - "filePath": "src/server/authenticate/public/customer-account/types.ts" - } - ], - "returns": { - "filePath": "src/server/authenticate/public/customer-account/types.ts", - "description": "", - "name": "Promise", - "value": "Promise" - }, - "value": "export type AuthenticateCustomerAccount = (\n request: Request,\n options?: AuthenticateCustomerAccountOptions,\n) => Promise;" - }, - "AuthenticateCustomerAccountOptions": { - "filePath": "src/server/authenticate/public/customer-account/types.ts", - "name": "AuthenticateCustomerAccountOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/public/customer-account/types.ts", - "syntaxKind": "PropertySignature", - "name": "corsHeaders", - "value": "string[]", - "description": "", - "isOptional": true - } - ], - "value": "export interface AuthenticateCustomerAccountOptions {\n corsHeaders?: string[];\n}" - }, - "CustomerAccountContext": { - "filePath": "src/server/authenticate/public/customer-account/types.ts", - "name": "CustomerAccountContext", - "description": "Authenticated Context for a customer account extension request", - "members": [ - { - "filePath": "src/server/authenticate/public/customer-account/types.ts", - "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", - "examples": [ - { - "title": "Setting CORS headers for a public request", - "description": "Use the `cors` helper to ensure your app can respond to customer account extension requests.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.customerAccount(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", - "title": "app/routes/public/my-route.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/public/customer-account/types.ts", - "syntaxKind": "PropertySignature", - "name": "sessionToken", - "value": "JwtPayload", - "description": "The decoded and validated session token for the request\n\nRefer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).", - "examples": [ - { - "title": "Using the decoded session token", - "description": "Get store-specific data using the `sessionToken` object.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.customerAccount(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", - "title": "app/routes/public/my-route.ts" - } - ] - } - ] - } - ], - "value": "export interface CustomerAccountContext {\n /**\n * The decoded and validated session token for the request\n *\n * Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).\n *\n * @example\n * Using the decoded session token.\n * Get store-specific data using the `sessionToken` object.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.customerAccount(\n * request\n * );\n * return json(await getMyAppData({shop: sessionToken.dest}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that ensures the CORS headers are set correctly for the response.\n *\n * @example\n * Setting CORS headers for a public request.\n * Use the `cors` helper to ensure your app can respond to customer account extension requests.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.customerAccount(\n * request,\n * { corsHeaders: [\"X-My-Custom-Header\"] }\n * );\n * const data = await getMyAppData({shop: sessionToken.dest});\n * return cors(json(data));\n * };\n * ```\n */\n cors: EnsureCORSFunction;\n}" - }, - "EnsureCORSFunction": { - "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", - "name": "EnsureCORSFunction", - "description": "", - "members": [], - "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" - }, - "JwtPayload": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "JwtPayload", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "aud", - "value": "string", - "description": "The client ID of the receiving app." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "dest", - "value": "string", - "description": "The shop's domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "exp", - "value": "number", - "description": "When the session token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "iat", - "value": "number", - "description": "When the session token was issued." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "iss", - "value": "string", - "description": "The shop's admin domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "jti", - "value": "string", - "description": "A secure random UUID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "nbf", - "value": "number", - "description": "When the session token activates." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "sid", - "value": "string", - "description": "A unique session ID per user and app." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "sub", - "value": "string", - "description": "The User that the session token is intended for." - } - ], - "value": "export interface JwtPayload {\n /**\n * The shop's admin domain.\n */\n iss: string;\n /**\n * The shop's domain.\n */\n dest: string;\n /**\n * The client ID of the receiving app.\n */\n aud: string;\n /**\n * The User that the session token is intended for.\n */\n sub: string;\n /**\n * When the session token expires.\n */\n exp: number;\n /**\n * When the session token activates.\n */\n nbf: number;\n /**\n * When the session token was issued.\n */\n iat: number;\n /**\n * A secure random UUID.\n */\n jti: string;\n /**\n * A unique session ID per user and app.\n */\n sid: string;\n}" - } - } - } - ], - "defaultExample": { - "description": "Authenticate and return offers for the customer", - "codeblock": { - "title": "Authenticate and return offers for the customer", - "tabs": [ - { - "title": "/app/routes/**.ts", - "language": "typescript", - "code": "import type {ActionFunctionArgs, LoaderFunctionArgs} from '@remix-run/node';\nimport {json} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\nimport {getOffers} from '../offers.server';\n\n// The loader responds to preflight requests from Shopify\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n await authenticate.public.customerAccount(request);\n};\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {cors, sessionToken} = await authenticate.public.customerAccount(request);\n\n // Get offers for the customer\n const offers = getOffers(sessionToken.des, sessionToken.sub);\n return cors(json({offers}));\n};\n" - }, - { - "title": "/app/offers.server.ts", - "language": "typescript", - "code": "// Most apps would load this from their database\nexport function getOffers(shop: string, customerID: string) {\n const offers: Record<string, any[]> = {\n 'shop.com': [\n {\n id: '1',\n title: '10% off',\n price: 10,\n type: 'percentage',\n customerId: 'gid://shopify/Customer/1001',\n },\n {\n id: '2',\n title: 'Free shipping',\n price: 0,\n type: 'shipping',\n customerId: 'gid://shopify/Customer/1001',\n },\n {\n id: '3',\n title: '5% off',\n price: 5,\n type: 'percentage',\n customerId: 'gid://shopify/Customer/1001',\n },\n ],\n };\n\n const allOffers = offers[shop] || [];\n // Filter offers to include only those that match the customerId\n const filteredOffers = allOffers.filter(\n (offer) => offer.customerId === customerID,\n );\n\n return filteredOffers;\n}\n" - } - ] - } - }, - "jsDocTypeExamples": [ - "CustomerAccountContext" - ], - "related": [ - { - "name": "Session token API", - "subtitle": "Customer account UI extensions API for interacting with session tokens.", - "url": "/docs/api/customer-account-ui-extensions/latest/apis/session-token", - "type": "shopify" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "cors", - "examples": [ - { - "description": "Use the `cors` helper to ensure your app can respond to customer account extension requests.", - "codeblock": { - "title": "Setting CORS headers for a public request", - "tabs": [ - { - "title": "app/routes/public/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.customerAccount(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "sessionToken", - "examples": [ - { - "description": "Get store-specific data using the `sessionToken` object.", - "codeblock": { - "title": "Using the decoded session token", - "tabs": [ - { - "title": "app/routes/public/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.customerAccount(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Webhook", - "description": "Contains functions for verifying Shopify webhooks.", - "category": "Authenticate", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "authenticate.webhook", - "description": "Verifies requests coming from Shopify webhooks.", - "type": "AuthenticateWebhook", - "typeDefinitions": { - "AuthenticateWebhook": { - "filePath": "src/server/authenticate/webhooks/types.ts", - "name": "AuthenticateWebhook", - "description": "", - "params": [ - { - "name": "request", - "description": "", - "value": "Request", - "filePath": "src/server/authenticate/webhooks/types.ts" - } - ], - "returns": { - "filePath": "src/server/authenticate/webhooks/types.ts", - "description": "", - "name": "Promise>", - "value": "Promise>" - }, - "value": "export type AuthenticateWebhook<\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> = (request: Request) => Promise>;" - }, - "WebhookContext": { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "WebhookContext", - "value": "WebhookContextWithoutSession | WebhookContextWithSession", - "description": "" - }, - "WebhookContextWithoutSession": { - "filePath": "src/server/authenticate/webhooks/types.ts", - "name": "WebhookContextWithoutSession", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "undefined", - "description": "" - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "string", - "description": "The API version used for the webhook.", - "examples": [ - { - "title": "Webhook API version", - "description": "Get the API version used for webhook request.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "payload", - "value": "Record", - "description": "The payload from the webhook request.", - "examples": [ - { - "title": "Webhook payload", - "description": "Get the request's POST payload.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "undefined", - "description": "" - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The shop where the webhook was triggered.", - "examples": [ - { - "title": "Webhook shop", - "description": "Get the shop that triggered a webhook.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "subTopic", - "value": "string", - "description": "The sub-topic of the webhook. This is only available for certain webhooks.", - "isOptional": true, - "examples": [ - { - "title": "Webhook sub-topic", - "description": "Get the webhook sub-topic.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { subTopic } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "topic", - "value": "Topics", - "description": "The topic of the webhook.", - "examples": [ - { - "title": "Webhook topic", - "description": "Get the event topic for the webhook.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "webhookId", - "value": "string", - "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", - "examples": [ - { - "title": "Webhook ID", - "description": "Get the webhook ID.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - } - ], - "value": "export interface WebhookContextWithoutSession\n extends Context {\n session: undefined;\n admin: undefined;\n}" - }, - "WebhookContextWithSession": { - "filePath": "src/server/authenticate/webhooks/types.ts", - "name": "WebhookContextWithSession", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "An admin context for the webhook.\n\nReturned only if there is a session for the shop.", - "examples": [ - { - "title": "Webhook admin context", - "description": "Use the `admin` object in the context to interact with the Admin API.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "string", - "description": "The API version used for the webhook.", - "examples": [ - { - "title": "Webhook API version", - "description": "Get the API version used for webhook request.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "payload", - "value": "Record", - "description": "The payload from the webhook request.", - "examples": [ - { - "title": "Webhook payload", - "description": "Get the request's POST payload.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop. Webhook requests can trigger after an app is uninstalled If the app is already uninstalled, the session may be undefined. Therefore, you should check for the session before using it.", - "examples": [ - { - "title": "Protecting against uninstalled apps", - "description": "", - "tabs": [ - { - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session } = await authenticate.webhook(request);\n \n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n // Handle webhook request\n console.log(\"Received webhook webhook\");\n\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The shop where the webhook was triggered.", - "examples": [ - { - "title": "Webhook shop", - "description": "Get the shop that triggered a webhook.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "subTopic", - "value": "string", - "description": "The sub-topic of the webhook. This is only available for certain webhooks.", - "isOptional": true, - "examples": [ - { - "title": "Webhook sub-topic", - "description": "Get the webhook sub-topic.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { subTopic } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "topic", - "value": "Topics", - "description": "The topic of the webhook.", - "examples": [ - { - "title": "Webhook topic", - "description": "Get the event topic for the webhook.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "webhookId", - "value": "string", - "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", - "examples": [ - { - "title": "Webhook ID", - "description": "Get the webhook ID.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - } - ], - "value": "export interface WebhookContextWithSession<\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> extends Context {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n * Webhook requests can trigger after an app is uninstalled\n * If the app is already uninstalled, the session may be undefined.\n * Therefore, you should check for the session before using it.\n *\n * @example\n * Protecting against uninstalled apps.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"~/shopify.server\";\n\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session } = await authenticate.webhook(request);\n * \n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n *\n * // Handle webhook request\n * console.log(\"Received webhook webhook\");\n *\n * return new Response();\n * };\n * ```\n */\n session: Session;\n\n /**\n * An admin context for the webhook.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Webhook admin context.\n * Use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n}" - }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", - "value": "string", - "description": "The access token for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." - } - ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" - }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user_scope", - "value": "string", - "description": "The effective set of scopes for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires_in", - "value": "number", - "description": "How long the access token is valid for, in seconds." - } - ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" - }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email", - "value": "string", - "description": "The user's email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email_verified", - "value": "boolean", - "description": "Whether the user has verified their email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "number", - "description": "The user's ID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "last_name", - "value": "string", - "description": "The user's last name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "locale", - "value": "string", - "description": "The user's locale." + } + ] + }, + { + "title": "session", + "examples": [ + { + "description": "Get your app's shop-specific data using an offline session.", + "codeblock": { + "title": "Using offline sessions", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] } - ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" - }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." + }, + { + "description": "Get your app's user-specific data using an online session.", + "codeblock": { + "title": "Using online sessions", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] } - ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" - }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "accessToken", - "value": "string", - "description": "The access token for the session.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "scope", - "value": "string", - "description": "The scopes for the access token.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." + } + ] + }, + { + "title": "sessionToken", + "examples": [ + { + "description": "Get user-specific data using the `sessionToken` object.", + "codeblock": { + "title": "Using the decoded session token", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.admin(\n request\n );\n return json(await getMyAppData({user: sessionToken.sub}));\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] } - ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" - }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" - } - } - } - ], - "defaultExample": { - "description": "Update a metafield when a product is updated", - "codeblock": { - "title": "Update a metafield when a product is updated", - "tabs": [ - { - "title": "/app/routes/**.ts", - "language": "typescript", - "code": "import {type ActionFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {topic, admin, payload, session} = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n switch (topic) {\n case 'PRODUCTS_UPDATE':\n await admin.graphql(\n `#graphql\n mutation setMetafield($productId: ID!, $time: String!) {\n metafieldsSet(metafields: {\n ownerId: $productId\n namespace: \"my-app\",\n key: \"webhook_received_at\",\n value: $time,\n type: \"string\",\n }) {\n metafields {\n key\n value\n }\n }\n }\n `,\n {\n variables: {\n productId: payload.admin_graphql_api_id,\n time: new Date().toISOString(),\n },\n },\n );\n }\n\n return new Response();\n};\n" - } - ] - } - }, - "jsDocTypeExamples": [ - "WebhookContextWithSession" - ], - "related": [ - { - "name": "Admin API context", - "subtitle": "Interact with the Admin API.", - "url": "/docs/api/shopify-app-remix/apis/admin-api" - } - ], - "examples": { - "description": "", - "exampleGroups": [ + } + ] + }, { - "title": "admin", + "title": "cancel", "examples": [ { - "description": "Use the `admin` object in the context to interact with the Admin API.", + "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", "codeblock": { - "title": "Webhook admin context", + "title": "Cancelling a subscription", "tabs": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "/app/routes/cancel-subscription.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", "language": "typescript" } ] @@ -6665,16 +1287,34 @@ ] }, { - "title": "apiVersion", + "title": "check", "examples": [ { - "description": "Get the API version used for webhook request.", + "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.", "codeblock": { - "title": "Webhook API version", + "title": "Check what billing plans a merchant is subscribed to", "tabs": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check({\n plans: [MONTHLY_PLAN],\n isTest: false,\n });\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + }, + { + "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.", + "codeblock": { + "title": "Check for payments without filtering", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check();\n // This will be true if any payment is found\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", "language": "typescript" } ] @@ -6683,16 +1323,21 @@ ] }, { - "title": "payload", + "title": "createUsageRecord", "examples": [ { - "description": "Get the request's POST payload.", + "description": "Create a usage record for the active usage billing plan", "codeblock": { - "title": "Webhook payload", + "title": "Creating a usage record", "tabs": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/create-usage-record.ts", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n const chargeBilling = await billing.createUsageRecord({\n description: \"Usage record for product creation\",\n price: {\n amount: 1,\n currencyCode: \"USD\",\n },\n isTest: true,\n });\n console.log(chargeBilling);\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", "language": "typescript" } ] @@ -6701,16 +1346,39 @@ ] }, { - "title": "session", + "title": "request", "examples": [ { - "description": "", + "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", "codeblock": { - "title": "Protecting against uninstalled apps", + "title": "Using a custom return URL", "tabs": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session } = await authenticate.webhook(request);\n \n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n // Handle webhook request\n console.log(\"Received webhook webhook\");\n\n return new Response();\n};", + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + }, + { + "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.", + "codeblock": { + "title": "Overriding plan settings", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n trialDays: 14,\n lineItems: [\n {\n interval: BillingInterval.Every30Days,\n discount: { value: { percentage: 0.1 } },\n },\n ],\n }),\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", "language": "typescript" } ] @@ -6719,16 +1387,39 @@ ] }, { - "title": "shop", + "title": "require", "examples": [ { - "description": "Get the shop that triggered a webhook.", + "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", "codeblock": { - "title": "Webhook shop", + "title": "Requesting billing right away", "tabs": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + }, + { + "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.", + "codeblock": { + "title": "Redirect to a plan selection page", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", "language": "typescript" } ] @@ -6737,16 +1428,21 @@ ] }, { - "title": "subTopic", + "title": "updateUsageCappedAmount", "examples": [ { - "description": "Get the webhook sub-topic.", + "description": "Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.", "codeblock": { - "title": "Webhook sub-topic", + "title": "Updating the capped amount for a usage billing plan", "tabs": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { subTopic } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/**\\/*.ts", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n await billing.updateUsageCappedAmount({\n subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n cappedAmount: {\n amount: 10,\n currencyCode: \"USD\"\n },\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n terms: \"Usage based\"\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", "language": "typescript" } ] @@ -6755,16 +1451,16 @@ ] }, { - "title": "topic", + "title": "query", "examples": [ { - "description": "Get the event topic for the webhook.", + "description": "Call `scopes.query` to get scope details.", "codeblock": { - "title": "Webhook topic", + "title": "Query for granted scopes", "tabs": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "title": "/app._index.tsx", + "code": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const scopesDetail = await scopes.query();\n\n return json({\n hasWriteProducts: scopesDetail.granted.includes('write_products'),\n });\n};\n\nexport default function Index() {\n const {hasWriteProducts} = useLoaderData<typeof loader>();\n\n ...\n}", "language": "typescript" } ] @@ -6773,16 +1469,34 @@ ] }, { - "title": "webhookId", + "title": "request", "examples": [ { - "description": "Get the webhook ID.", + "description": "Call `scopes.request` to request optional scopes.", "codeblock": { - "title": "Webhook ID", + "title": "Request consent from the merchant to grant the provided scopes for this app", "tabs": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/app.request.tsx", + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST a request to for optional scopes\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRequest = body.getAll(\"scopes\") as string[];\n\n // If the scopes are not already granted, a full page redirect to the request URL occurs\n await scopes.request(scopesToRequest);\n // otherwise return an empty response\n return json({});\n};\n\nexport default function Index() {\n const fetcher = useFetcher<typeof action>();\n\n const handleRequest = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "revoke", + "examples": [ + { + "description": "Call `scopes.revoke` to revoke optional scopes.", + "codeblock": { + "title": "Revoke optional scopes", + "tabs": [ + { + "title": "/app._index.tsx", + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST optional scopes to revoke\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRevoke = body.getAll(\"scopes\") as string[];\n\n const revokedResponse = await scopes.revoke(scopesToRevoke);\n\n return json(revokedResponse);\n};\n\nexport default function Index() {\n const fetcher = useFetcher<typeof action>();\n\n const handleRevoke = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", "language": "typescript" } ] @@ -6794,758 +1508,1141 @@ } }, { - "name": "Admin API", - "description": "Contains objects used to interact with the Admin API.\n\nThis object is returned as part of different contexts, such as [`admin`](/docs/api/shopify-app-remix/authenticate/admin), [`unauthenticated.admin`](/docs/api/shopify-app-remix/unauthenticated/unauthenticated-admin), and [`webhook`](/docs/api/shopify-app-remix/authenticate/webhook).", + "name": "Billing", + "description": "Contains function used to bill merchants for your app.\n\nThis object is returned on authenticated Admin requests.", "category": "APIs", "type": "object", "isVisualComponent": false, "definitions": [ { - "title": "admin", - "description": "Provides utilities that apps can use to make requests to the Admin API.", - "type": "AdminApiContext", + "title": "billing", + "description": "Provides utilities that apps can use to request billing for the app using the Admin API.", + "type": "BillingContext", "typeDefinitions": { - "AdminApiContext": { - "filePath": "src/server/clients/admin/types.ts", - "name": "AdminApiContext", + "BillingContext": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "BillingContext", "description": "", "members": [ { - "filePath": "src/server/clients/admin/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "cancel", + "value": "(options: CancelBillingOptions) => Promise", + "description": "Cancels an ongoing subscription, given its ID.", + "examples": [ + { + "title": "Cancelling a subscription", + "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", + "title": "/app/routes/cancel-subscription.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "name": "check", + "value": ">(options?: Options) => Promise", + "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", "examples": [ { - "title": "Querying the GraphQL API", - "description": "Use `admin.graphql` to make query / mutation requests.", + "title": "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.", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" },\n },\n },\n );\n\n const productData = await response.json();\n return json({\n productId: productData.data?.productCreate?.product?.id,\n });\n}", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check({\n plans: [MONTHLY_PLAN],\n isTest: false,\n });\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", "title": "/app/routes/**\\/*.ts" }, { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" } ] }, { - "title": "Handling GraphQL errors", - "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "title": "Check for payments without filtering", + "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.", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n try {\n const response = await admin.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // error.body.errors:\n // { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check();\n // This will be true if any payment is found\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", "title": "/app/routes/**\\/*.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "createUsageRecord", + "value": "(options: CreateUsageRecordOptions) => Promise", + "description": "Creates a usage record for an app subscription.", + "examples": [ + { + "title": "Creating a usage record", + "description": "Create a usage record for the active usage billing plan", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n const chargeBilling = await billing.createUsageRecord({\n description: \"Usage record for product creation\",\n price: {\n amount: 1,\n currencyCode: \"USD\",\n },\n isTest: true,\n });\n console.log(chargeBilling);\n\n // App logic\n};", + "title": "/app/routes/create-usage-record.ts" }, { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" } ] } ] }, { - "filePath": "src/server/clients/admin/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "rest", - "value": "RestClientWithResources", - "description": "Methods for interacting with the Shopify Admin REST API\n\nThere 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.\n\n\n\n\n", + "name": "request", + "value": "(options: RequestBillingOptions) => Promise", + "description": "Requests payment for the plan.", "examples": [ { - "title": "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.", + "title": "Using a custom return URL", + "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n return json(\n admin.rest.resources.Order.count({ session }),\n );\n};", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", "title": "/app/routes/**\\/*.ts" }, { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" } ] }, { - "title": "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", + "title": "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.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n const response = await admin.rest.get({\n path: \"/customers/count.json\",\n });\n const customers = await response.json();\n\n return json({ customers });\n};", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n trialDays: 14,\n lineItems: [\n {\n interval: BillingInterval.Every30Days,\n discount: { value: { percentage: 0.1 } },\n },\n ],\n }),\n });\n\n // App logic\n};", "title": "/app/routes/**\\/*.ts" }, { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "require", + "value": "(options: RequireBillingOptions) => Promise", + "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "examples": [ + { + "title": "Requesting billing right away", + "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" } ] }, { - "title": "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", + "title": "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.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n });\n\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", + "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", "title": "/app/routes/**\\/*.ts" }, { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" } ] } ] - } - ], - "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * 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.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * 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. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const {\n * admin,\n * session,\n * } = await authenticate.admin(request);\n *\n * return json(\n * admin.rest.resources.Order.count({ session }),\n * );\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const {\n * admin,\n * session,\n * } = await authenticate.admin(request);\n *\n * const response = await admin.rest.get({\n * path: \"/customers/count.json\",\n * });\n * const customers = await response.json();\n *\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const {\n * admin,\n * session,\n * } = await authenticate.admin(request);\n *\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n *\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-app-js/blob/main/packages/apps/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * {\n * variables: {\n * input: { title: \"Product Name\" },\n * },\n * },\n * );\n *\n * const productData = await response.json();\n * return json({\n * productId: productData.data?.productCreate?.product?.id,\n * });\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Handling GraphQL errors.\n * Catch `GraphqlQueryError` errors to see error messages from the API.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { admin } = await authenticate.admin(request);\n *\n * try {\n * const response = await admin.graphql(\n * `#graphql\n * query incorrectQuery {\n * products(first: 10) {\n * nodes {\n * not_a_field\n * }\n * }\n * }`,\n * );\n *\n * return json({ data: await response.json() });\n * } catch (error) {\n * if (error instanceof GraphqlQueryError) {\n * // error.body.errors:\n * // { graphQLErrors: [\n * // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n * // ] }\n * return json({ errors: error.body?.errors }, { status: 500 });\n * }\n * return json({ message: \"An error occurred\" }, { status: 500 });\n * }\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n graphql: GraphQLClient;\n}" - }, - "GraphQLClient": { - "filePath": "src/server/clients/types.ts", - "name": "GraphQLClient", - "description": "", - "params": [ - { - "name": "query", - "description": "", - "value": "Operation extends keyof Operations", - "filePath": "src/server/clients/types.ts" - }, - { - "name": "options", - "description": "", - "value": "GraphQLQueryOptions", - "isOptional": true, - "filePath": "src/server/clients/types.ts" - } - ], - "returns": { - "filePath": "src/server/clients/types.ts", - "description": "", - "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", - "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" - }, - "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise>;" - }, - "GraphQLQueryOptions": { - "filePath": "src/server/clients/types.ts", - "name": "GraphQLQueryOptions", - "description": "", - "members": [ - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "ApiVersion", - "description": "The version of the API to use for the request.", - "isOptional": true - }, - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "headers", - "value": "Record", - "description": "Additional headers to include in the request.", - "isOptional": true - }, - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "tries", - "value": "number", - "description": "The total number of times to try the request if it fails.", - "isOptional": true }, { - "filePath": "src/server/clients/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "variables", - "value": "ApiClientRequestOptions[\"variables\"]", - "description": "The variables to pass to the operation.", - "isOptional": true - } - ], - "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n /**\n * The variables to pass to the operation.\n */\n variables?: ApiClientRequestOptions['variables'];\n /**\n * The version of the API to use for the request.\n */\n apiVersion?: ApiVersion;\n /**\n * Additional headers to include in the request.\n */\n headers?: Record;\n /**\n * The total number of times to try the request if it fails.\n */\n tries?: number;\n}" - }, - "ApiVersion": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "ApiVersion", - "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n April24 = \"2024-04\",\n July24 = \"2024-07\",\n October24 = \"2024-10\",\n Unstable = \"unstable\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October22", - "value": "2022-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January23", - "value": "2023-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April23", - "value": "2023-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July23", - "value": "2023-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October23", - "value": "2023-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January24", - "value": "2024-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April24", - "value": "2024-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July24", - "value": "2024-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October24", - "value": "2024-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Unstable", - "value": "unstable" - } - ] - }, - "RestClientWithResources": { - "filePath": "src/server/clients/admin/rest.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RestClientWithResources", - "value": "RemixRestClient & {resources: Resources}", - "description": "" + "name": "updateUsageCappedAmount", + "value": "(options: UpdateUsageCappedAmountOptions) => Promise", + "description": "Updates the capped amount for a usage billing plan.", + "examples": [ + { + "title": "Updating the capped amount for a usage billing plan", + "description": "Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n await billing.updateUsageCappedAmount({\n subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n cappedAmount: {\n amount: 10,\n currencyCode: \"USD\"\n },\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n terms: \"Usage based\"\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + } + ], + "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Redirect to a plan selection page.\n * 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.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Check what billing plans a merchant is subscribed to.\n * Use billing.check if you want to determine which plans are in use. Unlike `require`, `check` does not\n * throw an error if no active billing plans are present. \n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const { hasActivePayment, appSubscriptions } = await billing.check({\n * plans: [MONTHLY_PLAN],\n * isTest: false,\n * });\n * console.log(hasActivePayment);\n * console.log(appSubscriptions);\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Check for payments without filtering.\n * Use billing.check to see if any payments exist for the store, regardless of whether it's a test or\n * matches one or more plans.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const { hasActivePayment, appSubscriptions } = await billing.check();\n * // This will be true if any payment is found\n * console.log(hasActivePayment);\n * console.log(appSubscriptions);\n * };\n * ```\n */\n check: >(\n options?: Options,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Overriding plan settings.\n * 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.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * trialDays: 14,\n * lineItems: [\n * {\n * interval: BillingInterval.Every30Days,\n * discount: { value: { percentage: 0.1 } },\n * },\n * ],\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n\n /**\n * Creates a usage record for an app subscription.\n *\n * @returns Returns a usage record when one was created successfully.\n *\n * @example\n * Creating a usage record\n * Create a usage record for the active usage billing plan\n * ```ts\n * // /app/routes/create-usage-record.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n *\n * const chargeBilling = await billing.createUsageRecord({\n * description: \"Usage record for product creation\",\n * price: {\n * amount: 1,\n * currencyCode: \"USD\",\n * },\n * isTest: true,\n * });\n * console.log(chargeBilling);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const USAGE_PLAN = 'Usage subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [USAGE_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Usage,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n createUsageRecord: (\n options: CreateUsageRecordOptions,\n ) => Promise;\n\n /**\n * Updates the capped amount for a usage billing plan.\n *\n * @returns Redirects to a confirmation page to update the usage billing plan.\n *\n * @example\n * Updating the capped amount for a usage billing plan.\n * Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n *\n * await billing.updateUsageCappedAmount({\n * subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n * cappedAmount: {\n * amount: 10,\n * currencyCode: \"USD\"\n * },\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const USAGE_PLAN = 'Usage subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [USAGE_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Usage,\n * terms: \"Usage based\"\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n updateUsageCappedAmount: (\n options: UpdateUsageCappedAmountOptions,\n ) => Promise;\n}" }, - "RemixRestClient": { - "filePath": "src/server/clients/admin/rest.ts", - "name": "RemixRestClient", + "CancelBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CancelBillingOptions", "description": "", "members": [ { - "filePath": "src/server/clients/admin/rest.ts", - "syntaxKind": "PropertyDeclaration", - "name": "session", - "value": "Session", - "description": "" + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "Whether to use the test mode. This prevents the credit card from being charged.", + "isOptional": true }, { - "filePath": "src/server/clients/admin/rest.ts", - "syntaxKind": "MethodDeclaration", - "name": "get", - "value": "(params: GetRequestParams) => Promise", - "description": "Performs a GET request on the given path." + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "prorate", + "value": "boolean", + "description": "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.", + "isOptional": true }, { - "filePath": "src/server/clients/admin/rest.ts", - "syntaxKind": "MethodDeclaration", - "name": "post", - "value": "(params: PostRequestParams) => Promise", - "description": "Performs a POST request on the given path." - }, + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptionId", + "value": "string", + "description": "The ID of the subscription to cancel." + } + ], + "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to issue prorated credits for the unused portion of the app subscription. There will be a corresponding\n * deduction (based on revenue share) to your Partner account. For example, if a $10.00 app subscription\n * (with 0% revenue share) is cancelled and prorated half way through the billing cycle, then the merchant will be\n * credited $5.00 and that amount will be deducted from your Partner account.\n */\n prorate?: boolean;\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest?: boolean;\n}" + }, + "CheckBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CheckBillingOptions", + "description": "", + "members": [ { - "filePath": "src/server/clients/admin/rest.ts", - "syntaxKind": "MethodDeclaration", - "name": "put", - "value": "(params: PostRequestParams) => Promise", - "description": "Performs a PUT request on the given path." + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", + "isOptional": true }, { - "filePath": "src/server/clients/admin/rest.ts", - "syntaxKind": "MethodDeclaration", - "name": "delete", - "value": "(params: GetRequestParams) => Promise", - "description": "Performs a DELETE request on the given path." + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "plans", + "value": "(keyof Config[\"billing\"])[]", + "description": "The plans to check for. Must be one of the values defined in the `billing` config option.", + "isOptional": true } ], - "value": "class RemixRestClient {\n public session: Session;\n private params: AdminClientOptions['params'];\n private handleClientError: AdminClientOptions['handleClientError'];\n\n constructor({params, session, handleClientError}: AdminClientOptions) {\n this.params = params;\n this.handleClientError = handleClientError;\n this.session = session;\n }\n\n /**\n * Performs a GET request on the given path.\n */\n public async get(params: GetRequestParams) {\n return this.makeRequest({\n method: 'GET' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a POST request on the given path.\n */\n public async post(params: PostRequestParams) {\n return this.makeRequest({\n method: 'POST' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a PUT request on the given path.\n */\n public async put(params: PutRequestParams) {\n return this.makeRequest({\n method: 'PUT' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a DELETE request on the given path.\n */\n public async delete(params: DeleteRequestParams) {\n return this.makeRequest({\n method: 'DELETE' as RequestParams['method'],\n ...params,\n });\n }\n\n protected async makeRequest(params: RequestParams): Promise {\n const originalClient = new this.params.api.clients.Rest({\n session: this.session,\n });\n const originalRequest = Reflect.get(originalClient, 'request');\n\n try {\n const apiResponse = await originalRequest.call(originalClient, params);\n\n // We use a separate client for REST requests and REST resources because we want to override the API library\n // client class to return a Response object instead.\n return new Response(JSON.stringify(apiResponse.body), {\n headers: apiResponse.headers,\n });\n } catch (error) {\n if (this.handleClientError) {\n throw await this.handleClientError({\n error,\n session: this.session,\n params: this.params,\n });\n } else throw new Error(error);\n }\n }\n}" + "value": "export interface CheckBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans?: (keyof Config['billing'])[];\n}" }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "CreateUsageRecordOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CreateUsageRecordOptions", + "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "description", "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." + "description": "The description of the app usage record." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "idempotencyKey", "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." + "description": "", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." + "description": "Whether to use the test mode. This prevents the credit card from being charged." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "price", + "value": "{ amount: number; currencyCode: string; }", + "description": "The price of the app usage record." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptionLineItemId", "value": "string", - "description": "The access token for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." + "description": "", + "isOptional": true } ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" + "value": "export interface CreateUsageRecordOptions {\n /**\n * The description of the app usage record.\n */\n description: string;\n /**\n * The price of the app usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest: boolean;\n /*\n * Defines the usage pricing plan the merchant is subscribed to.\n */\n subscriptionLineItemId?: string;\n /*\n * A unique key generated to avoid duplicate charges.\n */\n idempotencyKey?: string;\n}" }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", + "RequestBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequestBillingOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." + "name": "isTest", + "value": "boolean", + "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "associated_user_scope", - "value": "string", - "description": "The effective set of scopes for the session." + "name": "plan", + "value": "keyof Config[\"billing\"]", + "description": "The plan to request. Must be one of the values defined in the `billing` config option." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "expires_in", - "value": "number", - "description": "How long the access token is valid for, in seconds." + "name": "returnUrl", + "value": "string", + "description": "The URL to return to after the merchant approves the payment.", + "isOptional": true } ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" + "value": "export interface RequestBillingOptions\n extends Omit<\n BillingRequestParams>,\n 'session' | 'plan' | 'returnObject'\n > {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", + "RequireBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequireBillingOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email", - "value": "string", - "description": "The user's email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "email_verified", + "name": "isTest", "value": "boolean", - "description": "Whether the user has verified their email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "number", - "description": "The user's ID." + "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "last_name", - "value": "string", - "description": "The user's last name." + "name": "onFailure", + "value": "(error: any) => Promise", + "description": "How to handle the request if the shop doesn't have an active payment for any plan." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "locale", - "value": "string", - "description": "The user's locale." + "name": "plans", + "value": "(keyof Config[\"billing\"])[]", + "description": "The plans to check for. Must be one of the values defined in the `billing` config option." } ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" + "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", + "UpdateUsageCappedAmountOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "UpdateUsageCappedAmountOptions", + "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "cappedAmount", + "value": "{ amount: number; currencyCode: string; }", + "description": "The maximum charge for the usage billing plan." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptionLineItemId", + "value": "string", + "description": "The subscription line item ID to update." + } + ], + "value": "export interface UpdateUsageCappedAmountOptions {\n /**\n * The subscription line item ID to update.\n */\n subscriptionLineItemId: string;\n /**\n * The maximum charge for the usage billing plan.\n */\n cappedAmount: {\n /**\n * The amount to update.\n */\n amount: number;\n /**\n * The currency code to update.\n */\n currencyCode: string;\n };\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "BillingContext" + ], + "related": [ + { + "name": "Admin context", + "subtitle": "Authenticate requests from Shopify Admin.", + "url": "/docs/api/shopify-app-remix/authenticate/admin" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "cancel", + "examples": [ + { + "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", + "codeblock": { + "title": "Cancelling a subscription", + "tabs": [ + { + "title": "/app/routes/cancel-subscription.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "check", + "examples": [ + { + "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.", + "codeblock": { + "title": "Check what billing plans a merchant is subscribed to", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check({\n plans: [MONTHLY_PLAN],\n isTest: false,\n });\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + }, + { + "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.", + "codeblock": { + "title": "Check for payments without filtering", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check();\n // This will be true if any payment is found\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "createUsageRecord", + "examples": [ + { + "description": "Create a usage record for the active usage billing plan", + "codeblock": { + "title": "Creating a usage record", + "tabs": [ + { + "title": "/app/routes/create-usage-record.ts", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n const chargeBilling = await billing.createUsageRecord({\n description: \"Usage record for product creation\",\n price: {\n amount: 1,\n currencyCode: \"USD\",\n },\n isTest: true,\n });\n console.log(chargeBilling);\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "request", + "examples": [ + { + "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", + "codeblock": { + "title": "Using a custom return URL", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + }, + { + "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.", + "codeblock": { + "title": "Overriding plan settings", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n trialDays: 14,\n lineItems: [\n {\n interval: BillingInterval.Every30Days,\n discount: { value: { percentage: 0.1 } },\n },\n ],\n }),\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "require", + "examples": [ + { + "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", + "codeblock": { + "title": "Requesting billing right away", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + }, + { + "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.", + "codeblock": { + "title": "Redirect to a plan selection page", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "updateUsageCappedAmount", + "examples": [ + { + "description": "Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.", + "codeblock": { + "title": "Updating the capped amount for a usage billing plan", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n await billing.updateUsageCappedAmount({\n subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n cappedAmount: {\n amount: 10,\n currencyCode: \"USD\"\n },\n });\n\n // App logic\n};", + "language": "typescript" + }, + { + "title": "shopify.server.ts", + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n terms: \"Usage based\"\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "Scopes", + "description": "Contains functions used to manage scopes for your app.\n\nThis object is returned on authenticated Admin requests.", + "category": "APIs", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "scopes", + "description": "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.", + "type": "ScopesApiContext", + "typeDefinitions": { + "ScopesApiContext": { + "filePath": "src/server/authenticate/admin/scope/types.ts", + "name": "ScopesApiContext", + "description": "The Scopes API enables embedded apps and extensions to request merchant consent for access scopes.", + "members": [ + { + "filePath": "src/server/authenticate/admin/scope/types.ts", + "syntaxKind": "PropertySignature", + "name": "query", + "value": "() => Promise", + "description": "Queries Shopify for the scopes for this app on this shop", + "examples": [ + { + "title": "Query for granted scopes", + "description": "Call `scopes.query` to get scope details.", + "tabs": [ + { + "code": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const scopesDetail = await scopes.query();\n\n return json({\n hasWriteProducts: scopesDetail.granted.includes('write_products'),\n });\n};\n\nexport default function Index() {\n const {hasWriteProducts} = useLoaderData();\n\n ...\n}", + "title": "/app._index.tsx" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." + "filePath": "src/server/authenticate/admin/scope/types.ts", + "syntaxKind": "PropertySignature", + "name": "request", + "value": "(scopes: string[]) => Promise", + "description": "Requests the merchant to grant the provided scopes for this app on this shop\n\nWarning: This method performs a server-side redirect.", + "examples": [ + { + "title": "Request consent from the merchant to grant the provided scopes for this app", + "description": "Call `scopes.request` to request optional scopes.", + "tabs": [ + { + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST a request to for optional scopes\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRequest = body.getAll(\"scopes\") as string[];\n\n // If the scopes are not already granted, a full page redirect to the request URL occurs\n await scopes.request(scopesToRequest);\n // otherwise return an empty response\n return json({});\n};\n\nexport default function Index() {\n const fetcher = useFetcher();\n\n const handleRequest = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", + "title": "/app/routes/app.request.tsx" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." + "filePath": "src/server/authenticate/admin/scope/types.ts", + "syntaxKind": "PropertySignature", + "name": "revoke", + "value": "(scopes: string[]) => Promise", + "description": "Revokes the provided scopes from this app on this shop\n\nWarning: This method throws an [error](https://shopify.dev/docs/api/admin-graphql/unstable/objects/AppRevokeAccessScopesAppRevokeScopeError) if the provided optional scopes contains a required scope.", + "examples": [ + { + "title": "Revoke optional scopes", + "description": "Call `scopes.revoke` to revoke optional scopes.", + "tabs": [ + { + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST optional scopes to revoke\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRevoke = body.getAll(\"scopes\") as string[];\n\n const revokedResponse = await scopes.revoke(scopesToRevoke);\n\n return json(revokedResponse);\n};\n\nexport default function Index() {\n const fetcher = useFetcher();\n\n const handleRevoke = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", + "title": "/app._index.tsx" + } + ] + } + ] } ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" + "value": "export interface ScopesApiContext {\n /**\n * Queries Shopify for the scopes for this app on this shop\n *\n * @returns {ScopesDetail} The scope details.\n *\n * @example\n * Query for granted scopes.\n * Call `scopes.query` to get scope details.\n * ```ts\n * // /app._index.tsx\n * import type { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { useLoaderData } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const scopesDetail = await scopes.query();\n *\n * return json({\n * hasWriteProducts: scopesDetail.granted.includes('write_products'),\n * });\n * };\n *\n * export default function Index() {\n * const {hasWriteProducts} = useLoaderData();\n *\n * ...\n * }\n * ```\n */\n query: () => Promise;\n\n /**\n * Requests the merchant to grant the provided scopes for this app on this shop\n *\n * Warning: This method performs a server-side redirect.\n *\n * @example\n * Request consent from the merchant to grant the provided scopes for this app.\n * Call `scopes.request` to request optional scopes.\n * ```ts\n * // /app/routes/app.request.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { useFetcher } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * // Example of an action to POST a request to for optional scopes\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const body = await request.formData();\n * const scopesToRequest = body.getAll(\"scopes\") as string[];\n *\n * // If the scopes are not already granted, a full page redirect to the request URL occurs\n * await scopes.request(scopesToRequest);\n * // otherwise return an empty response\n * return json({});\n * };\n *\n * export default function Index() {\n * const fetcher = useFetcher();\n *\n * const handleRequest = () => {\n * fetcher.submit({scopes: [\"write_products\"]}, {\n * method: \"POST\",\n * });\n * };\n *\n * ...\n * }\n * ```\n */\n request: (scopes: Scope[]) => Promise;\n\n /**\n * Revokes the provided scopes from this app on this shop\n *\n * 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.\n *\n * @example\n * Revoke optional scopes.\n * Call `scopes.revoke` to revoke optional scopes.\n * ```ts\n * // /app._index.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { useFetcher } from \"@remix-run/react\";\n * import { authenticate } from \"../shopify.server\";\n * import { json } from \"@remix-run/node\";\n *\n * // Example of an action to POST optional scopes to revoke\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { scopes } = await authenticate.admin(request);\n *\n * const body = await request.formData();\n * const scopesToRevoke = body.getAll(\"scopes\") as string[];\n *\n * const revokedResponse = await scopes.revoke(scopesToRevoke);\n *\n * return json(revokedResponse);\n * };\n *\n * export default function Index() {\n * const fetcher = useFetcher();\n *\n * const handleRevoke = () => {\n * fetcher.submit({scopes: [\"write_products\"]}, {\n * method: \"POST\",\n * });\n * };\n *\n * ...\n * }\n * ```\n */\n revoke: (scopes: Scope[]) => Promise;\n}" }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", + "ScopesDetail": { + "filePath": "src/server/authenticate/admin/scope/types.ts", + "name": "ScopesDetail", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "accessToken", - "value": "string", - "description": "The access token for the session.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "scope", - "value": "string", - "description": "The scopes for the access token.", - "isOptional": true + "name": "granted", + "value": "string[]", + "description": "The scopes that have been granted on the shop for this app" }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." + "name": "optional", + "value": "string[]", + "description": "The optional scopes that the app has declared in its configuration" }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." + "name": "required", + "value": "string[]", + "description": "The required scopes that the app has declared in its configuration" } ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" - }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" + "value": "export interface ScopesDetail {\n /**\n * The scopes that have been granted on the shop for this app\n */\n granted: Scope[];\n /**\n * The required scopes that the app has declared in its configuration\n */\n required: Scope[];\n /**\n * The optional scopes that the app has declared in its configuration\n */\n optional: Scope[];\n}" }, - "GetRequestParams": { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", - "name": "GetRequestParams", + "ScopesRevokeResponse": { + "filePath": "src/server/authenticate/admin/scope/types.ts", + "name": "ScopesRevokeResponse", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", + "filePath": "src/server/authenticate/admin/scope/types.ts", "syntaxKind": "PropertySignature", - "name": "data", - "value": "Record | string", - "description": "The request body.", - "isOptional": true - }, + "name": "revoked", + "value": "string[]", + "description": "The scopes that have been revoked on the shop for this app" + } + ], + "value": "export interface ScopesRevokeResponse {\n /**\n * The scopes that have been revoked on the shop for this app\n */\n revoked: Scope[];\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "ScopesApiContext" + ], + "related": [ + { + "name": "Admin context", + "subtitle": "Authenticate requests from Shopify Admin.", + "url": "/docs/api/shopify-app-remix/authenticate/admin" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "query", + "examples": [ + { + "description": "Call `scopes.query` to get scope details.", + "codeblock": { + "title": "Query for granted scopes", + "tabs": [ + { + "title": "/app._index.tsx", + "code": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const scopesDetail = await scopes.query();\n\n return json({\n hasWriteProducts: scopesDetail.granted.includes('write_products'),\n });\n};\n\nexport default function Index() {\n const {hasWriteProducts} = useLoaderData<typeof loader>();\n\n ...\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "request", + "examples": [ + { + "description": "Call `scopes.request` to request optional scopes.", + "codeblock": { + "title": "Request consent from the merchant to grant the provided scopes for this app", + "tabs": [ + { + "title": "/app/routes/app.request.tsx", + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST a request to for optional scopes\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRequest = body.getAll(\"scopes\") as string[];\n\n // If the scopes are not already granted, a full page redirect to the request URL occurs\n await scopes.request(scopesToRequest);\n // otherwise return an empty response\n return json({});\n};\n\nexport default function Index() {\n const fetcher = useFetcher<typeof action>();\n\n const handleRequest = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "revoke", + "examples": [ + { + "description": "Call `scopes.revoke` to revoke optional scopes.", + "codeblock": { + "title": "Revoke optional scopes", + "tabs": [ + { + "title": "/app._index.tsx", + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport { authenticate } from \"../shopify.server\";\nimport { json } from \"@remix-run/node\";\n\n// Example of an action to POST optional scopes to revoke\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { scopes } = await authenticate.admin(request);\n\n const body = await request.formData();\n const scopesToRevoke = body.getAll(\"scopes\") as string[];\n\n const revokedResponse = await scopes.revoke(scopesToRevoke);\n\n return json(revokedResponse);\n};\n\nexport default function Index() {\n const fetcher = useFetcher<typeof action>();\n\n const handleRevoke = () => {\n fetcher.submit({scopes: [\"write_products\"]}, {\n method: \"POST\",\n });\n };\n\n ...\n}", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "Flow", + "description": "Contains functions for verifying Shopify Flow extensions.\n\nSee the [Flow documentation](https://shopify.dev/docs/apps/flow/actions/endpoints) for more information.", + "category": "Authenticate", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "authenticate.flow", + "description": "Verifies requests coming from Shopify Flow extensions.", + "type": "AuthenticateFlow", + "typeDefinitions": { + "AuthenticateFlow": { + "filePath": "src/server/authenticate/flow/types.ts", + "name": "AuthenticateFlow", + "description": "", + "params": [ { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "extraHeaders", - "value": "HeaderParams", - "description": "Additional headers to be sent with the request.", - "isOptional": true - }, + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/flow/types.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/flow/types.ts", + "description": "", + "name": "Promise>", + "value": "Promise>" + }, + "value": "export type AuthenticateFlow<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" + }, + "FlowContext": { + "filePath": "src/server/authenticate/flow/types.ts", + "name": "FlowContext", + "description": "", + "members": [ { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", + "filePath": "src/server/authenticate/flow/types.ts", "syntaxKind": "PropertySignature", - "name": "path", - "value": "string", - "description": "The path to the resource, relative to the API version root." + "name": "admin", + "value": "AdminApiContext", + "description": "An admin context for the Flow request.\n\nReturned only if there is a session for the shop.", + "examples": [ + { + "title": "Flow admin context", + "description": "Use the `admin` object in the context to interact with the Admin API.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.flow(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "/app/routes/flow.tsx" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", + "filePath": "src/server/authenticate/flow/types.ts", "syntaxKind": "PropertySignature", - "name": "query", - "value": "SearchParams", - "description": "Query parameters to be sent with the request.", - "isOptional": true + "name": "payload", + "value": "any", + "description": "The payload from the Flow request.", + "examples": [ + { + "title": "Flow payload", + "description": "Get the request's POST payload.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.flow(request);\n return new Response();\n};", + "title": "/app/routes/flow.tsx" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", + "filePath": "src/server/authenticate/flow/types.ts", "syntaxKind": "PropertySignature", - "name": "tries", - "value": "number", - "description": "The maximum number of times the request can be made if it fails with a throttling or server error.", - "isOptional": true - }, + "name": "session", + "value": "Session", + "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop.", + "examples": [ + { + "title": "Shopify session for the Flow request", + "description": "Use the session associated with this request.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.flow(request);\n\n console.log(session.id)\n\n return new Response();\n};", + "title": "/app/routes/flow.tsx" + } + ] + } + ] + } + ], + "value": "export interface FlowContext<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Shopify session for the Flow request.\n * Use the session associated with this request.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session, admin } = await authenticate.flow(request);\n *\n * console.log(session.id)\n *\n * return new Response();\n * };\n * ```\n */\n session: Session;\n\n /**\n * The payload from the Flow request.\n *\n * @example\n * Flow payload.\n * Get the request's POST payload.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { payload } = await authenticate.flow(request);\n * return new Response();\n * };\n * ```\n */\n payload: any;\n\n /**\n * An admin context for the Flow request.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Flow admin context.\n * Use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.flow(request);\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n}" + } + } + } + ], + "defaultExample": { + "description": "Handle a flow action call", + "codeblock": { + "title": "Set a metafield on a customer after a flow call", + "tabs": [ + { + "title": "/app/routes/**.ts", + "language": "typescript", + "code": "import {type ActionFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {admin, payload} = await authenticate.flow(request);\n\n const customerId = payload.properties.customer_id;\n\n const response = await admin.graphql(\n `#graphql\n mutation setMetafield($customerId: ID!, $time: String!) {\n metafieldsSet(metafields: {\n ownerId: $customerId\n namespace: \"my-app\",\n key: \"last_flow_update\",\n value: $time,\n type: \"string\",\n }) {\n metafields {\n key\n value\n }\n }\n }\n `,\n {\n variables: {\n customerId,\n time: new Date().toISOString(),\n },\n },\n );\n const body = await response.json();\n\n console.log('Updated value', body.data!.metafieldsSet!.metafields![0].value);\n\n return new Response();\n};\n" + } + ] + } + }, + "jsDocTypeExamples": [ + "FlowContext" + ], + "related": [ + { + "name": "Admin API context", + "subtitle": "Interact with the Admin API.", + "url": "/docs/api/shopify-app-remix/apis/admin-api" + }, + { + "name": "Flow action endpoints", + "subtitle": "Receive requests from Flow.", + "url": "/docs/apps/flow/actions/endpoints", + "type": "shopify" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "admin", + "examples": [ + { + "description": "Use the `admin` object in the context to interact with the Admin API.", + "codeblock": { + "title": "Flow admin context", + "tabs": [ + { + "title": "/app/routes/flow.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.flow(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "payload", + "examples": [ + { + "description": "Get the request's POST payload.", + "codeblock": { + "title": "Flow payload", + "tabs": [ + { + "title": "/app/routes/flow.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.flow(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "session", + "examples": [ + { + "description": "Use the session associated with this request.", + "codeblock": { + "title": "Shopify session for the Flow request", + "tabs": [ + { + "title": "/app/routes/flow.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.flow(request);\n\n console.log(session.id)\n\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "Fulfillment Service", + "description": "Contains functions for verifying fulfillment service requests.\n\nSee the [fulfillment service documentation](https://shopify.dev/docs/apps/fulfillment/fulfillment-service-apps) for more information.", + "category": "Authenticate", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "authenticate.fulfillmentService", + "description": "Verifies requests coming from Shopify to fulfillment service apps", + "type": "AuthenticateFulfillmentService", + "typeDefinitions": { + "AuthenticateFulfillmentService": { + "filePath": "src/server/authenticate/fulfillment-service/types.ts", + "name": "AuthenticateFulfillmentService", + "description": "", + "params": [ { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "type", - "value": "DataType", - "description": "The type of data expected in the response.", - "isOptional": true + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/fulfillment-service/types.ts" } ], - "value": "export interface GetRequestParams {\n /**\n * The path to the resource, relative to the API version root.\n */\n path: string;\n /**\n * The type of data expected in the response.\n */\n type?: DataType;\n /**\n * The request body.\n */\n data?: Record | string;\n /**\n * Query parameters to be sent with the request.\n */\n query?: SearchParams;\n /**\n * Additional headers to be sent with the request.\n */\n extraHeaders?: HeaderParams;\n /**\n * The maximum number of times the request can be made if it fails with a throttling or server error.\n */\n tries?: number;\n}" - }, - "HeaderParams": { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "HeaderParams", - "value": "Record", - "description": "Headers to be sent with the request.", - "members": [] + "returns": { + "filePath": "src/server/authenticate/fulfillment-service/types.ts", + "description": "", + "name": "Promise>", + "value": "Promise>" + }, + "value": "export type AuthenticateFulfillmentService<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (\n request: Request,\n) => Promise>;" }, - "DataType": { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "DataType", - "value": "export declare enum DataType {\n JSON = \"application/json\",\n GraphQL = \"application/graphql\",\n URLEncoded = \"application/x-www-form-urlencoded\"\n}", + "FulfillmentServiceContext": { + "filePath": "src/server/authenticate/fulfillment-service/types.ts", + "name": "FulfillmentServiceContext", + "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", - "name": "JSON", - "value": "application/json" + "filePath": "src/server/authenticate/fulfillment-service/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "\nAn admin context for the fulfillment service request.\n\nReturned only if there is a session for the shop.", + "examples": [ + { + "title": "Shopify session for the fulfillment service request", + "description": "Use the session associated with this request to use the Admin GraphQL API", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin, session } = await authenticate.fulfillmentService(request);\n\n console.log(session.id)\n\n return new Response();\n}", + "title": "/app/routes/fulfillment_order_notification.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", - "name": "GraphQL", - "value": "application/graphql" + "filePath": "src/server/authenticate/fulfillment-service/types.ts", + "syntaxKind": "PropertySignature", + "name": "payload", + "value": "FulfillmentServicePayload", + "description": "The payload from the fulfillment service request.", + "examples": [ + { + "title": "Fulfillment service request payload", + "description": "Get the request's POST payload.", + "tabs": [ + { + "code": "/app/routes/fulfillment_order_notification.ts\nimport { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.fulfillmentService(request);\n if(payload.kind === 'FULFILLMENT_REQUEST') {\n // handle fulfillment request\n } else if (payload.kind === 'CANCELLATION_REQUEST') {\n // handle cancellation request\n };\nreturn new Response();", + "title": "Example" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", - "name": "URLEncoded", - "value": "application/x-www-form-urlencoded" + "filePath": "src/server/authenticate/fulfillment-service/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop.", + "examples": [ + { + "title": "Shopify session for the fulfillment service notification request", + "description": "Use the session associated with this request.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.fulfillmentService(request);\n\n console.log(session.id)\n\n return new Response();\n};", + "title": "/app/routes/fulfillment_service_notification.tsx" + } + ] + } + ] } - ] + ], + "value": "export interface FulfillmentServiceContext<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n * @example\n * Shopify session for the fulfillment service notification request.\n * Use the session associated with this request.\n * ```ts\n * // /app/routes/fulfillment_service_notification.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session, admin } = await authenticate.fulfillmentService(request);\n *\n * console.log(session.id)\n *\n * return new Response();\n * };\n * ```\n * */\n session: Session;\n /**\n *\n * An admin context for the fulfillment service request.\n *\n * Returned only if there is a session for the shop.\n * @example\n * Shopify session for the fulfillment service request.\n * Use the session associated with this request to use the Admin GraphQL API \n * ```ts\n * // /app/routes/fulfillment_order_notification.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin, session } = await authenticate.fulfillmentService(request);\n *\n * console.log(session.id)\n *\n * return new Response();\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * The payload from the fulfillment service request.\n *\n * @example\n * Fulfillment service request payload.\n * Get the request's POST payload.\n * ```ts\n * /app/routes/fulfillment_order_notification.ts\n * import { ActionFunction } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action: ActionFunction = async ({ request }) => {\n * const { payload } = await authenticate.fulfillmentService(request);\n * if(payload.kind === 'FULFILLMENT_REQUEST') {\n * // handle fulfillment request\n * } else if (payload.kind === 'CANCELLATION_REQUEST') {\n * // handle cancellation request\n * };\n * return new Response();\n * ```\n */\n payload: FulfillmentServicePayload;\n}" }, - "PostRequestParams": { - "filePath": "../shopify-api/dist/ts/lib/clients/types.d.ts", + "FulfillmentServicePayload": { + "filePath": "src/server/authenticate/fulfillment-service/types.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "PostRequestParams", - "value": "GetRequestParams & {\n data: Record | string;\n}", + "name": "FulfillmentServicePayload", + "value": "Record & {\n kind: string;\n}", "description": "" } } } ], + "defaultExample": { + "description": "Handle a fulfillment service notification call", + "codeblock": { + "title": "Consume a fulfillment service notification request", + "tabs": [ + { + "title": "/app/routes/**.ts", + "language": "typescript", + "code": "import {type ActionFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {admin, payload} = await authenticate.fulfillmentService(request);\n\n const kind = payload.kind;\n\n if (kind === 'FULFILLMENT_REQUEST') {\n const response = await admin?.graphql(\n `#graphql\n query {\n shop {\n assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {\n edges {\n node {\n id\n destination {\n firstName\n lastName\n }\n lineItems(first: 10) {\n edges {\n node {\n id\n productTitle\n sku\n remainingQuantity\n }\n }\n }\n merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {\n edges {\n node {\n message\n }\n }\n }\n }\n }\n }\n }\n }`,\n );\n\n const fulfillments = await response.json();\n console.log(fulfillments);\n }\n\n return new Response();\n};\n" + } + ] + } + }, "jsDocTypeExamples": [ - "AdminApiContext" + "FulfillmentServiceContext" ], "related": [ { - "name": "Authenticated context", - "subtitle": "Authenticate requests from Shopify Admin.", - "url": "/docs/api/shopify-app-remix/authenticate/admin" + "name": "Admin API context", + "subtitle": "Interact with the Admin API.", + "url": "/docs/api/shopify-app-remix/apis/admin-api" }, { - "name": "Unauthenticated context", - "subtitle": "Interact with the Admin API on non-Shopify requests.", - "url": "/docs/api/shopify-app-remix/unauthenticated/unauthenticated-admin" + "name": "Manage Fulfillments", + "subtitle": "Receive fulfillment requests and cancellations.", + "url": "/docs/apps/fulfillment/fulfillment-service-apps/manage-fulfillments", + "type": "shopify" } ], "examples": { "description": "", "exampleGroups": [ { - "title": "graphql", + "title": "admin", "examples": [ { - "description": "Use `admin.graphql` to make query / mutation requests.", + "description": "Use the session associated with this request to use the Admin GraphQL API", "codeblock": { - "title": "Querying the GraphQL API", + "title": "Shopify session for the fulfillment service request", "tabs": [ { - "title": "/app/routes/**\\/*.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" },\n },\n },\n );\n\n const productData = await response.json();\n return json({\n productId: productData.data?.productCreate?.product?.id,\n });\n}", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/routes/fulfillment_order_notification.ts", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin, session } = await authenticate.fulfillmentService(request);\n\n console.log(session.id)\n\n return new Response();\n}", "language": "typescript" } ] } - }, + } + ] + }, + { + "title": "payload", + "examples": [ { - "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "description": "Get the request's POST payload.", "codeblock": { - "title": "Handling GraphQL errors", + "title": "Fulfillment service request payload", "tabs": [ { - "title": "/app/routes/**\\/*.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n try {\n const response = await admin.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // error.body.errors:\n // { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "Example", + "code": "/app/routes/fulfillment_order_notification.ts\nimport { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.fulfillmentService(request);\n if(payload.kind === 'FULFILLMENT_REQUEST') {\n // handle fulfillment request\n } else if (payload.kind === 'CANCELLATION_REQUEST') {\n // handle cancellation request\n };\nreturn new Response();", "language": "typescript" } ] @@ -7554,79 +2651,274 @@ ] }, { - "title": "rest", + "title": "session", "examples": [ { - "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.", + "description": "Use the session associated with this request.", "codeblock": { - "title": "Using REST resources", + "title": "Shopify session for the fulfillment service notification request", "tabs": [ { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n return json(\n admin.rest.resources.Order.count({ session }),\n );\n};", + "title": "/app/routes/fulfillment_service_notification.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.fulfillmentService(request);\n\n console.log(session.id)\n\n return new Response();\n};", "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "App proxy", + "description": "[App proxies](/docs/apps/online-store/app-proxies) take requests to Shopify links, and redirect them to external links.\nThe `authenticate.public.appProxy` function validates requests made to app proxies, and returns a context to enable querying Shopify APIs.\n\n> Note: If the store has not installed the app, store-related properties such as `admin` or `storefront` will be `undefined`", + "category": "Authenticate", + "subCategory": "Public", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "authenticate.public.appProxy", + "description": "Authenticates requests coming to the app from Shopify app proxies.", + "type": "AuthenticateAppProxy", + "typeDefinitions": { + "AuthenticateAppProxy": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "AuthenticateAppProxy", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/public/appProxy/types.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "description": "", + "name": "Promise<\n AppProxyContext | AppProxyContextWithSession\n>", + "value": "Promise<\n AppProxyContext | AppProxyContextWithSession\n>" + }, + "value": "export type AuthenticateAppProxy<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (\n request: Request,\n) => Promise<\n AppProxyContext | AppProxyContextWithSession\n>;" + }, + "AppProxyContext": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "AppProxyContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "undefined", + "description": "No session is available for the shop that made this request. Therefore no methods for interacting with the GraphQL / REST Admin APIs are available." + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "liquid", + "value": "LiquidResponseFunction", + "description": "A utility for creating a Liquid Response.", + "examples": [ + { + "title": "Rendering liquid content", + "description": "Use the `liquid` helper to render a `Response` with Liquid content using the shop's theme. See the [Liquid reference](https://shopify.dev/docs/api/liquid) for all the features it enables.", + "tabs": [ + { + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\");\n}", + "title": "/app/routes/**\\/*.ts" + } + ] }, { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" + "title": "Rendering liquid content without a layout", + "description": "Set the `layout` option to `false` to render the Liquid content without a theme.", + "tabs": [ + { + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\n \"Hello {{shop.name}}\",\n { layout: false }\n );\n}", + "title": "/app/routes/**\\/*.ts" + } + ] + }, + { + "title": "Rendering a form in a Liquid response", + "description": "Handle form submissions through an app proxy.", + "tabs": [ + { + "code": "import { redirect } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport async function loader({ request }) {\n const { liquid } = await authenticate.public.appProxy(request);\n\n return liquid(`\n
\n \n \n
\n `);\n}\n\nexport async function action({ request }) {\n await authenticate.public.appProxy(request);\n\n const formData = await request.formData();\n const field = formData.get(\"field\")?.toString();\n\n // Perform actions here\n if (field) {\n console.log(\"Field:\", field);\n }\n\n // Return to the form page\n return redirect(\"/apps/proxy/my-action\");\n}", + "title": "app/routes/apps.proxy.my-action.tsx" + } + ] } ] + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "undefined", + "description": "No session is available for the shop that made this request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice." + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "storefront", + "value": "undefined", + "description": "No session is available for the shop that made this request. Therefore no method for interacting with the Storefront API is available." + } + ], + "value": "export interface AppProxyContext extends Context {\n /**\n * No session is available for the shop that made this request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n */\n session: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no methods for interacting with the GraphQL / REST Admin APIs are available.\n */\n admin: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no method for interacting with the Storefront API is available.\n */\n storefront: undefined;\n}" + }, + "LiquidResponseFunction": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "LiquidResponseFunction", + "description": "", + "params": [ + { + "name": "body", + "description": "", + "value": "string", + "filePath": "src/server/authenticate/public/appProxy/types.ts" + }, + { + "name": "initAndOptions", + "description": "", + "value": "number | (ResponseInit & Options)", + "isOptional": true, + "filePath": "src/server/authenticate/public/appProxy/types.ts" } + ], + "returns": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "description": "", + "name": "Response", + "value": "Response" }, - { - "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", - "codeblock": { - "title": "Performing a GET request to the REST API", - "tabs": [ + "value": "export type LiquidResponseFunction = (\n body: string,\n initAndOptions?: number | (ResponseInit & Options),\n) => Response;" + }, + "Options": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "Options", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "layout", + "value": "boolean", + "description": "Whether to use the shop's theme layout around the Liquid content.", + "isOptional": true + } + ], + "value": "interface Options {\n /**\n * Whether to use the shop's theme layout around the Liquid content.\n */\n layout?: boolean;\n}" + }, + "AppProxyContextWithSession": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "AppProxyContextWithSession", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.", + "examples": [ { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n const response = await admin.rest.get({\n path: \"/customers/count.json\",\n });\n const customers = await response.json();\n\n return json({ customers });\n};", - "language": "typescript" + "title": "Interacting with the Admin API", + "description": "Use the `admin` object to interact with the admin GraphQL API.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" }\n }\n }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "liquid", + "value": "LiquidResponseFunction", + "description": "A utility for creating a Liquid Response.", + "examples": [ + { + "title": "Rendering liquid content", + "description": "Use the `liquid` helper to render a `Response` with Liquid content using the shop's theme. See the [Liquid reference](https://shopify.dev/docs/api/liquid) for all the features it enables.", + "tabs": [ + { + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\");\n}", + "title": "/app/routes/**\\/*.ts" + } + ] + }, + { + "title": "Rendering liquid content without a layout", + "description": "Set the `layout` option to `false` to render the Liquid content without a theme.", + "tabs": [ + { + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\n \"Hello {{shop.name}}\",\n { layout: false }\n );\n}", + "title": "/app/routes/**\\/*.ts" + } + ] }, { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" + "title": "Rendering a form in a Liquid response", + "description": "Handle form submissions through an app proxy.", + "tabs": [ + { + "code": "import { redirect } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport async function loader({ request }) {\n const { liquid } = await authenticate.public.appProxy(request);\n\n return liquid(`\n
\n \n \n
\n `);\n}\n\nexport async function action({ request }) {\n await authenticate.public.appProxy(request);\n\n const formData = await request.formData();\n const field = formData.get(\"field\")?.toString();\n\n // Perform actions here\n if (field) {\n console.log(\"Field:\", field);\n }\n\n // Return to the form page\n return redirect(\"/apps/proxy/my-action\");\n}", + "title": "app/routes/apps.proxy.my-action.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The session for the shop that made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "examples": [ + { + "title": "Using the session object", + "description": "Get the session for the shop that initiated the request to the app proxy.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppModelData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }) => {\n // Get the session for the shop that initiated the request to the app proxy.\n const { session } =\n await authenticate.public.appProxy(request);\n\n // Use the session data to make to queries to your database or additional requests.\n return json(\n await getMyAppModelData({shop: session.shop})\n );\n};", + "title": "app/routes/**\\/.ts" + } + ] } ] - } - }, - { - "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", - "codeblock": { - "title": "Performing a POST request to the REST API", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n });\n\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", - "language": "typescript" - }, + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "storefront", + "value": "StorefrontContext", + "description": "Method for interacting with the Shopify Storefront Graphql API for the store that made the request.", + "examples": [ { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" + "title": "Interacting with the Storefront API", + "description": "Use the `storefront` object to interact with the GraphQL API.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(\n `#graphql\n query blogIds {\n blogs(first: 10) {\n edges {\n node {\n id\n }\n }\n }\n }`\n );\n\n return json(await response.json());\n}", + "title": "app/routes/**\\/.ts" + } + ] } ] } - } - ] - } - ] - } - }, - { - "name": "Storefront API", - "description": "Contains objects used to interact with the Storefront API.\n\nThis object is returned as part of different contexts, such as [`appProxy`](/docs/api/shopify-app-remix/authenticate/public/app-proxy), and [`unauthenticated.storefront`](/docs/api/shopify-app-remix/unauthenticated/unauthenticated-storefront).", - "category": "APIs", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "storefront", - "description": "Provides utilities that apps can use to make requests to the Storefront API.", - "type": "StorefrontContext", - "typeDefinitions": { + ], + "value": "export interface AppProxyContextWithSession<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends Context {\n /**\n * The session for the shop that made the request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * Use this to get shop or user-specific data.\n *\n * @example\n * Using the session object.\n * Get the session for the shop that initiated the request to the app proxy.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppModelData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }) => {\n * // Get the session for the shop that initiated the request to the app proxy.\n * const { session } =\n * await authenticate.public.appProxy(request);\n *\n * // Use the session data to make to queries to your database or additional requests.\n * return json(\n * await getMyAppModelData({shop: session.shop})\n * );\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.\n *\n * @example\n * Interacting with the Admin API.\n * Use the `admin` object to interact with the admin GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.public.appProxy(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * {\n * variables: {\n * input: { title: \"Product Name\" }\n * }\n * }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * Method for interacting with the Shopify Storefront Graphql API for the store that made the request.\n *\n * @example\n * Interacting with the Storefront API.\n * Use the `storefront` object to interact with the GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(\n * `#graphql\n * query blogIds {\n * blogs(first: 10) {\n * edges {\n * node {\n * id\n * }\n * }\n * }\n * }`\n * );\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: StorefrontContext;\n}" + }, "StorefrontContext": { "filePath": "src/server/clients/storefront/types.ts", "name": "StorefrontContext", @@ -7734,115 +3026,138 @@ } ], "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n /**\n * The variables to pass to the operation.\n */\n variables?: ApiClientRequestOptions['variables'];\n /**\n * The version of the API to use for the request.\n */\n apiVersion?: ApiVersion;\n /**\n * Additional headers to include in the request.\n */\n headers?: Record;\n /**\n * The total number of times to try the request if it fails.\n */\n tries?: number;\n}" - }, - "ApiVersion": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "ApiVersion", - "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n April24 = \"2024-04\",\n July24 = \"2024-07\",\n October24 = \"2024-10\",\n Unstable = \"unstable\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October22", - "value": "2022-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January23", - "value": "2023-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April23", - "value": "2023-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July23", - "value": "2023-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October23", - "value": "2023-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January24", - "value": "2024-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April24", - "value": "2024-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July24", - "value": "2024-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October24", - "value": "2024-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Unstable", - "value": "unstable" - } - ] } } } ], + "defaultExample": { + "description": "Authenticate and fetch product information", + "codeblock": { + "title": "Authenticate and fetch product information", + "tabs": [ + { + "title": "/app/routes/**.ts", + "language": "typescript", + "code": "import type {LoaderFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n const {storefront, liquid} = await authenticate.public.appProxy(request);\n\n if (!storefront) {\n return new Response();\n }\n\n const response = await storefront.graphql(\n `#graphql\n query productTitle {\n products(first: 1) {\n nodes {\n title\n }\n }\n }`,\n );\n const body = await response.json();\n\n const title = body.data.products.nodes[0].title;\n\n return liquid(`Found product ${title} from {{shop.name}}`);\n};\n" + } + ] + } + }, "jsDocTypeExamples": [ - "StorefrontContext" + "AppProxyContextWithSession" ], "related": [ { - "name": "App proxy context", - "subtitle": "Authenticate requests from Shopify app proxies.", - "url": "/docs/api/shopify-app-remix/authenticate/public/app-proxy" + "name": "Admin API context", + "subtitle": "Interact with the Admin API.", + "url": "/docs/api/shopify-app-remix/apis/admin-api" }, { - "name": "Unauthenticated context", - "subtitle": "Interact with the Storefront API on non-Shopify requests.", - "url": "/docs/api/shopify-app-remix/unauthenticated/unauthenticated-storefront" + "name": "Storefront API context", + "subtitle": "Interact with the Storefront API.", + "url": "/docs/api/shopify-app-remix/apis/storefront-api" + }, + { + "name": "Liquid reference", + "subtitle": "Use the shop's theme to render a template.", + "url": "/docs/api/liquid", + "type": "liquid" } ], "examples": { "description": "", "exampleGroups": [ { - "title": "graphql", + "title": "admin", "examples": [ { - "description": "Use `storefront.graphql` to make query / mutation requests.", + "description": "Use the `admin` object to interact with the admin GraphQL API.", "codeblock": { - "title": "Querying the GraphQL API", + "title": "Interacting with the Admin API", "tabs": [ { "title": "app/routes/**\\/.ts", - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" }\n }\n }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "liquid", + "examples": [ + { + "description": "Use the `liquid` helper to render a `Response` with Liquid content using the shop's theme. See the [Liquid reference](https://shopify.dev/docs/api/liquid) for all the features it enables.", + "codeblock": { + "title": "Rendering liquid content", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\");\n}", "language": "typescript" } ] } }, { - "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "description": "Set the `layout` option to `false` to render the Liquid content without a theme.", "codeblock": { - "title": "Handling GraphQL errors", + "title": "Rendering liquid content without a layout", "tabs": [ { "title": "/app/routes/**\\/*.ts", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { storefront } = await authenticate.public.appProxy(request);\n\n try {\n const response = await storefront.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // { errors: { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] } }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\n \"Hello {{shop.name}}\",\n { layout: false }\n );\n}", "language": "typescript" - }, + } + ] + } + }, + { + "description": "Handle form submissions through an app proxy.", + "codeblock": { + "title": "Rendering a form in a Liquid response", + "tabs": [ { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "app/routes/apps.proxy.my-action.tsx", + "code": "import { redirect } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport async function loader({ request }) {\n const { liquid } = await authenticate.public.appProxy(request);\n\n return liquid(`\n <form method=\"post\" action=\"/apps/proxy/my-action\">\n <input type=\"text\" name=\"field\" />\n <button type=\"submit\">Submit</button>\n </form>\n `);\n}\n\nexport async function action({ request }) {\n await authenticate.public.appProxy(request);\n\n const formData = await request.formData();\n const field = formData.get(\"field\")?.toString();\n\n // Perform actions here\n if (field) {\n console.log(\"Field:\", field);\n }\n\n // Return to the form page\n return redirect(\"/apps/proxy/my-action\");\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "session", + "examples": [ + { + "description": "Get the session for the shop that initiated the request to the app proxy.", + "codeblock": { + "title": "Using the session object", + "tabs": [ + { + "title": "app/routes/**\\/.ts", + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppModelData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }) => {\n // Get the session for the shop that initiated the request to the app proxy.\n const { session } =\n await authenticate.public.appProxy(request);\n\n // Use the session data to make to queries to your database or additional requests.\n return json(\n await getMyAppModelData({shop: session.shop})\n );\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "storefront", + "examples": [ + { + "description": "Use the `storefront` object to interact with the GraphQL API.", + "codeblock": { + "title": "Interacting with the Storefront API", + "tabs": [ + { + "title": "app/routes/**\\/.ts", + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(\n `#graphql\n query blogIds {\n blogs(first: 10) {\n edges {\n node {\n id\n }\n }\n }\n }`\n );\n\n return json(await response.json());\n}", "language": "typescript" } ] @@ -7854,1588 +3169,2282 @@ } }, { - "name": "shopifyApp", - "description": "Returns a set of functions that can be used by the app's backend to be able to respond to all Shopify requests.\n\nThe shape of the returned object changes depending on the value of `distribution`. If it is `AppDistribution.ShopifyAdmin`, then only `ShopifyAppBase` objects are returned, otherwise `ShopifyAppLogin` objects are included.", - "category": "Entrypoints", - "type": "function", + "name": "Checkout", + "description": "The `authenticate.public.checkout` function ensures that checkout extension requests are coming from Shopify, and returns helpers to respond with the correct headers.", + "category": "Authenticate", + "subCategory": "Public", + "type": "object", "isVisualComponent": false, "definitions": [ { - "title": "shopifyApp", - "description": "Function to create a new Shopify API object.", - "type": "ShopifyAppGeneratedType", + "title": "authenticate.public.checkout", + "description": "Authenticates requests coming from Shopify checkout extensions.", + "type": "AuthenticateCheckout", "typeDefinitions": { - "ShopifyAppGeneratedType": { - "filePath": "src/server/shopify-app.ts", - "name": "ShopifyAppGeneratedType", - "description": "Creates an object your app will use to interact with Shopify.", + "AuthenticateCheckout": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "AuthenticateCheckout", + "description": "", "params": [ { - "name": "appConfig", - "description": "Configuration options for your Shopify app, such as the scopes your app needs.", - "value": "Readonly", - "filePath": "src/server/shopify-app.ts" + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/public/checkout/types.ts" + }, + { + "name": "options", + "description": "", + "value": "AuthenticateCheckoutOptions", + "isOptional": true, + "filePath": "src/server/authenticate/public/checkout/types.ts" } ], "returns": { - "filePath": "src/server/shopify-app.ts", - "description": "`ShopifyApp` An object constructed using your appConfig. It has methods for interacting with Shopify.", - "name": "ShopifyApp>", - "value": "ShopifyApp>" + "filePath": "src/server/authenticate/public/checkout/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" }, - "value": "export function shopifyApp<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources,\n Storage extends SessionStorage,\n Future extends FutureFlagOptions = Config['future'],\n>(appConfig: Readonly): ShopifyApp {\n const api = deriveApi(appConfig);\n const config = deriveConfig(appConfig, api.config);\n const logger = overrideLogger(api.logger);\n\n if (appConfig.webhooks) {\n api.webhooks.addHandlers(appConfig.webhooks);\n }\n\n const params: BasicParams = {api, config, logger};\n\n let strategy;\n if (config.distribution === AppDistribution.ShopifyAdmin) {\n strategy = new MerchantCustomAuth(params);\n } else if (\n config.future.unstable_newEmbeddedAuthStrategy &&\n config.isEmbeddedApp\n ) {\n strategy = new TokenExchangeStrategy(params);\n } else {\n strategy = new AuthCodeFlowStrategy(params);\n }\n\n const authStrategy = authStrategyFactory({\n ...params,\n strategy,\n });\n\n const shopify:\n | AdminApp\n | AppStoreApp\n | SingleMerchantApp = {\n sessionStorage: config.sessionStorage,\n addDocumentResponseHeaders: addDocumentResponseHeadersFactory(params),\n registerWebhooks: registerWebhooksFactory(params),\n authenticate: {\n admin: authStrategy,\n flow: authenticateFlowFactory(params),\n public: authenticatePublicFactory(params),\n fulfillmentService:\n authenticateFulfillmentServiceFactory(params),\n webhook: authenticateWebhookFactory(params),\n },\n unauthenticated: {\n admin: unauthenticatedAdminContextFactory(params),\n storefront: unauthenticatedStorefrontContextFactory(params),\n },\n };\n\n if (\n isAppStoreApp(shopify, appConfig) ||\n isSingleMerchantApp(shopify, appConfig)\n ) {\n shopify.login = loginFactory(params);\n }\n\n logDisabledFutureFlags(config, logger);\n\n return shopify as ShopifyApp;\n}", - "examples": [ - { - "title": "The minimum viable configuration", - "description": "", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n apiKey: process.env.SHOPIFY_API_KEY!,\n apiSecretKey: process.env.SHOPIFY_API_SECRET!,\n scopes: process.env.SCOPES?.split(\",\")!,\n appUrl: process.env.SHOPIFY_APP_URL!,\n});\nexport default shopify;", - "title": "/shopify.server.ts" - } - ] - } - ] - }, - "ShopifyApp": { - "filePath": "src/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "ShopifyApp", - "value": "Config['distribution'] extends AppDistribution.ShopifyAdmin\n ? AdminApp\n : Config['distribution'] extends AppDistribution.SingleMerchant\n ? EnforceSessionStorage>\n : Config['distribution'] extends AppDistribution.AppStore\n ? EnforceSessionStorage>\n : EnforceSessionStorage>", - "description": "An object your app can use to interact with Shopify.\n\nBy default, the app's distribution is `AppStore`." + "value": "export type AuthenticateCheckout = (\n request: Request,\n options?: AuthenticateCheckoutOptions,\n) => Promise;" }, - "AppDistribution": { - "filePath": "src/server/types.ts", - "syntaxKind": "EnumDeclaration", - "name": "AppDistribution", - "value": "export enum AppDistribution {\n AppStore = 'app_store',\n SingleMerchant = 'single_merchant',\n ShopifyAdmin = 'shopify_admin',\n}", + "AuthenticateCheckoutOptions": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "AuthenticateCheckoutOptions", + "description": "", "members": [ { - "filePath": "src/server/types.ts", - "name": "AppStore", - "value": "app_store" - }, - { - "filePath": "src/server/types.ts", - "name": "SingleMerchant", - "value": "single_merchant" - }, - { - "filePath": "src/server/types.ts", - "name": "ShopifyAdmin", - "value": "shopify_admin" + "filePath": "src/server/authenticate/public/checkout/types.ts", + "syntaxKind": "PropertySignature", + "name": "corsHeaders", + "value": "string[]", + "description": "", + "isOptional": true } - ] + ], + "value": "export interface AuthenticateCheckoutOptions {\n corsHeaders?: string[];\n}" }, - "AdminApp": { - "filePath": "src/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "AdminApp", - "value": "ShopifyAppBase", - "description": "", + "CheckoutContext": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "CheckoutContext", + "description": "Authenticated Context for a checkout request", "members": [ { - "filePath": "src/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "addDocumentResponseHeaders", - "value": "AddDocumentResponseHeaders", - "description": "Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n\n\n\n\n", - "examples": [ - { - "title": "Return headers on all requests", - "description": "Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const addDocumentResponseheaders = shopify.addDocumentResponseheaders;", - "title": "~/shopify.server.ts" - }, - { - "code": "import { addDocumentResponseHeaders } from \"~/shopify.server\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const markup = renderToString(\n \n );\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n addDocumentResponseHeaders(request, responseHeaders);\n\n return new Response(\"\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}", - "title": "entry.server.tsx" - } - ] - } - ] - }, - { - "filePath": "src/server/types.ts", + "filePath": "src/server/authenticate/public/checkout/types.ts", "syntaxKind": "PropertySignature", - "name": "authenticate", - "value": "Authenticate", - "description": "Ways to authenticate requests from different surfaces across Shopify.", + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", "examples": [ { - "title": "Authenticate Shopify requests", - "description": "Use the functions in `authenticate` to validate requests coming from Shopify.", + "title": "Setting CORS headers for a public request", + "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", "tabs": [ { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "title": "/app/routes/**\\/*.jsx" + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", + "title": "app/routes/public/my-route.ts" } ] } ] }, { - "filePath": "src/server/types.ts", + "filePath": "src/server/authenticate/public/checkout/types.ts", "syntaxKind": "PropertySignature", - "name": "registerWebhooks", - "value": "RegisterWebhooks", - "description": "Register shop-specific webhook subscriptions using the Admin GraphQL API.\n\nIn many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n\n\n\n\n\n\n\nYou should only use this if you need shop-specific webhooks.", + "name": "sessionToken", + "value": "JwtPayload", + "description": "The decoded and validated session token for the request\n\nRefer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).", "examples": [ { - "title": "Registering shop-specific webhooks after install", - "description": "Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)", + "title": "Using the decoded session token", + "description": "Get store-specific data using the `sessionToken` object.", "tabs": [ { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});", - "title": "app/shopify.server.ts" + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", + "title": "app/routes/public/my-route.ts" } ] } ] - }, - { - "filePath": "src/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "sessionStorage", - "value": "SessionStorageType", - "description": "The `SessionStorage` instance you passed in as a config option.", - "isOptional": true, - "examples": [ + } + ], + "value": "export interface CheckoutContext {\n /**\n * The decoded and validated session token for the request\n *\n * Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).\n *\n * @example\n * Using the decoded session token.\n * Get store-specific data using the `sessionToken` object.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({shop: sessionToken.dest}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that ensures the CORS headers are set correctly for the response.\n *\n * @example\n * Setting CORS headers for a public request.\n * Use the `cors` helper to ensure your app can respond to checkout extension requests.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * { corsHeaders: [\"X-My-Custom-Header\"] }\n * );\n * const data = await getMyAppData({shop: sessionToken.dest});\n * return cors(json(data));\n * };\n * ```\n */\n cors: EnsureCORSFunction;\n}" + }, + "EnsureCORSFunction": { + "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", + "name": "EnsureCORSFunction", + "description": "", + "members": [], + "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" + } + } + } + ], + "defaultExample": { + "description": "Authenticate and return offers for the shop", + "codeblock": { + "title": "Authenticate and return offers for the shop", + "tabs": [ + { + "title": "/app/routes/**.ts", + "language": "typescript", + "code": "import type {ActionFunctionArgs, LoaderFunctionArgs} from '@remix-run/node';\nimport {json} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\nimport {getOffers} from '../offers.server';\n\n// The loader responds to preflight requests from Shopify\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n await authenticate.public.checkout(request);\n};\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {cors, sessionToken} = await authenticate.public.checkout(request);\n\n const offers = getOffers(sessionToken.dest);\n return cors(json({offers}));\n};\n" + }, + { + "title": "/app/offers.server.ts", + "language": "typescript", + "code": "// Most apps would load this from their database\nexport function getOffers(shop: string) {\n const offers: Record<any, any[]> = {\n 'shop.com': [\n {\n id: '1',\n title: '10% off',\n price: 10,\n type: 'percentage',\n },\n {\n id: '2',\n title: 'Free shipping',\n price: 0,\n type: 'shipping',\n },\n ],\n };\n\n return offers[shop];\n}\n" + } + ] + } + }, + "jsDocTypeExamples": [ + "CheckoutContext" + ], + "related": [ + { + "name": "Session token API", + "subtitle": "Checkout UI extension API for interacting with session tokens.", + "url": "/docs/api/checkout-ui-extensions/latest/apis/session-token", + "type": "shopify" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "cors", + "examples": [ + { + "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", + "codeblock": { + "title": "Setting CORS headers for a public request", + "tabs": [ { - "title": "Storing sessions with Prisma", - "description": "Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n sessionStorage: new PrismaSessionStorage(prisma),\n // ...etc\n})\n\n// shopify.sessionStorage is an instance of PrismaSessionStorage", - "title": "/app/shopify.server.ts" - } - ] + "title": "app/routes/public/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", + "language": "typescript" } ] - }, - { - "filePath": "src/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "unauthenticated", - "value": "Unauthenticated>", - "description": "Ways to get Contexts from requests that do not originate from Shopify.", - "examples": [ + } + } + ] + }, + { + "title": "sessionToken", + "examples": [ + { + "description": "Get store-specific data using the `sessionToken` object.", + "codeblock": { + "title": "Using the decoded session token", + "tabs": [ { - "title": "Using unauthenticated contexts", - "description": "Create contexts for requests that don't come from Shopify.", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "title": "/app/routes/**\\/*.jsx" - } - ] + "title": "app/routes/public/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", + "language": "typescript" } ] } - ] - }, - "AddDocumentResponseHeaders": { - "filePath": "src/server/types.ts", - "name": "AddDocumentResponseHeaders", + } + ] + } + ] + } + }, + { + "name": "Customer account", + "description": "The `authenticate.public.customerAccount` function ensures that customer account extension requests are coming from Shopify, and returns helpers to respond with the correct headers.", + "category": "Authenticate", + "subCategory": "Public", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "authenticate.public.customerAccount", + "description": "Authenticates requests coming from Shopify customer account extensions.", + "type": "AuthenticateCustomerAccount", + "typeDefinitions": { + "AuthenticateCustomerAccount": { + "filePath": "src/server/authenticate/public/customer-account/types.ts", + "name": "AuthenticateCustomerAccount", "description": "", "params": [ { "name": "request", "description": "", "value": "Request", - "filePath": "src/server/types.ts" + "filePath": "src/server/authenticate/public/customer-account/types.ts" }, { - "name": "headers", + "name": "options", "description": "", - "value": "Headers", - "filePath": "src/server/types.ts" + "value": "AuthenticateCustomerAccountOptions", + "isOptional": true, + "filePath": "src/server/authenticate/public/customer-account/types.ts" } ], "returns": { - "filePath": "src/server/types.ts", + "filePath": "src/server/authenticate/public/customer-account/types.ts", "description": "", - "name": "void", - "value": "void" + "name": "Promise", + "value": "Promise" }, - "value": "type AddDocumentResponseHeaders = (request: Request, headers: Headers) => void;" - }, - "Headers": { - "filePath": "../shopify-api/dist/ts/runtime/http/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "Headers", - "value": "Record", - "description": "", - "members": [] + "value": "export type AuthenticateCustomerAccount = (\n request: Request,\n options?: AuthenticateCustomerAccountOptions,\n) => Promise;" }, - "Authenticate": { - "filePath": "src/server/types.ts", - "name": "Authenticate", + "AuthenticateCustomerAccountOptions": { + "filePath": "src/server/authenticate/public/customer-account/types.ts", + "name": "AuthenticateCustomerAccountOptions", "description": "", "members": [ { - "filePath": "src/server/types.ts", + "filePath": "src/server/authenticate/public/customer-account/types.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AuthenticateAdmin>", - "description": "Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.\n\nExamples of when to use this are requests from your app's UI, or requests from admin extensions.\n\nIf there is no session for the Request, this will redirect the merchant to correct auth flows.", - "examples": [ - { - "title": "Authenticating a request for an embedded app", - "description": "", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = authenticate.admin(request);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "title": "/app/routes/**\\/*.jsx" - }, - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - } - ] - }, + "name": "corsHeaders", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface AuthenticateCustomerAccountOptions {\n corsHeaders?: string[];\n}" + }, + "CustomerAccountContext": { + "filePath": "src/server/authenticate/public/customer-account/types.ts", + "name": "CustomerAccountContext", + "description": "Authenticated Context for a customer account extension request", + "members": [ { - "filePath": "src/server/types.ts", + "filePath": "src/server/authenticate/public/customer-account/types.ts", "syntaxKind": "PropertySignature", - "name": "flow", - "value": "AuthenticateFlow>", - "description": "Authenticate a Flow extension Request and get back an authenticated context, containing an admin context to access the API, and the payload of the request.\n\nIf there is no session for the Request, this will return an HTTP 400 error.\n\nNote that this will always be a POST request.", + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", "examples": [ { - "title": "Authenticating a Flow extension request", - "description": "", + "title": "Setting CORS headers for a public request", + "description": "Use the `cors` helper to ensure your app can respond to customer account extension requests.", "tabs": [ { - "code": "import { ActionFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const {admin, session, payload} = authenticate.flow(request);\n\n // Perform flow extension logic\n\n // Return a 200 response\n return null;\n}", - "title": "/app/routes/**\\/*.jsx" - }, - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.customerAccount(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", + "title": "app/routes/public/my-route.ts" } ] } ] }, { - "filePath": "src/server/types.ts", + "filePath": "src/server/authenticate/public/customer-account/types.ts", "syntaxKind": "PropertySignature", - "name": "fulfillmentService", - "value": "AuthenticateFulfillmentService>", - "description": "Authenticate a request from a fulfillment service and get back an authenticated context.", + "name": "sessionToken", + "value": "JwtPayload", + "description": "The decoded and validated session token for the request\n\nRefer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).", "examples": [ { - "title": "Shopify session for the fulfillment service request", - "description": "Use the session associated with this request to use the Admin GraphQL API", + "title": "Using the decoded session token", + "description": "Get store-specific data using the `sessionToken` object.", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.fulfillmentService(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation acceptFulfillmentRequest {\n fulfillmentOrderAcceptFulfillmentRequest(\n id: \"gid://shopify/FulfillmentOrder/5014440902678\",\n message: \"Reminder that tomorrow is a holiday. We won't be able to ship this until Monday.\"){\n fulfillmentOrder {\n status\n requestStatus\n }\n }\n }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "/app/routes/fulfillment_order_notification.ts" + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.customerAccount(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", + "title": "app/routes/public/my-route.ts" } ] } ] - }, - { - "filePath": "src/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "public", - "value": "AuthenticatePublic", - "description": "Authenticate a public request and get back a session token.", - "examples": [ + } + ], + "value": "export interface CustomerAccountContext {\n /**\n * The decoded and validated session token for the request\n *\n * Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).\n *\n * @example\n * Using the decoded session token.\n * Get store-specific data using the `sessionToken` object.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.customerAccount(\n * request\n * );\n * return json(await getMyAppData({shop: sessionToken.dest}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that ensures the CORS headers are set correctly for the response.\n *\n * @example\n * Setting CORS headers for a public request.\n * Use the `cors` helper to ensure your app can respond to customer account extension requests.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.customerAccount(\n * request,\n * { corsHeaders: [\"X-My-Custom-Header\"] }\n * );\n * const data = await getMyAppData({shop: sessionToken.dest});\n * return cors(json(data));\n * };\n * ```\n */\n cors: EnsureCORSFunction;\n}" + }, + "EnsureCORSFunction": { + "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", + "name": "EnsureCORSFunction", + "description": "", + "members": [], + "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" + } + } + } + ], + "defaultExample": { + "description": "Authenticate and return offers for the customer", + "codeblock": { + "title": "Authenticate and return offers for the customer", + "tabs": [ + { + "title": "/app/routes/**.ts", + "language": "typescript", + "code": "import type {ActionFunctionArgs, LoaderFunctionArgs} from '@remix-run/node';\nimport {json} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\nimport {getOffers} from '../offers.server';\n\n// The loader responds to preflight requests from Shopify\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n await authenticate.public.customerAccount(request);\n};\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {cors, sessionToken} = await authenticate.public.customerAccount(request);\n\n // Get offers for the customer\n const offers = getOffers(sessionToken.des, sessionToken.sub);\n return cors(json({offers}));\n};\n" + }, + { + "title": "/app/offers.server.ts", + "language": "typescript", + "code": "// Most apps would load this from their database\nexport function getOffers(shop: string, customerID: string) {\n const offers: Record<string, any[]> = {\n 'shop.com': [\n {\n id: '1',\n title: '10% off',\n price: 10,\n type: 'percentage',\n customerId: 'gid://shopify/Customer/1001',\n },\n {\n id: '2',\n title: 'Free shipping',\n price: 0,\n type: 'shipping',\n customerId: 'gid://shopify/Customer/1001',\n },\n {\n id: '3',\n title: '5% off',\n price: 5,\n type: 'percentage',\n customerId: 'gid://shopify/Customer/1001',\n },\n ],\n };\n\n const allOffers = offers[shop] || [];\n // Filter offers to include only those that match the customerId\n const filteredOffers = allOffers.filter(\n (offer) => offer.customerId === customerID,\n );\n\n return filteredOffers;\n}\n" + } + ] + } + }, + "jsDocTypeExamples": [ + "CustomerAccountContext" + ], + "related": [ + { + "name": "Session token API", + "subtitle": "Customer account UI extensions API for interacting with session tokens.", + "url": "/docs/api/customer-account-ui-extensions/latest/apis/session-token", + "type": "shopify" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "cors", + "examples": [ + { + "description": "Use the `cors` helper to ensure your app can respond to customer account extension requests.", + "codeblock": { + "title": "Setting CORS headers for a public request", + "tabs": [ { - "title": "Authenticating a request from a checkout extension", - "description": "", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\nimport { getWidgets } from \"~/db/widgets\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {sessionToken} = authenticate.public.checkout(request);\n\n return json(await getWidgets(sessionToken));\n}", - "title": "/app/routes/api/checkout.jsx" - } - ] + "title": "app/routes/public/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.customerAccount(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", + "language": "typescript" } ] - }, - { - "filePath": "src/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "webhook", - "value": "AuthenticateWebhook, string>", - "description": "Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request", - "examples": [ - { - "title": "Authenticating a webhook request", - "description": "", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic, shop, session, payload } = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n // Handle the webhook\n console.log(`${TOPIC} webhook received with payload:`, JSON.stringify(payload))\n\n throw new Response();\n};", - "title": "app/routes/webhooks.ts" - } - ] - }, - { - "title": "Registering app-specific webhooks (Recommended)", - "description": "", - "tabs": [ - { - "code": "# shopify.app.toml\n[webhooks]\napi_version = \"2024-07\"\n\n [[webhooks.subscriptions]]\n topics = [\"products/create\"]\n uri = \"/webhooks/products/create\"", - "title": "Example" - } - ] - }, + } + } + ] + }, + { + "title": "sessionToken", + "examples": [ + { + "description": "Get store-specific data using the `sessionToken` object.", + "codeblock": { + "title": "Using the decoded session token", + "tabs": [ { - "title": "Registering shop-specific webhooks", - "description": "In many cases you won't need this. Please see: [https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions)", - "tabs": [ - { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});", - "title": "app/shopify.server.ts" - } - ] + "title": "app/routes/public/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.customerAccount(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", + "language": "typescript" } ] } - ], - "value": "interface Authenticate {\n /**\n * Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.\n *\n * Examples of when to use this are requests from your app's UI, or requests from admin extensions.\n *\n * If there is no session for the Request, this will redirect the merchant to correct auth flows.\n *\n * @example\n * Authenticating a request for an embedded app.\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {admin, session, sessionToken, billing} = authenticate.admin(request);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n admin: AuthenticateAdmin>;\n\n /**\n * Authenticate a Flow extension Request and get back an authenticated context, containing an admin context to access\n * the API, and the payload of the request.\n *\n * If there is no session for the Request, this will return an HTTP 400 error.\n *\n * Note that this will always be a POST request.\n *\n * @example\n * Authenticating a Flow extension request.\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { ActionFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const {admin, session, payload} = authenticate.flow(request);\n *\n * // Perform flow extension logic\n *\n * // Return a 200 response\n * return null;\n * }\n * ```\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n flow: AuthenticateFlow>;\n\n /**\n * Authenticate a request from a fulfillment service and get back an authenticated context.\n *\n * @example\n * Shopify session for the fulfillment service request.\n * Use the session associated with this request to use the Admin GraphQL API \n * ```ts\n * // /app/routes/fulfillment_order_notification.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.fulfillmentService(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation acceptFulfillmentRequest {\n * fulfillmentOrderAcceptFulfillmentRequest(\n * id: \"gid://shopify/FulfillmentOrder/5014440902678\",\n * message: \"Reminder that tomorrow is a holiday. We won't be able to ship this until Monday.\"){\n * fulfillmentOrder {\n * status\n * requestStatus\n * }\n * }\n * }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n * */\n fulfillmentService: AuthenticateFulfillmentService>;\n\n /**\n * Authenticate a public request and get back a session token.\n *\n * @example\n * Authenticating a request from a checkout extension\n *\n * ```ts\n * // /app/routes/api/checkout.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n * import { getWidgets } from \"~/db/widgets\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {sessionToken} = authenticate.public.checkout(request);\n *\n * return json(await getWidgets(sessionToken));\n * }\n * ```\n */\n public: AuthenticatePublic;\n\n /**\n * Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request\n * \n * @example\n * Authenticating a webhook request\n *\n * ```ts\n * // app/routes/webhooks.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import db from \"../db.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { topic, shop, session, payload } = await authenticate.webhook(request);\n * \n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n * \n * // Handle the webhook\n * console.log(`${TOPIC} webhook received with payload:`, JSON.stringify(payload))\n *\n * throw new Response();\n * };\n * ```\n * \n * @example\n * Registering app-specific webhooks (Recommended)\n * ```toml\n * # shopify.app.toml\n * [webhooks]\n * api_version = \"2024-07\"\n\n * [[webhooks.subscriptions]]\n * topics = [\"products/create\"]\n * uri = \"/webhooks/products/create\"\n * \n * ```\n * \n * @example\n * Registering shop-specific webhooks.\n * In many cases you won't need this. Please see: [https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions)\n * \n * ```ts\n * // app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * PRODUCTS_CREATE: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks/products/create\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * // Register webhooks for the shop\n * // In this example, every shop will have these webhooks\n * // You could wrap this in some custom shop specific conditional logic if needed\n * shopify.registerWebhooks({ session });\n * },\n * },\n * // ...etc\n * });\n * ```\n */\n webhook: AuthenticateWebhook, string>;\n}" - }, - "AuthenticateAdmin": { - "filePath": "src/server/authenticate/admin/types.ts", - "name": "AuthenticateAdmin", + } + ] + } + ] + } + }, + { + "name": "Webhook", + "description": "Contains functions for verifying Shopify webhooks.", + "category": "Authenticate", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "authenticate.webhook", + "description": "Verifies requests coming from Shopify webhooks.", + "type": "AuthenticateWebhook", + "typeDefinitions": { + "AuthenticateWebhook": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "AuthenticateWebhook", "description": "", "params": [ { "name": "request", "description": "", "value": "Request", - "filePath": "src/server/authenticate/admin/types.ts" + "filePath": "src/server/authenticate/webhooks/types.ts" } ], "returns": { - "filePath": "src/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "description": "", - "name": "Promise>", - "value": "Promise>" + "name": "Promise>", + "value": "Promise>" }, - "value": "export type AuthenticateAdmin<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" - }, - "AdminContext": { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "AdminContext", - "value": "FeatureEnabled extends true\n ? EmbeddedTypedAdminContext & ScopesContext\n : EmbeddedTypedAdminContext", - "description": "" + "value": "export type AuthenticateWebhook<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> = (request: Request) => Promise>;" }, - "FeatureEnabled": { - "filePath": "src/server/future/flags.ts", + "WebhookContext": { + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "FeatureEnabled", - "value": "Future extends FutureFlags\n ? Future[Flag] extends true\n ? true\n : false\n : false", + "name": "WebhookContext", + "value": "WebhookContextWithoutSession | WebhookContextWithSession", "description": "" }, - "FutureFlags": { - "filePath": "src/server/future/flags.ts", - "name": "FutureFlags", + "WebhookContextWithoutSession": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "WebhookContextWithoutSession", "description": "", "members": [ { - "filePath": "src/server/future/flags.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "unstable_newEmbeddedAuthStrategy", - "value": "boolean", - "description": "When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange). This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n\nLearn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).", - "isOptional": true, - "defaultValue": "false" + "name": "admin", + "value": "undefined", + "description": "" }, { - "filePath": "src/server/future/flags.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "wip_optionalScopesApi", - "value": "boolean", - "description": "When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.", - "isOptional": true, - "defaultValue": "false" - } - ], - "value": "export interface FutureFlags {\n /**\n * When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).\n * This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n *\n * Learn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).\n *\n * @default false\n */\n unstable_newEmbeddedAuthStrategy?: boolean;\n\n /**\n * When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.\n *\n * @default false\n */\n wip_optionalScopesApi?: boolean;\n}" - }, - "EmbeddedTypedAdminContext": { - "filePath": "src/server/authenticate/admin/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "EmbeddedTypedAdminContext", - "value": "Config['isEmbeddedApp'] extends false\n ? NonEmbeddedAdminContext\n : EmbeddedAdminContext", - "description": "" - }, - "NonEmbeddedAdminContext": { - "filePath": "src/server/authenticate/admin/types.ts", - "name": "NonEmbeddedAdminContext", - "description": "", - "members": [ + "name": "apiVersion", + "value": "string", + "description": "The API version used for the webhook.", + "examples": [ + { + "title": "Webhook API version", + "description": "Get the API version used for webhook request.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, { - "filePath": "src/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." + "name": "payload", + "value": "Record", + "description": "The payload from the webhook request.", + "examples": [ + { + "title": "Webhook payload", + "description": "Get the request's POST payload.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] }, { - "filePath": "src/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "billing", - "value": "BillingContext", - "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" + "name": "session", + "value": "undefined", + "description": "" }, { - "filePath": "src/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", + "name": "shop", + "value": "string", + "description": "The shop where the webhook was triggered.", "examples": [ { - "title": "Setting CORS headers for a admin request", - "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "title": "Webhook shop", + "description": "Get the shop that triggered a webhook.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", - "title": "/app/routes/admin/my-route.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] }, { - "filePath": "src/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "name": "subTopic", + "value": "string", + "description": "The sub-topic of the webhook. This is only available for certain webhooks.", + "isOptional": true, "examples": [ { - "title": "Using offline sessions", - "description": "Get your app's shop-specific data using an offline session.", + "title": "Webhook sub-topic", + "description": "Get the webhook sub-topic.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { subTopic } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] - }, + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "topic", + "value": "Topics", + "description": "The topic of the webhook.", + "examples": [ { - "title": "Using online sessions", - "description": "Get your app's user-specific data using an online session.", + "title": "Webhook topic", + "description": "Get the event topic for the webhook.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", - "title": "/app/routes/**\\/*.ts" - }, + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "webhookId", + "value": "string", + "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", + "examples": [ + { + "title": "Webhook ID", + "description": "Get the webhook ID.", + "tabs": [ { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] } ], - "value": "export interface NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" + "value": "export interface WebhookContextWithoutSession\n extends Context {\n session: undefined;\n admin: undefined;\n}" }, - "BillingContext": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "BillingContext", + "WebhookContextWithSession": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "WebhookContextWithSession", "description": "", "members": [ { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "cancel", - "value": "(options: CancelBillingOptions) => Promise", - "description": "Cancels an ongoing subscription, given its ID.", + "name": "admin", + "value": "AdminApiContext", + "description": "An admin context for the webhook.\n\nReturned only if there is a session for the shop.", "examples": [ { - "title": "Cancelling a subscription", - "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", + "title": "Webhook admin context", + "description": "Use the `admin` object in the context to interact with the Admin API.", "tabs": [ { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", - "title": "/app/routes/cancel-subscription.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "/app/routes/webhooks.tsx" } ] } ] }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "check", - "value": ">(options?: Options) => Promise", - "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "name": "apiVersion", + "value": "string", + "description": "The API version used for the webhook.", "examples": [ { - "title": "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.", + "title": "Webhook API version", + "description": "Get the API version used for webhook request.", "tabs": [ { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check({\n plans: [MONTHLY_PLAN],\n isTest: false,\n });\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", - "title": "/app/routes/**\\/*.ts" - }, + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "payload", + "value": "Record", + "description": "The payload from the webhook request.", + "examples": [ + { + "title": "Webhook payload", + "description": "Get the request's POST payload.", + "tabs": [ { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] - }, + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop. Webhook requests can trigger after an app is uninstalled If the app is already uninstalled, the session may be undefined. Therefore, you should check for the session before using it.", + "examples": [ { - "title": "Check for payments without filtering", - "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.", + "title": "Protecting against uninstalled apps", + "description": "", "tabs": [ { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check();\n // This will be true if any payment is found\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", - "title": "/app/routes/**\\/*.ts" + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session } = await authenticate.webhook(request);\n \n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n // Handle webhook request\n console.log(\"Received webhook webhook\");\n\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "createUsageRecord", - "value": "(options: CreateUsageRecordOptions) => Promise", - "description": "Creates a usage record for an app subscription.", + "name": "shop", + "value": "string", + "description": "The shop where the webhook was triggered.", "examples": [ { - "title": "Creating a usage record", - "description": "Create a usage record for the active usage billing plan", + "title": "Webhook shop", + "description": "Get the shop that triggered a webhook.", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n const chargeBilling = await billing.createUsageRecord({\n description: \"Usage record for product creation\",\n price: {\n amount: 1,\n currencyCode: \"USD\",\n },\n isTest: true,\n });\n console.log(chargeBilling);\n\n // App logic\n};", - "title": "/app/routes/create-usage-record.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "request", - "value": "(options: RequestBillingOptions) => Promise", - "description": "Requests payment for the plan.", + "name": "subTopic", + "value": "string", + "description": "The sub-topic of the webhook. This is only available for certain webhooks.", + "isOptional": true, "examples": [ { - "title": "Using a custom return URL", - "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", + "title": "Webhook sub-topic", + "description": "Get the webhook sub-topic.", "tabs": [ { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { subTopic } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] - }, + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "topic", + "value": "Topics", + "description": "The topic of the webhook.", + "examples": [ { - "title": "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.", + "title": "Webhook topic", + "description": "Get the event topic for the webhook.", "tabs": [ { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n trialDays: 14,\n lineItems: [\n {\n interval: BillingInterval.Every30Days,\n discount: { value: { percentage: 0.1 } },\n },\n ],\n }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "require", - "value": "(options: RequireBillingOptions) => Promise", - "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "name": "webhookId", + "value": "string", + "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", "examples": [ { - "title": "Requesting billing right away", - "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", + "title": "Webhook ID", + "description": "Get the webhook ID.", "tabs": [ { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] - }, + } + ] + } + ], + "value": "export interface WebhookContextWithSession<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> extends Context {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n * Webhook requests can trigger after an app is uninstalled\n * If the app is already uninstalled, the session may be undefined.\n * Therefore, you should check for the session before using it.\n *\n * @example\n * Protecting against uninstalled apps.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"~/shopify.server\";\n\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session } = await authenticate.webhook(request);\n * \n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n *\n * // Handle webhook request\n * console.log(\"Received webhook webhook\");\n *\n * return new Response();\n * };\n * ```\n */\n session: Session;\n\n /**\n * An admin context for the webhook.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Webhook admin context.\n * Use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n}" + } + } + } + ], + "defaultExample": { + "description": "Update a metafield when a product is updated", + "codeblock": { + "title": "Update a metafield when a product is updated", + "tabs": [ + { + "title": "/app/routes/**.ts", + "language": "typescript", + "code": "import {type ActionFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {topic, admin, payload, session} = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n switch (topic) {\n case 'PRODUCTS_UPDATE':\n await admin.graphql(\n `#graphql\n mutation setMetafield($productId: ID!, $time: String!) {\n metafieldsSet(metafields: {\n ownerId: $productId\n namespace: \"my-app\",\n key: \"webhook_received_at\",\n value: $time,\n type: \"string\",\n }) {\n metafields {\n key\n value\n }\n }\n }\n `,\n {\n variables: {\n productId: payload.admin_graphql_api_id,\n time: new Date().toISOString(),\n },\n },\n );\n }\n\n return new Response();\n};\n" + } + ] + } + }, + "jsDocTypeExamples": [ + "WebhookContextWithSession" + ], + "related": [ + { + "name": "Admin API context", + "subtitle": "Interact with the Admin API.", + "url": "/docs/api/shopify-app-remix/apis/admin-api" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "admin", + "examples": [ + { + "description": "Use the `admin` object in the context to interact with the Admin API.", + "codeblock": { + "title": "Webhook admin context", + "tabs": [ { - "title": "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.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "apiVersion", + "examples": [ + { + "description": "Get the API version used for webhook request.", + "codeblock": { + "title": "Webhook API version", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "payload", + "examples": [ + { + "description": "Get the request's POST payload.", + "codeblock": { + "title": "Webhook payload", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "session", + "examples": [ + { + "description": "", + "codeblock": { + "title": "Protecting against uninstalled apps", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"~/shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session } = await authenticate.webhook(request);\n \n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n // Handle webhook request\n console.log(\"Received webhook webhook\");\n\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "shop", + "examples": [ + { + "description": "Get the shop that triggered a webhook.", + "codeblock": { + "title": "Webhook shop", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "subTopic", + "examples": [ + { + "description": "Get the webhook sub-topic.", + "codeblock": { + "title": "Webhook sub-topic", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { subTopic } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "topic", + "examples": [ + { + "description": "Get the event topic for the webhook.", + "codeblock": { + "title": "Webhook topic", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "language": "typescript" } ] - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "updateUsageCappedAmount", - "value": "(options: UpdateUsageCappedAmountOptions) => Promise", - "description": "Updates the capped amount for a usage billing plan.", - "examples": [ + } + } + ] + }, + { + "title": "webhookId", + "examples": [ + { + "description": "Get the webhook ID.", + "codeblock": { + "title": "Webhook ID", + "tabs": [ { - "title": "Updating the capped amount for a usage billing plan", - "description": "Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n await billing.updateUsageCappedAmount({\n subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n cappedAmount: {\n amount: 10,\n currencyCode: \"USD\"\n },\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n terms: \"Usage based\"\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" } ] } - ], - "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Redirect to a plan selection page.\n * 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.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Check what billing plans a merchant is subscribed to.\n * Use billing.check if you want to determine which plans are in use. Unlike `require`, `check` does not\n * throw an error if no active billing plans are present. \n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const { hasActivePayment, appSubscriptions } = await billing.check({\n * plans: [MONTHLY_PLAN],\n * isTest: false,\n * });\n * console.log(hasActivePayment);\n * console.log(appSubscriptions);\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Check for payments without filtering.\n * Use billing.check to see if any payments exist for the store, regardless of whether it's a test or\n * matches one or more plans.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const { hasActivePayment, appSubscriptions } = await billing.check();\n * // This will be true if any payment is found\n * console.log(hasActivePayment);\n * console.log(appSubscriptions);\n * };\n * ```\n */\n check: >(\n options?: Options,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Overriding plan settings.\n * 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.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * trialDays: 14,\n * lineItems: [\n * {\n * interval: BillingInterval.Every30Days,\n * discount: { value: { percentage: 0.1 } },\n * },\n * ],\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n\n /**\n * Creates a usage record for an app subscription.\n *\n * @returns Returns a usage record when one was created successfully.\n *\n * @example\n * Creating a usage record\n * Create a usage record for the active usage billing plan\n * ```ts\n * // /app/routes/create-usage-record.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n *\n * const chargeBilling = await billing.createUsageRecord({\n * description: \"Usage record for product creation\",\n * price: {\n * amount: 1,\n * currencyCode: \"USD\",\n * },\n * isTest: true,\n * });\n * console.log(chargeBilling);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const USAGE_PLAN = 'Usage subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [USAGE_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Usage,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n createUsageRecord: (\n options: CreateUsageRecordOptions,\n ) => Promise;\n\n /**\n * Updates the capped amount for a usage billing plan.\n *\n * @returns Redirects to a confirmation page to update the usage billing plan.\n *\n * @example\n * Updating the capped amount for a usage billing plan.\n * Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n *\n * await billing.updateUsageCappedAmount({\n * subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n * cappedAmount: {\n * amount: 10,\n * currencyCode: \"USD\"\n * },\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const USAGE_PLAN = 'Usage subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [USAGE_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Usage,\n * terms: \"Usage based\"\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n updateUsageCappedAmount: (\n options: UpdateUsageCappedAmountOptions,\n ) => Promise;\n}" + } + ] + } + ] + } + }, + { + "name": "Admin API", + "description": "Contains objects used to interact with the Admin API.\n\nThis object is returned as part of different contexts, such as [`admin`](/docs/api/shopify-app-remix/authenticate/admin), [`unauthenticated.admin`](/docs/api/shopify-app-remix/unauthenticated/unauthenticated-admin), and [`webhook`](/docs/api/shopify-app-remix/authenticate/webhook).", + "category": "APIs", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "admin", + "description": "Provides utilities that apps can use to make requests to the Admin API.", + "type": "AdminApiContext", + "typeDefinitions": { + "AdminApiContext": { + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminApiContext", + "value": "FeatureEnabled extends true\n ? AdminApiContextWithoutRest\n : AdminApiContextWithRest", + "description": "" }, - "CancelBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "CancelBillingOptions", + "FeatureEnabled": { + "filePath": "src/server/future/flags.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "FeatureEnabled", + "value": "Future extends FutureFlags\n ? Future[Flag] extends true\n ? true\n : false\n : false", + "description": "" + }, + "FutureFlags": { + "filePath": "src/server/future/flags.ts", + "name": "FutureFlags", "description": "", "members": [ { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", - "name": "isTest", + "name": "removeRest", "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged.", - "isOptional": true + "description": "When enabled, methods for interacting with the admin REST API will not be returned.\n\nThis affects:\n\n* `authenticate.admin(request)` * `authenticate.webhook(request)` * `authenticate.flow(request)` * `authenticate.appProxy(request)` * `authenticate.fulfillmentService(request)` * `unauthenticated.admin(shop)`\n\nIn a future release we will remove REST from the package completely.\n\nPlease see: [https://www.shopify.com/ca/partners/blog/all-in-on-graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)", + "isOptional": true, + "defaultValue": "false" }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", - "name": "prorate", + "name": "unstable_newEmbeddedAuthStrategy", "value": "boolean", - "description": "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.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "subscriptionId", - "value": "string", - "description": "The ID of the subscription to cancel." - } - ], - "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to issue prorated credits for the unused portion of the app subscription. There will be a corresponding\n * deduction (based on revenue share) to your Partner account. For example, if a $10.00 app subscription\n * (with 0% revenue share) is cancelled and prorated half way through the billing cycle, then the merchant will be\n * credited $5.00 and that amount will be deducted from your Partner account.\n */\n prorate?: boolean;\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest?: boolean;\n}" - }, - "AppSubscription": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppSubscription", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the app subscription." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "lineItems", - "value": "ActiveSubscriptionLineItem[]", - "description": "", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "name", - "value": "string", - "description": "The name of the purchased plan." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "status", - "value": "string", - "description": "" + "description": "When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange). This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n\nLearn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).", + "isOptional": true, + "defaultValue": "false" }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", - "name": "test", + "name": "wip_optionalScopesApi", "value": "boolean", - "description": "Whether this is a test subscription." - } - ], - "value": "export interface AppSubscription {\n /**\n * The ID of the app subscription.\n */\n id: string;\n /**\n * The name of the purchased plan.\n */\n name: string;\n /**\n * Whether this is a test subscription.\n */\n test: boolean;\n lineItems?: ActiveSubscriptionLineItem[];\n status: string;\n}" - }, - "ActiveSubscriptionLineItem": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "ActiveSubscriptionLineItem", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "" - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "plan", - "value": "AppPlan", - "description": "" + "description": "When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.", + "isOptional": true, + "defaultValue": "false" } ], - "value": "export interface ActiveSubscriptionLineItem {\n id: string;\n plan: AppPlan;\n}" + "value": "export interface FutureFlags {\n /**\n * When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).\n * This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n *\n * Learn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).\n *\n * @default false\n */\n unstable_newEmbeddedAuthStrategy?: boolean;\n\n /**\n * When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.\n *\n * @default false\n */\n wip_optionalScopesApi?: boolean;\n\n /**\n * When enabled, methods for interacting with the admin REST API will not be returned.\n *\n * This affects:\n *\n * * `authenticate.admin(request)`\n * * `authenticate.webhook(request)`\n * * `authenticate.flow(request)`\n * * `authenticate.appProxy(request)`\n * * `authenticate.fulfillmentService(request)`\n * * `unauthenticated.admin(shop)`\n *\n * In a future release we will remove REST from the package completely.\n *\n * Please see: [https://www.shopify.com/ca/partners/blog/all-in-on-graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)\n *\n * @default false\n */\n removeRest?: boolean;\n}" }, - "AppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppPlan", + "AdminApiContextWithoutRest": { + "filePath": "src/server/clients/admin/types.ts", + "name": "AdminApiContextWithoutRest", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "pricingDetails", - "value": "RecurringAppPlan | UsageAppPlan", - "description": "" + "name": "graphql", + "value": "GraphQLClient", + "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `admin.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" },\n },\n },\n );\n\n const productData = await response.json();\n return json({\n productId: productData.data?.productCreate?.product?.id,\n });\n}", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "Handling GraphQL errors", + "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n try {\n const response = await admin.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // error.body.errors:\n // { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] } ], - "value": "export interface AppPlan {\n pricingDetails: RecurringAppPlan | UsageAppPlan;\n}" + "value": "export interface AdminApiContextWithoutRest {\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-app-js/blob/main/packages/apps/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * {\n * variables: {\n * input: { title: \"Product Name\" },\n * },\n * },\n * );\n *\n * const productData = await response.json();\n * return json({\n * productId: productData.data?.productCreate?.product?.id,\n * });\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Handling GraphQL errors.\n * Catch `GraphqlQueryError` errors to see error messages from the API.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { admin } = await authenticate.admin(request);\n *\n * try {\n * const response = await admin.graphql(\n * `#graphql\n * query incorrectQuery {\n * products(first: 10) {\n * nodes {\n * not_a_field\n * }\n * }\n * }`,\n * );\n *\n * return json({ data: await response.json() });\n * } catch (error) {\n * if (error instanceof GraphqlQueryError) {\n * // error.body.errors:\n * // { graphQLErrors: [\n * // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n * // ] }\n * return json({ errors: error.body?.errors }, { status: 500 });\n * }\n * return json({ message: \"An error occurred\" }, { status: 500 });\n * }\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n graphql: GraphQLClient;\n}" }, - "RecurringAppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "RecurringAppPlan", + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "discount", - "value": "AppPlanDiscount", - "description": "" - }, + "params": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "interval", - "value": "BillingInterval.Every30Days | BillingInterval.Annual", - "description": "" + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "price", - "value": "Money", - "description": "" + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" } ], - "value": "export interface RecurringAppPlan {\n interval: BillingInterval.Every30Days | BillingInterval.Annual;\n price: Money;\n discount: AppPlanDiscount;\n}" + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise>;" }, - "AppPlanDiscount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "AppPlanDiscount", + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "durationLimitInIntervals", - "value": "number", - "description": "" + "name": "apiVersion", + "value": "ApiVersion", + "description": "The version of the API to use for the request.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "priceAfterDiscount", - "value": "Money", - "description": "" + "name": "headers", + "value": "Record", + "description": "Additional headers to include in the request.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "remainingDurationInIntervals", + "name": "tries", "value": "number", - "description": "" + "description": "The total number of times to try the request if it fails.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "value", - "value": "AppPlanDiscountAmount", - "description": "" + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "The variables to pass to the operation.", + "isOptional": true } ], - "value": "export interface AppPlanDiscount {\n durationLimitInIntervals: number;\n remainingDurationInIntervals: number;\n priceAfterDiscount: Money;\n value: AppPlanDiscountAmount;\n}" + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n /**\n * The variables to pass to the operation.\n */\n variables?: ApiClientRequestOptions['variables'];\n /**\n * The version of the API to use for the request.\n */\n apiVersion?: ApiVersion;\n /**\n * Additional headers to include in the request.\n */\n headers?: Record;\n /**\n * The total number of times to try the request if it fails.\n */\n tries?: number;\n}" }, - "Money": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "Money", + "AdminApiContextWithRest": { + "filePath": "src/server/clients/admin/types.ts", + "name": "AdminApiContextWithRest", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "" + "name": "graphql", + "value": "GraphQLClient", + "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `admin.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" },\n },\n },\n );\n\n const productData = await response.json();\n return json({\n productId: productData.data?.productCreate?.product?.id,\n });\n}", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "Handling GraphQL errors", + "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { admin } = await authenticate.admin(request);\n\n try {\n const response = await admin.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // error.body.errors:\n // { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "currencyCode", - "value": "string", - "description": "" + "name": "rest", + "value": "RestClientWithResources", + "description": "Methods for interacting with the Shopify Admin REST API", + "deprecationMessage": "In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql).\n\nThere 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.\n\n{@link https://shopify.dev/docs/api/admin-rest}", + "examples": [ + { + "title": "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.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n return json(\n admin.rest.resources.Order.count({ session }),\n );\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "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", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n const response = await admin.rest.get({\n path: \"/customers/count.json\",\n });\n const customers = await response.json();\n\n return json({ customers });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "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", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const {\n admin,\n session,\n } = await authenticate.admin(request);\n\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n });\n\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] } ], - "value": "export interface Money {\n amount: number;\n currencyCode: string;\n}" + "value": "export interface AdminApiContextWithRest<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminApiContextWithoutRest {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * @deprecated In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql).\n *\n * 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.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * 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. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const {\n * admin,\n * session,\n * } = await authenticate.admin(request);\n *\n * return json(\n * admin.rest.resources.Order.count({ session }),\n * );\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const {\n * admin,\n * session,\n * } = await authenticate.admin(request);\n *\n * const response = await admin.rest.get({\n * path: \"/customers/count.json\",\n * });\n * const customers = await response.json();\n *\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const {\n * admin,\n * session,\n * } = await authenticate.admin(request);\n *\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n *\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n}" }, - "AppPlanDiscountAmount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "RestClientWithResources": { + "filePath": "src/server/clients/admin/rest.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "AppPlanDiscountAmount", - "value": "BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage", + "name": "RestClientWithResources", + "value": "RemixRestClient & {resources: Resources}", "description": "" }, - "BillingConfigSubscriptionPlanDiscountAmount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionPlanDiscountAmount", + "RemixRestClient": { + "filePath": "src/server/clients/admin/rest.ts", + "name": "RemixRestClient", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "The amount to discount.\n\nCannot be set if `percentage` is set." + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "percentage", - "value": "never", - "description": "The percentage to discount.\n\nCannot be set if `amount` is set.", - "isOptional": true + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a GET request on the given path.", + "deprecationMessage": "In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a POST request on the given path.", + "deprecationMessage": "In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a PUT request on the given path.", + "deprecationMessage": "In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a DELETE request on the given path.", + "deprecationMessage": "In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)." } ], - "value": "export interface BillingConfigSubscriptionPlanDiscountAmount {\n /**\n * The amount to discount.\n *\n * Cannot be set if `percentage` is set.\n */\n amount: number;\n /**\n * The percentage to discount.\n *\n * Cannot be set if `amount` is set.\n */\n percentage?: never;\n}" - }, - "BillingConfigSubscriptionPlanDiscountPercentage": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionPlanDiscountPercentage", + "value": "class RemixRestClient {\n public session: Session;\n private params: AdminClientOptions['params'];\n private handleClientError: AdminClientOptions['handleClientError'];\n\n constructor({params, session, handleClientError}: AdminClientOptions) {\n this.params = params;\n this.handleClientError = handleClientError;\n this.session = session;\n }\n\n /**\n * Performs a GET request on the given path.\n *\n * @deprecated In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql).\n */\n public async get(params: GetRequestParams) {\n return this.makeRequest({\n method: 'GET' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a POST request on the given path.\n *\n * @deprecated In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql).\n */\n public async post(params: PostRequestParams) {\n return this.makeRequest({\n method: 'POST' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a PUT request on the given path.\n *\n * @deprecated In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql).\n */\n public async put(params: PutRequestParams) {\n return this.makeRequest({\n method: 'PUT' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a DELETE request on the given path.\n *\n * @deprecated In a future major release REST will be removed from this package. Please see [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql).\n */\n public async delete(params: DeleteRequestParams) {\n return this.makeRequest({\n method: 'DELETE' as RequestParams['method'],\n ...params,\n });\n }\n\n protected async makeRequest(params: RequestParams): Promise {\n const originalClient = new this.params.api.clients.Rest({\n session: this.session,\n });\n const originalRequest = Reflect.get(originalClient, 'request');\n\n try {\n const apiResponse = await originalRequest.call(originalClient, params);\n\n // We use a separate client for REST requests and REST resources because we want to override the API library\n // client class to return a Response object instead.\n return new Response(JSON.stringify(apiResponse.body), {\n headers: apiResponse.headers,\n });\n } catch (error) {\n if (this.handleClientError) {\n throw await this.handleClientError({\n error,\n session: this.session,\n params: this.params,\n });\n } else throw new Error(error);\n }\n }\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "AdminApiContext" + ], + "related": [ + { + "name": "Authenticated context", + "subtitle": "Authenticate requests from Shopify Admin.", + "url": "/docs/api/shopify-app-remix/authenticate/admin" + }, + { + "name": "Unauthenticated context", + "subtitle": "Interact with the Admin API on non-Shopify requests.", + "url": "/docs/api/shopify-app-remix/unauthenticated/unauthenticated-admin" + } + ] + }, + { + "name": "Storefront API", + "description": "Contains objects used to interact with the Storefront API.\n\nThis object is returned as part of different contexts, such as [`appProxy`](/docs/api/shopify-app-remix/authenticate/public/app-proxy), and [`unauthenticated.storefront`](/docs/api/shopify-app-remix/unauthenticated/unauthenticated-storefront).", + "category": "APIs", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "storefront", + "description": "Provides utilities that apps can use to make requests to the Storefront API.", + "type": "StorefrontContext", + "typeDefinitions": { + "StorefrontContext": { + "filePath": "src/server/clients/storefront/types.ts", + "name": "StorefrontContext", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "amount", - "value": "never", - "description": "The amount to discount.\n\nCannot be set if `percentage` is set.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/storefront/types.ts", "syntaxKind": "PropertySignature", - "name": "percentage", - "value": "number", - "description": "The percentage to discount.\n\nCannot be set if `amount` is set." + "name": "graphql", + "value": "GraphQLClient", + "description": "Method for interacting with the Shopify Storefront GraphQL API\n\nIf you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `storefront.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "title": "app/routes/**\\/.ts" + } + ] + }, + { + "title": "Handling GraphQL errors", + "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { storefront } = await authenticate.public.appProxy(request);\n\n try {\n const response = await storefront.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // { errors: { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] } }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] } ], - "value": "export interface BillingConfigSubscriptionPlanDiscountPercentage {\n /**\n * The amount to discount.\n *\n * Cannot be set if `percentage` is set.\n */\n amount?: never;\n /**\n * The percentage to discount.\n *\n * Cannot be set if `amount` is set.\n */\n percentage: number;\n}" + "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n *\n * @example\n * Handling GraphQL errors.\n * Catch `GraphqlQueryError` errors to see error messages from the API.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * try {\n * const response = await storefront.graphql(\n * `#graphql\n * query incorrectQuery {\n * products(first: 10) {\n * nodes {\n * not_a_field\n * }\n * }\n * }`,\n * );\n *\n * return json({ data: await response.json() });\n * } catch (error) {\n * if (error instanceof GraphqlQueryError) {\n * // { errors: { graphQLErrors: [\n * // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n * // ] } }\n * return json({ errors: error.body?.errors }, { status: 500 });\n * }\n * return json({ message: \"An error occurred\" }, { status: 500 });\n * }\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n graphql: GraphQLClient;\n}" }, - "BillingInterval": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "BillingInterval", - "value": "export declare enum BillingInterval {\n OneTime = \"ONE_TIME\",\n Every30Days = \"EVERY_30_DAYS\",\n Annual = \"ANNUAL\",\n Usage = \"USAGE\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "OneTime", - "value": "ONE_TIME" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Every30Days", - "value": "EVERY_30_DAYS" - }, + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", + "description": "", + "params": [ { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Annual", - "value": "ANNUAL" + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" }, { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Usage", - "value": "USAGE" + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" } - ] + ], + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise>;" }, - "UsageAppPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "UsageAppPlan", + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "balanceUsed", - "value": "Money", - "description": "" + "name": "apiVersion", + "value": "ApiVersion", + "description": "The version of the API to use for the request.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "cappedAmount", - "value": "Money", - "description": "" + "name": "headers", + "value": "Record", + "description": "Additional headers to include in the request.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "terms", - "value": "string", - "description": "" - } - ], - "value": "export interface UsageAppPlan {\n balanceUsed: Money;\n cappedAmount: Money;\n terms: string;\n}" - }, - "CheckBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "CheckBillingOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", + "name": "tries", + "value": "number", + "description": "The total number of times to try the request if it fails.", "isOptional": true }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "plans", - "value": "(keyof Config[\"billing\"])[]", - "description": "The plans to check for. Must be one of the values defined in the `billing` config option.", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "The variables to pass to the operation.", "isOptional": true } ], - "value": "export interface CheckBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans?: (keyof Config['billing'])[];\n}" + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n /**\n * The variables to pass to the operation.\n */\n variables?: ApiClientRequestOptions['variables'];\n /**\n * The version of the API to use for the request.\n */\n apiVersion?: ApiVersion;\n /**\n * Additional headers to include in the request.\n */\n headers?: Record;\n /**\n * The total number of times to try the request if it fails.\n */\n tries?: number;\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "StorefrontContext" + ], + "related": [ + { + "name": "App proxy context", + "subtitle": "Authenticate requests from Shopify app proxies.", + "url": "/docs/api/shopify-app-remix/authenticate/public/app-proxy" + }, + { + "name": "Unauthenticated context", + "subtitle": "Interact with the Storefront API on non-Shopify requests.", + "url": "/docs/api/shopify-app-remix/unauthenticated/unauthenticated-storefront" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "graphql", + "examples": [ + { + "description": "Use `storefront.graphql` to make query / mutation requests.", + "codeblock": { + "title": "Querying the GraphQL API", + "tabs": [ + { + "title": "app/routes/**\\/.ts", + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "language": "typescript" + } + ] + } + }, + { + "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "codeblock": { + "title": "Handling GraphQL errors", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { storefront } = await authenticate.public.appProxy(request);\n\n try {\n const response = await storefront.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // { errors: { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] } }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", + "language": "typescript" + }, + { + "title": "/app/shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "shopifyApp", + "description": "Returns a set of functions that can be used by the app's backend to be able to respond to all Shopify requests.\n\nThe shape of the returned object changes depending on the value of `distribution`. If it is `AppDistribution.ShopifyAdmin`, then only `ShopifyAppBase` objects are returned, otherwise `ShopifyAppLogin` objects are included.", + "category": "Entrypoints", + "type": "function", + "isVisualComponent": false, + "definitions": [ + { + "title": "shopifyApp", + "description": "Function to create a new Shopify API object.", + "type": "ShopifyAppGeneratedType", + "typeDefinitions": { + "ShopifyAppGeneratedType": { + "filePath": "src/server/shopify-app.ts", + "name": "ShopifyAppGeneratedType", + "description": "Creates an object your app will use to interact with Shopify.", + "params": [ + { + "name": "appConfig", + "description": "Configuration options for your Shopify app, such as the scopes your app needs.", + "value": "Readonly", + "filePath": "src/server/shopify-app.ts" + } + ], + "returns": { + "filePath": "src/server/shopify-app.ts", + "description": "`ShopifyApp` An object constructed using your appConfig. It has methods for interacting with Shopify.", + "name": "ShopifyApp>", + "value": "ShopifyApp>" + }, + "value": "export function shopifyApp<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources,\n Storage extends SessionStorage,\n Future extends FutureFlagOptions = Config['future'],\n>(appConfig: Readonly): ShopifyApp {\n const api = deriveApi(appConfig);\n const config = deriveConfig(appConfig, api.config);\n const logger = overrideLogger(api.logger);\n\n if (appConfig.webhooks) {\n api.webhooks.addHandlers(appConfig.webhooks);\n }\n\n const params: BasicParams = {api, config, logger};\n\n let strategy;\n if (config.distribution === AppDistribution.ShopifyAdmin) {\n strategy = new MerchantCustomAuth(params);\n } else if (\n config.future.unstable_newEmbeddedAuthStrategy &&\n config.isEmbeddedApp\n ) {\n strategy = new TokenExchangeStrategy(params);\n } else {\n strategy = new AuthCodeFlowStrategy(params);\n }\n\n const authStrategy = authStrategyFactory({\n ...params,\n strategy,\n });\n\n const shopify:\n | AdminApp\n | AppStoreApp\n | SingleMerchantApp = {\n sessionStorage: config.sessionStorage,\n addDocumentResponseHeaders: addDocumentResponseHeadersFactory(params),\n registerWebhooks: registerWebhooksFactory(params),\n authenticate: {\n admin: authStrategy,\n flow: authenticateFlowFactory(params),\n public: authenticatePublicFactory(params),\n fulfillmentService: authenticateFulfillmentServiceFactory<\n Config,\n Resources\n >(params),\n webhook: authenticateWebhookFactory(params),\n },\n unauthenticated: {\n admin: unauthenticatedAdminContextFactory(params),\n storefront: unauthenticatedStorefrontContextFactory(params),\n },\n };\n\n if (\n isAppStoreApp(shopify, appConfig) ||\n isSingleMerchantApp(shopify, appConfig)\n ) {\n shopify.login = loginFactory(params);\n }\n\n logDisabledFutureFlags(config, logger);\n\n return shopify as ShopifyApp;\n}", + "examples": [ + { + "title": "The minimum viable configuration", + "description": "", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n apiKey: process.env.SHOPIFY_API_KEY!,\n apiSecretKey: process.env.SHOPIFY_API_SECRET!,\n scopes: process.env.SCOPES?.split(\",\")!,\n appUrl: process.env.SHOPIFY_APP_URL!,\n});\nexport default shopify;", + "title": "/shopify.server.ts" + } + ] + } + ] }, - "BillingCheckResponseObject": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingCheckResponseObject", - "description": "", + "ShopifyApp": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyApp", + "value": "Config['distribution'] extends AppDistribution.ShopifyAdmin\n ? AdminApp\n : Config['distribution'] extends AppDistribution.SingleMerchant\n ? EnforceSessionStorage>\n : Config['distribution'] extends AppDistribution.AppStore\n ? EnforceSessionStorage>\n : EnforceSessionStorage>", + "description": "An object your app can use to interact with Shopify.\n\nBy default, the app's distribution is `AppStore`." + }, + "AppDistribution": { + "filePath": "src/server/types.ts", + "syntaxKind": "EnumDeclaration", + "name": "AppDistribution", + "value": "export enum AppDistribution {\n AppStore = 'app_store',\n SingleMerchant = 'single_merchant',\n ShopifyAdmin = 'shopify_admin',\n}", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "appSubscriptions", - "value": "AppSubscription[]", - "description": "The active subscriptions the shop has." + "filePath": "src/server/types.ts", + "name": "AppStore", + "value": "app_store" }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "hasActivePayment", - "value": "boolean", - "description": "Whether the user has an active payment method." + "filePath": "src/server/types.ts", + "name": "SingleMerchant", + "value": "single_merchant" }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "oneTimePurchases", - "value": "OneTimePurchase[]", - "description": "The one-time purchases the shop has." + "filePath": "src/server/types.ts", + "name": "ShopifyAdmin", + "value": "shopify_admin" } - ], - "value": "export interface BillingCheckResponseObject {\n /**\n * Whether the user has an active payment method.\n */\n hasActivePayment: boolean;\n /**\n * The one-time purchases the shop has.\n */\n oneTimePurchases: OneTimePurchase[];\n /**\n * The active subscriptions the shop has.\n */\n appSubscriptions: AppSubscription[];\n}" + ] }, - "OneTimePurchase": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "OneTimePurchase", + "AdminApp": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminApp", + "value": "ShopifyAppBase", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the one-time purchase." + "name": "addDocumentResponseHeaders", + "value": "AddDocumentResponseHeaders", + "description": "Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n\n\n\n\n", + "examples": [ + { + "title": "Return headers on all requests", + "description": "Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const addDocumentResponseheaders = shopify.addDocumentResponseheaders;", + "title": "~/shopify.server.ts" + }, + { + "code": "import { addDocumentResponseHeaders } from \"~/shopify.server\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const markup = renderToString(\n \n );\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n addDocumentResponseHeaders(request, responseHeaders);\n\n return new Response(\"\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}", + "title": "entry.server.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "authenticate", + "value": "Authenticate", + "description": "Ways to authenticate requests from different surfaces across Shopify.", + "examples": [ + { + "title": "Authenticate Shopify requests", + "description": "Use the functions in `authenticate` to validate requests coming from Shopify.", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n const response = admin.graphql(`{ shop { name } }`)\n\n return json(await response.json());\n}", + "title": "/app/routes/**\\/*.jsx" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "name", - "value": "string", - "description": "The name of the purchased plan." + "name": "registerWebhooks", + "value": "RegisterWebhooks", + "description": "Register shop-specific webhook subscriptions using the Admin GraphQL API.\n\nIn many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n\n\n\n\n\n\n\nYou should only use this if you need shop-specific webhooks.", + "examples": [ + { + "title": "Registering shop-specific webhooks after install", + "description": "Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});", + "title": "app/shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "status", - "value": "string", - "description": "The status of the one-time purchase." + "name": "sessionStorage", + "value": "SessionStorageType", + "description": "The `SessionStorage` instance you passed in as a config option.", + "isOptional": true, + "examples": [ + { + "title": "Storing sessions with Prisma", + "description": "Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n sessionStorage: new PrismaSessionStorage(prisma),\n // ...etc\n})\n\n// shopify.sessionStorage is an instance of PrismaSessionStorage", + "title": "/app/shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "test", - "value": "boolean", - "description": "Whether this is a test purchase." + "name": "unauthenticated", + "value": "Unauthenticated>", + "description": "Ways to get Contexts from requests that do not originate from Shopify.", + "examples": [ + { + "title": "Using unauthenticated contexts", + "description": "Create contexts for requests that don't come from Shopify.", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n const response = admin.graphql(`{ shop { currencyCode } }`)\n\n return json(await response.json());\n}", + "title": "/app/routes/**\\/*.jsx" + } + ] + } + ] } - ], - "value": "export interface OneTimePurchase {\n /**\n * The ID of the one-time purchase.\n */\n id: string;\n /**\n * The name of the purchased plan.\n */\n name: string;\n /**\n * Whether this is a test purchase.\n */\n test: boolean;\n /**\n * The status of the one-time purchase.\n */\n status: string;\n}" + ] }, - "CreateUsageRecordOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "CreateUsageRecordOptions", + "AddDocumentResponseHeaders": { + "filePath": "src/server/types.ts", + "name": "AddDocumentResponseHeaders", "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "description", - "value": "string", - "description": "The description of the app usage record." - }, + "params": [ { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "idempotencyKey", - "value": "string", + "name": "request", "description": "", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged." - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "price", - "value": "{ amount: number; currencyCode: string; }", - "description": "The price of the app usage record." + "value": "Request", + "filePath": "src/server/types.ts" }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "subscriptionLineItemId", - "value": "string", + "name": "headers", "description": "", - "isOptional": true + "value": "Headers", + "filePath": "src/server/types.ts" } ], - "value": "export interface CreateUsageRecordOptions {\n /**\n * The description of the app usage record.\n */\n description: string;\n /**\n * The price of the app usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest: boolean;\n /*\n * Defines the usage pricing plan the merchant is subscribed to.\n */\n subscriptionLineItemId?: string;\n /*\n * A unique key generated to avoid duplicate charges.\n */\n idempotencyKey?: string;\n}" + "returns": { + "filePath": "src/server/types.ts", + "description": "", + "name": "void", + "value": "void" + }, + "value": "type AddDocumentResponseHeaders = (request: Request, headers: Headers) => void;" }, - "UsageRecord": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "UsageRecord", + "Authenticate": { + "filePath": "src/server/types.ts", + "name": "Authenticate", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "description", - "value": "string", - "description": "The description of the usage record." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The ID of the usage record." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "idempotencyKey", - "value": "string", - "description": "The idempotency key for this request.", - "isOptional": true + "name": "admin", + "value": "AuthenticateAdmin>", + "description": "Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.\n\nExamples of when to use this are requests from your app's UI, or requests from admin extensions.\n\nIf there is no session for the Request, this will redirect the merchant to correct auth flows.", + "examples": [ + { + "title": "Authenticating a request for an embedded app", + "description": "", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = authenticate.admin(request);\n const response = await admin.graphql(`{ shop { name } }`)\n\n return json(await response.json());\n}", + "title": "/app/routes/**\\/*.jsx" + }, + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "plan", - "value": "ActiveSubscriptionLineItem", - "description": "The subscription line item associated with the usage record." + "name": "flow", + "value": "AuthenticateFlow>", + "description": "Authenticate a Flow extension Request and get back an authenticated context, containing an admin context to access the API, and the payload of the request.\n\nIf there is no session for the Request, this will return an HTTP 400 error.\n\nNote that this will always be a POST request.", + "examples": [ + { + "title": "Authenticating a Flow extension request", + "description": "", + "tabs": [ + { + "code": "import { ActionFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const {admin, session, payload} = authenticate.flow(request);\n\n // Perform flow extension logic\n\n // Return a 200 response\n return null;\n}", + "title": "/app/routes/**\\/*.jsx" + }, + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "price", - "value": "{ amount: number; currencyCode: string; }", - "description": "The price and currency of the usage record." - } - ], - "value": "export interface UsageRecord {\n /**\n * The ID of the usage record.\n */\n id: string;\n /**\n * The description of the usage record.\n */\n description: string;\n /**\n * The price and currency of the usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * The subscription line item associated with the usage record.\n */\n plan: ActiveSubscriptionLineItem;\n /**\n * The idempotency key for this request.\n */\n idempotencyKey?: string;\n}" - }, - "RequestBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "RequestBillingOptions", - "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", - "isOptional": true + "name": "fulfillmentService", + "value": "AuthenticateFulfillmentService<\n Config,\n RestResourcesType\n >", + "description": "Authenticate a request from a fulfillment service and get back an authenticated context.", + "examples": [ + { + "title": "Shopify session for the fulfillment service request", + "description": "Use the session associated with this request to use the Admin GraphQL API", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin, session } = await authenticate.fulfillmentService(request);\n\n console.log(session.id)\n\n return new Response();\n}", + "title": "/app/routes/fulfillment_order_notification.ts" + } + ] + } + ] }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "plan", - "value": "keyof Config[\"billing\"]", - "description": "The plan to request. Must be one of the values defined in the `billing` config option." + "name": "public", + "value": "AuthenticatePublic", + "description": "Authenticate a public request and get back a session token.", + "examples": [ + { + "title": "Authenticating a request from a checkout extension", + "description": "", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\nimport { getWidgets } from \"~/db/widgets\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {sessionToken} = authenticate.public.checkout(request);\n\n return json(await getWidgets(sessionToken));\n}", + "title": "/app/routes/api/checkout.jsx" + } + ] + } + ] }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "returnUrl", - "value": "string", - "description": "The URL to return to after the merchant approves the payment.", - "isOptional": true + "name": "webhook", + "value": "AuthenticateWebhook, string>", + "description": "Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request", + "examples": [ + { + "title": "Authenticating a webhook request", + "description": "", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic, shop, session, payload } = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n // Handle the webhook\n console.log(`${TOPIC} webhook received with payload:`, JSON.stringify(payload))\n\n throw new Response();\n};", + "title": "app/routes/webhooks.ts" + } + ] + }, + { + "title": "Registering app-specific webhooks (Recommended)", + "description": "", + "tabs": [ + { + "code": "# shopify.app.toml\n[webhooks]\napi_version = \"2024-07\"\n\n [[webhooks.subscriptions]]\n topics = [\"products/create\"]\n uri = \"/webhooks/products/create\"", + "title": "Example" + } + ] + }, + { + "title": "Registering shop-specific webhooks", + "description": "In many cases you won't need this. Please see: [https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions)", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});", + "title": "app/shopify.server.ts" + } + ] + } + ] } ], - "value": "export interface RequestBillingOptions\n extends Omit<\n BillingRequestParams>,\n 'session' | 'plan' | 'returnObject'\n > {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" + "value": "interface Authenticate {\n /**\n * Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.\n *\n * Examples of when to use this are requests from your app's UI, or requests from admin extensions.\n *\n * If there is no session for the Request, this will redirect the merchant to correct auth flows.\n *\n * @example\n * Authenticating a request for an embedded app.\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {admin, session, sessionToken, billing} = authenticate.admin(request);\n * const response = await admin.graphql(`{ shop { name } }`)\n *\n * return json(await response.json());\n * }\n * ```\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n admin: AuthenticateAdmin>;\n\n /**\n * Authenticate a Flow extension Request and get back an authenticated context, containing an admin context to access\n * the API, and the payload of the request.\n *\n * If there is no session for the Request, this will return an HTTP 400 error.\n *\n * Note that this will always be a POST request.\n *\n * @example\n * Authenticating a Flow extension request.\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { ActionFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const {admin, session, payload} = authenticate.flow(request);\n *\n * // Perform flow extension logic\n *\n * // Return a 200 response\n * return null;\n * }\n * ```\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n flow: AuthenticateFlow>;\n\n /**\n * Authenticate a request from a fulfillment service and get back an authenticated context.\n *\n * @example\n * Shopify session for the fulfillment service request.\n * Use the session associated with this request to use the Admin GraphQL API \n * ```ts\n * // /app/routes/fulfillment_order_notification.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin, session } = await authenticate.fulfillmentService(request);\n *\n * console.log(session.id)\n *\n * return new Response();\n * }\n * ```\n * */\n fulfillmentService: AuthenticateFulfillmentService<\n Config,\n RestResourcesType\n >;\n\n /**\n * Authenticate a public request and get back a session token.\n *\n * @example\n * Authenticating a request from a checkout extension\n *\n * ```ts\n * // /app/routes/api/checkout.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n * import { getWidgets } from \"~/db/widgets\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {sessionToken} = authenticate.public.checkout(request);\n *\n * return json(await getWidgets(sessionToken));\n * }\n * ```\n */\n public: AuthenticatePublic;\n\n /**\n * Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request\n * \n * @example\n * Authenticating a webhook request\n *\n * ```ts\n * // app/routes/webhooks.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import db from \"../db.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { topic, shop, session, payload } = await authenticate.webhook(request);\n * \n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n * \n * // Handle the webhook\n * console.log(`${TOPIC} webhook received with payload:`, JSON.stringify(payload))\n *\n * throw new Response();\n * };\n * ```\n * \n * @example\n * Registering app-specific webhooks (Recommended)\n * ```toml\n * # shopify.app.toml\n * [webhooks]\n * api_version = \"2024-07\"\n\n * [[webhooks.subscriptions]]\n * topics = [\"products/create\"]\n * uri = \"/webhooks/products/create\"\n * \n * ```\n * \n * @example\n * Registering shop-specific webhooks.\n * In many cases you won't need this. Please see: [https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions)\n * \n * ```ts\n * // app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * PRODUCTS_CREATE: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks/products/create\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * // Register webhooks for the shop\n * // In this example, every shop will have these webhooks\n * // You could wrap this in some custom shop specific conditional logic if needed\n * shopify.registerWebhooks({ session });\n * },\n * },\n * // ...etc\n * });\n * ```\n */\n webhook: AuthenticateWebhook, string>;\n}" }, - "RequireBillingOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "RequireBillingOptions", + "AuthenticateAdmin": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "AuthenticateAdmin", "description": "", - "members": [ - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", - "isOptional": true - }, - { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "onFailure", - "value": "(error: any) => Promise", - "description": "How to handle the request if the shop doesn't have an active payment for any plan." - }, + "params": [ { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plans", - "value": "(keyof Config[\"billing\"])[]", - "description": "The plans to check for. Must be one of the values defined in the `billing` config option." + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/admin/types.ts" } ], - "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" + "returns": { + "filePath": "src/server/authenticate/admin/types.ts", + "description": "", + "name": "Promise>", + "value": "Promise>" + }, + "value": "export type AuthenticateAdmin<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" }, - "UpdateUsageCappedAmountOptions": { - "filePath": "src/server/authenticate/admin/billing/types.ts", - "name": "UpdateUsageCappedAmountOptions", + "AdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminContext", + "value": "FeatureEnabled extends true\n ? EmbeddedTypedAdminContext & ScopesContext\n : EmbeddedTypedAdminContext", + "description": "" + }, + "FeatureEnabled": { + "filePath": "src/server/future/flags.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "FeatureEnabled", + "value": "Future extends FutureFlags\n ? Future[Flag] extends true\n ? true\n : false\n : false", + "description": "" + }, + "FutureFlags": { + "filePath": "src/server/future/flags.ts", + "name": "FutureFlags", "description": "", "members": [ { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", - "name": "cappedAmount", - "value": "{ amount: number; currencyCode: string; }", - "description": "The maximum charge for the usage billing plan." + "name": "removeRest", + "value": "boolean", + "description": "When enabled, methods for interacting with the admin REST API will not be returned.\n\nThis affects:\n\n* `authenticate.admin(request)` * `authenticate.webhook(request)` * `authenticate.flow(request)` * `authenticate.appProxy(request)` * `authenticate.fulfillmentService(request)` * `unauthenticated.admin(shop)`\n\nIn a future release we will remove REST from the package completely.\n\nPlease see: [https://www.shopify.com/ca/partners/blog/all-in-on-graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)", + "isOptional": true, + "defaultValue": "false" }, { - "filePath": "src/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", - "name": "subscriptionLineItemId", - "value": "string", - "description": "The subscription line item ID to update." - } - ], - "value": "export interface UpdateUsageCappedAmountOptions {\n /**\n * The subscription line item ID to update.\n */\n subscriptionLineItemId: string;\n /**\n * The maximum charge for the usage billing plan.\n */\n cappedAmount: {\n /**\n * The amount to update.\n */\n amount: number;\n /**\n * The currency code to update.\n */\n currencyCode: string;\n };\n}" - }, - "EnsureCORSFunction": { - "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", - "name": "EnsureCORSFunction", - "description": "", - "members": [], - "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" - }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", + "name": "unstable_newEmbeddedAuthStrategy", "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", - "value": "string", - "description": "The access token for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." + "description": "When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange). This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n\nLearn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).", + "isOptional": true, + "defaultValue": "false" }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." + "filePath": "src/server/future/flags.ts", + "syntaxKind": "PropertySignature", + "name": "wip_optionalScopesApi", + "value": "boolean", + "description": "When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.", + "isOptional": true, + "defaultValue": "false" } ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" + "value": "export interface FutureFlags {\n /**\n * When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).\n * This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n *\n * Learn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).\n *\n * @default false\n */\n unstable_newEmbeddedAuthStrategy?: boolean;\n\n /**\n * When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.\n *\n * @default false\n */\n wip_optionalScopesApi?: boolean;\n\n /**\n * When enabled, methods for interacting with the admin REST API will not be returned.\n *\n * This affects:\n *\n * * `authenticate.admin(request)`\n * * `authenticate.webhook(request)`\n * * `authenticate.flow(request)`\n * * `authenticate.appProxy(request)`\n * * `authenticate.fulfillmentService(request)`\n * * `unauthenticated.admin(shop)`\n *\n * In a future release we will remove REST from the package completely.\n *\n * Please see: [https://www.shopify.com/ca/partners/blog/all-in-on-graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)\n *\n * @default false\n */\n removeRest?: boolean;\n}" }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", + "EmbeddedTypedAdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "EmbeddedTypedAdminContext", + "value": "Config['isEmbeddedApp'] extends false\n ? NonEmbeddedAdminContext\n : EmbeddedAdminContext", + "description": "" + }, + "NonEmbeddedAdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "NonEmbeddedAdminContext", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "associated_user_scope", - "value": "string", - "description": "The effective set of scopes for the session." + "name": "billing", + "value": "BillingContext", + "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "expires_in", - "value": "number", - "description": "How long the access token is valid for, in seconds." + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", + "examples": [ + { + "title": "Setting CORS headers for a admin request", + "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "examples": [ + { + "title": "Using offline sessions", + "description": "Get your app's shop-specific data using an offline session.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + }, + { + "title": "Using online sessions", + "description": "Get your app's user-specific data using an online session.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] } ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" + "value": "export interface NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", + "BillingContext": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "BillingContext", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." + "name": "cancel", + "value": "(options: CancelBillingOptions) => Promise", + "description": "Cancels an ongoing subscription, given its ID.", + "examples": [ + { + "title": "Cancelling a subscription", + "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", + "title": "/app/routes/cancel-subscription.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." + "name": "check", + "value": ">(options?: Options) => Promise", + "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "examples": [ + { + "title": "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.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check({\n plans: [MONTHLY_PLAN],\n isTest: false,\n });\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + }, + { + "title": "Check for payments without filtering", + "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.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const { hasActivePayment, appSubscriptions } = await billing.check();\n // This will be true if any payment is found\n console.log(hasActivePayment);\n console.log(appSubscriptions);\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "email", - "value": "string", - "description": "The user's email address." + "name": "createUsageRecord", + "value": "(options: CreateUsageRecordOptions) => Promise", + "description": "Creates a usage record for an app subscription.", + "examples": [ + { + "title": "Creating a usage record", + "description": "Create a usage record for the active usage billing plan", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n const chargeBilling = await billing.createUsageRecord({\n description: \"Usage record for product creation\",\n price: {\n amount: 1,\n currencyCode: \"USD\",\n },\n isTest: true,\n });\n console.log(chargeBilling);\n\n // App logic\n};", + "title": "/app/routes/create-usage-record.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "email_verified", - "value": "boolean", - "description": "Whether the user has verified their email address." + "name": "request", + "value": "(options: RequestBillingOptions) => Promise", + "description": "Requests payment for the plan.", + "examples": [ + { + "title": "Using a custom return URL", + "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + }, + { + "title": "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.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n trialDays: 14,\n lineItems: [\n {\n interval: BillingInterval.Every30Days,\n discount: { value: { percentage: 0.1 } },\n },\n ],\n }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." + "name": "require", + "value": "(options: RequireBillingOptions) => Promise", + "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "examples": [ + { + "title": "Requesting billing right away", + "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + }, + { + "title": "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.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n }\n ],\n },\n [ANNUAL_PLAN]: {\n lineItems: [\n {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "id", - "value": "number", - "description": "The user's ID." + "name": "updateUsageCappedAmount", + "value": "(options: UpdateUsageCappedAmountOptions) => Promise", + "description": "Updates the capped amount for a usage billing plan.", + "examples": [ + { + "title": "Updating the capped amount for a usage billing plan", + "description": "Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n\n await billing.updateUsageCappedAmount({\n subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n cappedAmount: {\n amount: 10,\n currencyCode: \"USD\"\n },\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const USAGE_PLAN = 'Usage subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [USAGE_PLAN]: {\n lineItems: [\n {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Usage,\n terms: \"Usage based\"\n }\n ],\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + } + ], + "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Redirect to a plan selection page.\n * 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.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Check what billing plans a merchant is subscribed to.\n * Use billing.check if you want to determine which plans are in use. Unlike `require`, `check` does not\n * throw an error if no active billing plans are present. \n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const { hasActivePayment, appSubscriptions } = await billing.check({\n * plans: [MONTHLY_PLAN],\n * isTest: false,\n * });\n * console.log(hasActivePayment);\n * console.log(appSubscriptions);\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Check for payments without filtering.\n * Use billing.check to see if any payments exist for the store, regardless of whether it's a test or\n * matches one or more plans.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const { hasActivePayment, appSubscriptions } = await billing.check();\n * // This will be true if any payment is found\n * console.log(hasActivePayment);\n * console.log(appSubscriptions);\n * };\n * ```\n */\n check: >(\n options?: Options,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Overriding plan settings.\n * 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.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * trialDays: 14,\n * lineItems: [\n * {\n * interval: BillingInterval.Every30Days,\n * discount: { value: { percentage: 0.1 } },\n * },\n * ],\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * }\n * ],\n * },\n * [ANNUAL_PLAN]: {\n * lineItems: [\n * {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n\n /**\n * Creates a usage record for an app subscription.\n *\n * @returns Returns a usage record when one was created successfully.\n *\n * @example\n * Creating a usage record\n * Create a usage record for the active usage billing plan\n * ```ts\n * // /app/routes/create-usage-record.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n *\n * const chargeBilling = await billing.createUsageRecord({\n * description: \"Usage record for product creation\",\n * price: {\n * amount: 1,\n * currencyCode: \"USD\",\n * },\n * isTest: true,\n * });\n * console.log(chargeBilling);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const USAGE_PLAN = 'Usage subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [USAGE_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Usage,\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n createUsageRecord: (\n options: CreateUsageRecordOptions,\n ) => Promise;\n\n /**\n * Updates the capped amount for a usage billing plan.\n *\n * @returns Redirects to a confirmation page to update the usage billing plan.\n *\n * @example\n * Updating the capped amount for a usage billing plan.\n * Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n *\n * await billing.updateUsageCappedAmount({\n * subscriptionLineItemId: \"gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1\",\n * cappedAmount: {\n * amount: 10,\n * currencyCode: \"USD\"\n * },\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const USAGE_PLAN = 'Usage subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [USAGE_PLAN]: {\n * lineItems: [\n * {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Usage,\n * terms: \"Usage based\"\n * }\n * ],\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n updateUsageCappedAmount: (\n options: UpdateUsageCappedAmountOptions,\n ) => Promise;\n}" + }, + "CancelBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CancelBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "Whether to use the test mode. This prevents the credit card from being charged.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "last_name", - "value": "string", - "description": "The user's last name." + "name": "prorate", + "value": "boolean", + "description": "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.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "locale", + "name": "subscriptionId", "value": "string", - "description": "The user's locale." + "description": "The ID of the subscription to cancel." } ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" + "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to issue prorated credits for the unused portion of the app subscription. There will be a corresponding\n * deduction (based on revenue share) to your Partner account. For example, if a $10.00 app subscription\n * (with 0% revenue share) is cancelled and prorated half way through the billing cycle, then the merchant will be\n * credited $5.00 and that amount will be deducted from your Partner account.\n */\n prorate?: boolean;\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest?: boolean;\n}" }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", + "CheckBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CheckBillingOptions", + "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "plans", + "value": "(keyof Config[\"billing\"])[]", + "description": "The plans to check for. Must be one of the values defined in the `billing` config option.", + "isOptional": true } ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" + "value": "export interface CheckBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans?: (keyof Config['billing'])[];\n}" }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", + "CreateUsageRecordOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CreateUsageRecordOptions", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "description", + "value": "string", + "description": "The description of the app usage record." }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "accessToken", + "name": "idempotencyKey", "value": "string", - "description": "The access token for the session.", + "description": "", "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true + "name": "isTest", + "value": "boolean", + "description": "Whether to use the test mode. This prevents the credit card from being charged." }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." + "name": "price", + "value": "{ amount: number; currencyCode: string; }", + "description": "The price of the app usage record." }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "isOnline", + "name": "subscriptionLineItemId", + "value": "string", + "description": "", + "isOptional": true + } + ], + "value": "export interface CreateUsageRecordOptions {\n /**\n * The description of the app usage record.\n */\n description: string;\n /**\n * The price of the app usage record.\n */\n price: {\n /**\n * The amount to charge for this usage record.\n */\n amount: number;\n /**\n * The currency code for this usage record.\n */\n currencyCode: string;\n };\n /**\n * Whether to use the test mode. This prevents the credit card from being charged.\n */\n isTest: boolean;\n /*\n * Defines the usage pricing plan the merchant is subscribed to.\n */\n subscriptionLineItemId?: string;\n /*\n * A unique key generated to avoid duplicate charges.\n */\n idempotencyKey?: string;\n}" + }, + "RequestBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequestBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", "value": "boolean", - "description": "Whether the access token in the session is online or offline." + "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true + "name": "plan", + "value": "keyof Config[\"billing\"]", + "description": "The plan to request. Must be one of the values defined in the `billing` config option." }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "scope", + "name": "returnUrl", "value": "string", - "description": "The scopes for the access token.", + "description": "The URL to return to after the merchant approves the payment.", + "isOptional": true + } + ], + "value": "export interface RequestBillingOptions\n extends Omit<\n BillingRequestParams>,\n 'session' | 'plan' | 'returnObject'\n > {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" + }, + "RequireBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequireBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.", "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." + "name": "onFailure", + "value": "(error: any) => Promise", + "description": "How to handle the request if the shop doesn't have an active payment for any plan." }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", - "name": "state", + "name": "plans", + "value": "(keyof Config[\"billing\"])[]", + "description": "The plans to check for. Must be one of the values defined in the `billing` config option." + } + ], + "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" + }, + "UpdateUsageCappedAmountOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "UpdateUsageCappedAmountOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "cappedAmount", + "value": "{ amount: number; currencyCode: string; }", + "description": "The maximum charge for the usage billing plan." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptionLineItemId", "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." + "description": "The subscription line item ID to update." } ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" + "value": "export interface UpdateUsageCappedAmountOptions {\n /**\n * The subscription line item ID to update.\n */\n subscriptionLineItemId: string;\n /**\n * The maximum charge for the usage billing plan.\n */\n cappedAmount: {\n /**\n * The amount to update.\n */\n amount: number;\n /**\n * The currency code to update.\n */\n currencyCode: string;\n };\n}" }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" + "EnsureCORSFunction": { + "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", + "name": "EnsureCORSFunction", + "description": "", + "members": [], + "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" }, "EmbeddedAdminContext": { "filePath": "src/server/authenticate/admin/types.ts", @@ -9446,7 +5455,7 @@ "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "admin", - "value": "AdminApiContext", + "value": "AdminApiContext", "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." }, { @@ -9608,77 +5617,6 @@ "value": "'_self' | '_parent' | '_top' | '_blank'", "description": "" }, - "JwtPayload": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "JwtPayload", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "aud", - "value": "string", - "description": "The client ID of the receiving app." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "dest", - "value": "string", - "description": "The shop's domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "exp", - "value": "number", - "description": "When the session token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "iat", - "value": "number", - "description": "When the session token was issued." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "iss", - "value": "string", - "description": "The shop's admin domain." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "jti", - "value": "string", - "description": "A secure random UUID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "nbf", - "value": "number", - "description": "When the session token activates." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "sid", - "value": "string", - "description": "A unique session ID per user and app." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "sub", - "value": "string", - "description": "The User that the session token is intended for." - } - ], - "value": "export interface JwtPayload {\n /**\n * The shop's admin domain.\n */\n iss: string;\n /**\n * The shop's domain.\n */\n dest: string;\n /**\n * The client ID of the receiving app.\n */\n aud: string;\n /**\n * The User that the session token is intended for.\n */\n sub: string;\n /**\n * When the session token expires.\n */\n exp: number;\n /**\n * When the session token activates.\n */\n nbf: number;\n /**\n * When the session token was issued.\n */\n iat: number;\n /**\n * A secure random UUID.\n */\n jti: string;\n /**\n * A unique session ID per user and app.\n */\n sid: string;\n}" - }, "ScopesContext": { "filePath": "src/server/authenticate/admin/types.ts", "name": "ScopesContext", @@ -9810,14 +5748,6 @@ "value": "Config['restResources'] extends ShopifyRestResources\n ? Config['restResources']\n : ShopifyRestResources", "description": "" }, - "ShopifyRestResources": { - "filePath": "../shopify-api/dist/ts/rest/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "ShopifyRestResources", - "value": "Record", - "description": "", - "members": [] - }, "AuthenticateFlow": { "filePath": "src/server/authenticate/flow/types.ts", "name": "AuthenticateFlow", @@ -9833,10 +5763,10 @@ "returns": { "filePath": "src/server/authenticate/flow/types.ts", "description": "", - "name": "Promise>", - "value": "Promise>" + "name": "Promise>", + "value": "Promise>" }, - "value": "export type AuthenticateFlow<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" + "value": "export type AuthenticateFlow<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" }, "FlowContext": { "filePath": "src/server/authenticate/flow/types.ts", @@ -9847,7 +5777,7 @@ "filePath": "src/server/authenticate/flow/types.ts", "syntaxKind": "PropertySignature", "name": "admin", - "value": "AdminApiContext", + "value": "AdminApiContext", "description": "An admin context for the Flow request.\n\nReturned only if there is a session for the shop.", "examples": [ { @@ -9890,10 +5820,10 @@ "examples": [ { "title": "Shopify session for the Flow request", - "description": "Use the session associated with this request to use REST resources.", + "description": "Use the session associated with this request.", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.flow(request);\n\n const products = await admin?.rest.resources.Product.all({ session });\n // Use products\n\n return new Response();\n};", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.flow(request);\n\n console.log(session.id)\n\n return new Response();\n};", "title": "/app/routes/flow.tsx" } ] @@ -9901,7 +5831,7 @@ ] } ], - "value": "export interface FlowContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Shopify session for the Flow request.\n * Use the session associated with this request to use REST resources.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session, admin } = await authenticate.flow(request);\n *\n * const products = await admin?.rest.resources.Product.all({ session });\n * // Use products\n *\n * return new Response();\n * };\n * ```\n */\n session: Session;\n\n /**\n * The payload from the Flow request.\n *\n * @example\n * Flow payload.\n * Get the request's POST payload.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { payload } = await authenticate.flow(request);\n * return new Response();\n * };\n * ```\n */\n payload: any;\n\n /**\n * An admin context for the Flow request.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Flow admin context.\n * Use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.flow(request);\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n}" + "value": "export interface FlowContext<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Shopify session for the Flow request.\n * Use the session associated with this request.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session, admin } = await authenticate.flow(request);\n *\n * console.log(session.id)\n *\n * return new Response();\n * };\n * ```\n */\n session: Session;\n\n /**\n * The payload from the Flow request.\n *\n * @example\n * Flow payload.\n * Get the request's POST payload.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { payload } = await authenticate.flow(request);\n * return new Response();\n * };\n * ```\n */\n payload: any;\n\n /**\n * An admin context for the Flow request.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Flow admin context.\n * Use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/flow.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.flow(request);\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n}" }, "AuthenticateFulfillmentService": { "filePath": "src/server/authenticate/fulfillment-service/types.ts", @@ -9918,10 +5848,10 @@ "returns": { "filePath": "src/server/authenticate/fulfillment-service/types.ts", "description": "", - "name": "Promise>", - "value": "Promise>" + "name": "Promise>", + "value": "Promise>" }, - "value": "export type AuthenticateFulfillmentService<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" + "value": "export type AuthenticateFulfillmentService<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (\n request: Request,\n) => Promise>;" }, "FulfillmentServiceContext": { "filePath": "src/server/authenticate/fulfillment-service/types.ts", @@ -9932,7 +5862,7 @@ "filePath": "src/server/authenticate/fulfillment-service/types.ts", "syntaxKind": "PropertySignature", "name": "admin", - "value": "AdminApiContext", + "value": "AdminApiContext", "description": "\nAn admin context for the fulfillment service request.\n\nReturned only if there is a session for the shop.", "examples": [ { @@ -9940,7 +5870,7 @@ "description": "Use the session associated with this request to use the Admin GraphQL API", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.fulfillmentService(request);\n const response = await admin?.graphql(\n `#graphql\n query {\n shop {\n assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {\n edges {\n node {\n id\n destination {\n firstName\n lastName\n }\n lineItems(first: 10) {\n edges {\n node {\n id\n productTitle\n sku\n remainingQuantity\n }\n }\n }\n merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {\n edges {\n node {\n message\n }\n }\n }\n }\n }\n }\n }\n}`);\n\n const fulfillments = await response.json();\n return json({ data: fulfillments.data });\n}", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin, session } = await authenticate.fulfillmentService(request);\n\n console.log(session.id)\n\n return new Response();\n}", "title": "/app/routes/fulfillment_order_notification.ts" } ] @@ -9975,10 +5905,10 @@ "examples": [ { "title": "Shopify session for the fulfillment service notification request", - "description": "Use the session associated with this request to use REST resources.", + "description": "Use the session associated with this request.", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\n export const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.fulfillmentService(request);\n\n const products = await admin?.rest.resources.Product.all({ session });\n // Use products\n\n return new Response();\n};", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { session, admin } = await authenticate.fulfillmentService(request);\n\n console.log(session.id)\n\n return new Response();\n};", "title": "/app/routes/fulfillment_service_notification.tsx" } ] @@ -9986,7 +5916,7 @@ ] } ], - "value": "export interface FulfillmentServiceContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n * @example\n * Shopify session for the fulfillment service notification request.\n * Use the session associated with this request to use REST resources.\n * ```ts\n * // /app/routes/fulfillment_service_notification.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session, admin } = await authenticate.fulfillmentService(request);\n *\n * const products = await admin?.rest.resources.Product.all({ session });\n * // Use products\n *\n * return new Response();\n * };\n * ```\n * */\n session: Session;\n /**\n *\n * An admin context for the fulfillment service request.\n *\n * Returned only if there is a session for the shop.\n * @example\n * Shopify session for the fulfillment service request.\n * Use the session associated with this request to use the Admin GraphQL API \n * ```ts\n * // /app/routes/fulfillment_order_notification.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.fulfillmentService(request);\n * const response = await admin?.graphql(\n * `#graphql\n * query {\n * shop {\n * assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {\n * edges {\n * node {\n * id\n * destination {\n * firstName\n * lastName\n * }\n * lineItems(first: 10) {\n * edges {\n * node {\n * id\n * productTitle\n * sku\n * remainingQuantity\n * }\n * }\n * }\n * merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {\n * edges {\n * node {\n * message\n * }\n * }\n * }\n * }\n * }\n * }\n * }\n * }`);\n *\n * const fulfillments = await response.json();\n * return json({ data: fulfillments.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * The payload from the fulfillment service request.\n *\n * @example\n * Fulfillment service request payload.\n * Get the request's POST payload.\n * ```ts\n * /app/routes/fulfillment_order_notification.ts\n * import { ActionFunction } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action: ActionFunction = async ({ request }) => {\n * const { payload } = await authenticate.fulfillmentService(request);\n * if(payload.kind === 'FULFILLMENT_REQUEST') {\n * // handle fulfillment request\n * } else if (payload.kind === 'CANCELLATION_REQUEST') {\n * // handle cancellation request\n * };\n * return new Response();\n * ```\n */\n payload: FulfillmentServicePayload;\n}" + "value": "export interface FulfillmentServiceContext<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n * @example\n * Shopify session for the fulfillment service notification request.\n * Use the session associated with this request.\n * ```ts\n * // /app/routes/fulfillment_service_notification.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session, admin } = await authenticate.fulfillmentService(request);\n *\n * console.log(session.id)\n *\n * return new Response();\n * };\n * ```\n * */\n session: Session;\n /**\n *\n * An admin context for the fulfillment service request.\n *\n * Returned only if there is a session for the shop.\n * @example\n * Shopify session for the fulfillment service request.\n * Use the session associated with this request to use the Admin GraphQL API \n * ```ts\n * // /app/routes/fulfillment_order_notification.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin, session } = await authenticate.fulfillmentService(request);\n *\n * console.log(session.id)\n *\n * return new Response();\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * The payload from the fulfillment service request.\n *\n * @example\n * Fulfillment service request payload.\n * Get the request's POST payload.\n * ```ts\n * /app/routes/fulfillment_order_notification.ts\n * import { ActionFunction } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action: ActionFunction = async ({ request }) => {\n * const { payload } = await authenticate.fulfillmentService(request);\n * if(payload.kind === 'FULFILLMENT_REQUEST') {\n * // handle fulfillment request\n * } else if (payload.kind === 'CANCELLATION_REQUEST') {\n * // handle cancellation request\n * };\n * return new Response();\n * ```\n */\n payload: FulfillmentServicePayload;\n}" }, "FulfillmentServicePayload": { "filePath": "src/server/authenticate/fulfillment-service/types.ts", @@ -10004,7 +5934,7 @@ "filePath": "src/server/authenticate/public/types.ts", "syntaxKind": "PropertySignature", "name": "appProxy", - "value": "AuthenticateAppProxy", + "value": "AuthenticateAppProxy", "description": "Authenticate a request from an app proxy", "examples": [ { @@ -10058,7 +5988,7 @@ ] } ], - "value": "export interface AuthenticatePublic {\n /**\n * Authenticate a request from a checkout extension\n *\n * @example\n * Authenticating a checkout extension request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * );\n * return cors(json({my: \"data\", shop: sessionToken.dest}));\n * };\n * ```\n */\n checkout: AuthenticateCheckout;\n\n /**\n * Authenticate a request from an app proxy\n *\n * @example\n * Authenticating an app proxy request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * await authenticate.public.appProxy(\n * request,\n * );\n *\n * const {searchParams} = new URL(request.url);\n * const shop = searchParams.get(\"shop\");\n * const customerId = searchParams.get(\"logged_in_customer_id\")\n *\n * return json({my: \"data\", shop, customerId});\n * };\n * ```\n */\n appProxy: AuthenticateAppProxy;\n\n /**\n * Authenticate a request from a customer account extension\n *\n * @example\n * Authenticating a customer account extension request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.customerAccount(\n * request,\n * );\n * return cors(json({my: \"data\", shop: sessionToken.dest}));\n * };\n * ```\n */\n customerAccount: AuthenticateCustomerAccount;\n}" + "value": "export interface AuthenticatePublic {\n /**\n * Authenticate a request from a checkout extension\n *\n * @example\n * Authenticating a checkout extension request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * );\n * return cors(json({my: \"data\", shop: sessionToken.dest}));\n * };\n * ```\n */\n checkout: AuthenticateCheckout;\n\n /**\n * Authenticate a request from an app proxy\n *\n * @example\n * Authenticating an app proxy request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * await authenticate.public.appProxy(\n * request,\n * );\n *\n * const {searchParams} = new URL(request.url);\n * const shop = searchParams.get(\"shop\");\n * const customerId = searchParams.get(\"logged_in_customer_id\")\n *\n * return json({my: \"data\", shop, customerId});\n * };\n * ```\n */\n appProxy: AuthenticateAppProxy;\n\n /**\n * Authenticate a request from a customer account extension\n *\n * @example\n * Authenticating a customer account extension request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.customerAccount(\n * request,\n * );\n * return cors(json({my: \"data\", shop: sessionToken.dest}));\n * };\n * ```\n */\n customerAccount: AuthenticateCustomerAccount;\n}" }, "AuthenticateAppProxy": { "filePath": "src/server/authenticate/public/appProxy/types.ts", @@ -10075,10 +6005,10 @@ "returns": { "filePath": "src/server/authenticate/public/appProxy/types.ts", "description": "", - "name": "Promise", - "value": "Promise" + "name": "Promise<\n AppProxyContext | AppProxyContextWithSession\n>", + "value": "Promise<\n AppProxyContext | AppProxyContextWithSession\n>" }, - "value": "export type AuthenticateAppProxy = (\n request: Request,\n) => Promise;" + "value": "export type AuthenticateAppProxy<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (\n request: Request,\n) => Promise<\n AppProxyContext | AppProxyContextWithSession\n>;" }, "AppProxyContext": { "filePath": "src/server/authenticate/public/appProxy/types.ts", @@ -10200,12 +6130,12 @@ "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "admin", - "value": "AdminApiContext", + "value": "AdminApiContext", "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.", "examples": [ { "title": "Interacting with the Admin API", - "description": "Use the `admin` object to interact with the REST or GraphQL APIs.", + "description": "Use the `admin` object to interact with the admin GraphQL API.", "tabs": [ { "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: { title: \"Product Name\" }\n }\n }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", @@ -10293,7 +6223,7 @@ ] } ], - "value": "export interface AppProxyContextWithSession<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends Context {\n /**\n * The session for the shop that made the request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * Use this to get shop or user-specific data.\n *\n * @example\n * Using the session object.\n * Get the session for the shop that initiated the request to the app proxy.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppModelData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }) => {\n * // Get the session for the shop that initiated the request to the app proxy.\n * const { session } =\n * await authenticate.public.appProxy(request);\n *\n * // Use the session data to make to queries to your database or additional requests.\n * return json(\n * await getMyAppModelData({shop: session.shop})\n * );\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.\n *\n * @example\n * Interacting with the Admin API.\n * Use the `admin` object to interact with the REST or GraphQL APIs.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.public.appProxy(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * {\n * variables: {\n * input: { title: \"Product Name\" }\n * }\n * }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * Method for interacting with the Shopify Storefront Graphql API for the store that made the request.\n *\n * @example\n * Interacting with the Storefront API.\n * Use the `storefront` object to interact with the GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(\n * `#graphql\n * query blogIds {\n * blogs(first: 10) {\n * edges {\n * node {\n * id\n * }\n * }\n * }\n * }`\n * );\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: StorefrontContext;\n}" + "value": "export interface AppProxyContextWithSession<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends Context {\n /**\n * The session for the shop that made the request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * Use this to get shop or user-specific data.\n *\n * @example\n * Using the session object.\n * Get the session for the shop that initiated the request to the app proxy.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppModelData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }) => {\n * // Get the session for the shop that initiated the request to the app proxy.\n * const { session } =\n * await authenticate.public.appProxy(request);\n *\n * // Use the session data to make to queries to your database or additional requests.\n * return json(\n * await getMyAppModelData({shop: session.shop})\n * );\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.\n *\n * @example\n * Interacting with the Admin API.\n * Use the `admin` object to interact with the admin GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.public.appProxy(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * {\n * variables: {\n * input: { title: \"Product Name\" }\n * }\n * }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * Method for interacting with the Shopify Storefront Graphql API for the store that made the request.\n *\n * @example\n * Interacting with the Storefront API.\n * Use the `storefront` object to interact with the GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(\n * `#graphql\n * query blogIds {\n * blogs(first: 10) {\n * edges {\n * node {\n * id\n * }\n * }\n * }\n * }`\n * );\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: StorefrontContext;\n}" }, "StorefrontContext": { "filePath": "src/server/clients/storefront/types.ts", @@ -10376,90 +6306,32 @@ "description": "The version of the API to use for the request.", "isOptional": true }, - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "headers", - "value": "Record", - "description": "Additional headers to include in the request.", - "isOptional": true - }, - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "tries", - "value": "number", - "description": "The total number of times to try the request if it fails.", - "isOptional": true - }, - { - "filePath": "src/server/clients/types.ts", - "syntaxKind": "PropertySignature", - "name": "variables", - "value": "ApiClientRequestOptions[\"variables\"]", - "description": "The variables to pass to the operation.", - "isOptional": true - } - ], - "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n /**\n * The variables to pass to the operation.\n */\n variables?: ApiClientRequestOptions['variables'];\n /**\n * The version of the API to use for the request.\n */\n apiVersion?: ApiVersion;\n /**\n * Additional headers to include in the request.\n */\n headers?: Record;\n /**\n * The total number of times to try the request if it fails.\n */\n tries?: number;\n}" - }, - "ApiVersion": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "ApiVersion", - "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n April24 = \"2024-04\",\n July24 = \"2024-07\",\n October24 = \"2024-10\",\n Unstable = \"unstable\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October22", - "value": "2022-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January23", - "value": "2023-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April23", - "value": "2023-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July23", - "value": "2023-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October23", - "value": "2023-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January24", - "value": "2024-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April24", - "value": "2024-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July24", - "value": "2024-07" + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "Record", + "description": "Additional headers to include in the request.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October24", - "value": "2024-10" + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "The total number of times to try the request if it fails.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Unstable", - "value": "unstable" + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "The variables to pass to the operation.", + "isOptional": true } - ] + ], + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n /**\n * The variables to pass to the operation.\n */\n variables?: ApiClientRequestOptions['variables'];\n /**\n * The version of the API to use for the request.\n */\n apiVersion?: ApiVersion;\n /**\n * Additional headers to include in the request.\n */\n headers?: Record;\n /**\n * The total number of times to try the request if it fails.\n */\n tries?: number;\n}" }, "AuthenticateCheckout": { "filePath": "src/server/authenticate/public/checkout/types.ts", @@ -10654,16 +6526,16 @@ "returns": { "filePath": "src/server/authenticate/webhooks/types.ts", "description": "", - "name": "Promise>", - "value": "Promise>" + "name": "Promise>", + "value": "Promise>" }, - "value": "export type AuthenticateWebhook<\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> = (request: Request) => Promise>;" + "value": "export type AuthenticateWebhook<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> = (request: Request) => Promise>;" }, "WebhookContext": { "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "TypeAliasDeclaration", "name": "WebhookContext", - "value": "WebhookContextWithoutSession | WebhookContextWithSession", + "value": "WebhookContextWithoutSession | WebhookContextWithSession", "description": "" }, "WebhookContextWithoutSession": { @@ -10812,7 +6684,7 @@ "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", "name": "admin", - "value": "AdminApiContext", + "value": "AdminApiContext", "description": "An admin context for the webhook.\n\nReturned only if there is a session for the shop.", "examples": [ { @@ -10962,7 +6834,7 @@ ] } ], - "value": "export interface WebhookContextWithSession<\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> extends Context {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n * Webhook requests can trigger after an app is uninstalled\n * If the app is already uninstalled, the session may be undefined.\n * Therefore, you should check for the session before using it.\n *\n * @example\n * Protecting against uninstalled apps.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"~/shopify.server\";\n\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session } = await authenticate.webhook(request);\n * \n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n *\n * // Handle webhook request\n * console.log(\"Received webhook webhook\");\n *\n * return new Response();\n * };\n * ```\n */\n session: Session;\n\n /**\n * An admin context for the webhook.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Webhook admin context.\n * Use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n}" + "value": "export interface WebhookContextWithSession<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> extends Context {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n * Webhook requests can trigger after an app is uninstalled\n * If the app is already uninstalled, the session may be undefined.\n * Therefore, you should check for the session before using it.\n *\n * @example\n * Protecting against uninstalled apps.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import type { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"~/shopify.server\";\n\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { session } = await authenticate.webhook(request);\n * \n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n *\n * // Handle webhook request\n * console.log(\"Received webhook webhook\");\n *\n * return new Response();\n * };\n * ```\n */\n session: Session;\n\n /**\n * An admin context for the webhook.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * Webhook admin context.\n * Use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n}" }, "RegisterWebhooks": { "filePath": "src/server/types.ts", @@ -10999,14 +6871,6 @@ ], "value": "export interface RegisterWebhooksOptions {\n /**\n * The Shopify session used to register webhooks using the Admin API.\n */\n session: Session;\n}" }, - "RegisterReturn": { - "filePath": "../shopify-api/dist/ts/lib/webhooks/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RegisterReturn", - "value": "Record", - "description": "", - "members": [] - }, "SessionStorageType": { "filePath": "src/server/types.ts", "syntaxKind": "TypeAliasDeclaration", @@ -11023,7 +6887,7 @@ "filePath": "src/server/unauthenticated/types.ts", "syntaxKind": "PropertySignature", "name": "admin", - "value": "GetUnauthenticatedAdminContext", + "value": "GetUnauthenticatedAdminContext", "description": "Get an admin context by passing a shop\n\n**Warning** This should only be used for Requests that do not originate from Shopify. You must do your own authentication before using this method. This method throws an error if there is no session for the shop.", "examples": [ { @@ -11031,11 +6895,11 @@ "description": "", "tabs": [ { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", "title": "/app/shopify.server.ts" }, { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n const response = await admin.graphql(\"{ shop { name} }\")\n\n return json(await response.json());\n}", "title": "/app/routes/**\\/*.jsx" } ] @@ -11062,7 +6926,7 @@ ] } ], - "value": "export interface Unauthenticated {\n /**\n * Get an admin context by passing a shop\n *\n * **Warning** This should only be used for Requests that do not originate from Shopify.\n * You must do your own authentication before using this method.\n * This method throws an error if there is no session for the shop.\n *\n * @example\n * Responding to a request not controlled by Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {admin} = await shopify.unauthenticated.admin(shop);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n */\n admin: GetUnauthenticatedAdminContext;\n\n /**\n * Get a storefront context by passing a shop\n *\n * **Warning** This should only be used for Requests that do not originate from Shopify.\n * You must do your own authentication before using this method.\n * This method throws an error if there is no session for the shop.\n *\n * @example\n * Responding to a request not controlled by Shopify\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {storefront} = await shopify.unauthenticated.storefront(shop);\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: GetUnauthenticatedStorefrontContext;\n}" + "value": "export interface Unauthenticated<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n> {\n /**\n * Get an admin context by passing a shop\n *\n * **Warning** This should only be used for Requests that do not originate from Shopify.\n * You must do your own authentication before using this method.\n * This method throws an error if there is no session for the shop.\n *\n * @example\n * Responding to a request not controlled by Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {admin} = await shopify.unauthenticated.admin(shop);\n * const response = await admin.graphql(\"{ shop { name} }\")\n *\n * return json(await response.json());\n * }\n * ```\n */\n admin: GetUnauthenticatedAdminContext;\n\n /**\n * Get a storefront context by passing a shop\n *\n * **Warning** This should only be used for Requests that do not originate from Shopify.\n * You must do your own authentication before using this method.\n * This method throws an error if there is no session for the shop.\n *\n * @example\n * Responding to a request not controlled by Shopify\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {storefront} = await shopify.unauthenticated.storefront(shop);\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: GetUnauthenticatedStorefrontContext;\n}" }, "GetUnauthenticatedAdminContext": { "filePath": "src/server/unauthenticated/admin/types.ts", @@ -11079,10 +6943,10 @@ "returns": { "filePath": "src/server/unauthenticated/admin/types.ts", "description": "", - "name": "Promise>", - "value": "Promise>" + "name": "Promise>", + "value": "Promise>" }, - "value": "export type GetUnauthenticatedAdminContext<\n Resources extends ShopifyRestResources,\n> = (shop: string) => Promise>;" + "value": "export type GetUnauthenticatedAdminContext<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n> = (\n shop: string,\n) => Promise>;" }, "UnauthenticatedAdminContext": { "filePath": "src/server/unauthenticated/admin/types.ts", @@ -11093,23 +6957,9 @@ "filePath": "src/server/unauthenticated/admin/types.ts", "syntaxKind": "PropertySignature", "name": "admin", - "value": "AdminApiContext", + "value": "AdminApiContext", "description": "Methods for interacting with the GraphQL / REST Admin APIs for the given store.", "examples": [ - { - "title": "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", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await unauthenticated.admin(request);\n\n const response = await admin.rest.get(\n {\n path: \"/customers/count.json\"\n }\n );\n const customers = await response.json();\n\n return json({ customers });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\n\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", - "title": "/app/shopify.server.ts" - } - ] - }, { "title": "Querying the GraphQL API", "description": "Use `admin.graphql` to make query / mutation requests.", @@ -11119,7 +6969,7 @@ "title": "/app/routes/**\\/*.ts" }, { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", "title": "/app/shopify.server.ts" } ] @@ -11146,1624 +6996,2004 @@ ] } ], - "value": "export interface UnauthenticatedAdminContext<\n Resources extends ShopifyRestResources,\n> {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use to get shop-specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.admin(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the given store.\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await unauthenticated.admin(request);\n *\n * const response = await admin.rest.get(\n * {\n * path: \"/customers/count.json\"\n * }\n * );\n * const customers = await response.json();\n *\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n *\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await unauthenticated.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n */\n admin: AdminApiContext;\n}" + "value": "export interface UnauthenticatedAdminContext<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n> {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use to get shop-specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.admin(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the given store.\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await unauthenticated.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n */\n admin: AdminApiContext;\n}" + }, + "GetUnauthenticatedStorefrontContext": { + "filePath": "src/server/unauthenticated/storefront/types.ts", + "name": "GetUnauthenticatedStorefrontContext", + "description": "", + "params": [ + { + "name": "shop", + "description": "", + "value": "string", + "filePath": "src/server/unauthenticated/storefront/types.ts" + } + ], + "returns": { + "filePath": "src/server/unauthenticated/storefront/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type GetUnauthenticatedStorefrontContext = (\n shop: string,\n) => Promise;" + }, + "UnauthenticatedStorefrontContext": { + "filePath": "src/server/unauthenticated/storefront/types.ts", + "name": "UnauthenticatedStorefrontContext", + "description": "", + "members": [ + { + "filePath": "src/server/unauthenticated/storefront/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The session for the given shop.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nThis will always be an offline session. You can use this to get shop specific data.", + "examples": [ + { + "title": "Using the offline session", + "description": "Get your app's shop-specific data using the returned offline `session` object.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { session } = await unauthenticated.storefront(shop);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/unauthenticated/storefront/types.ts", + "syntaxKind": "PropertySignature", + "name": "storefront", + "value": "StorefrontContext", + "description": "Method for interacting with the Shopify GraphQL Storefront API for the given store.", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `storefront.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const shop = getShopFromExternalRequest(request);\n const { storefront } = await unauthenticated.storefront(shop);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "title": "app/routes/**\\/.ts" + } + ] + }, + { + "title": "Handling GraphQL errors", + "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { storefront } = await unauthenticated.storefront(shop);\n\n try {\n const response = await storefront.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // { errors: { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] } }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", + "title": "/app/shopify.server.ts" + } + ] + } + ] + } + ], + "value": "export interface UnauthenticatedStorefrontContext {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use this to get shop specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // app/routes/**\\/.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.storefront(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Method for interacting with the Shopify GraphQL Storefront API for the given store.\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const shop = getShopFromExternalRequest(request);\n * const { storefront } = await unauthenticated.storefront(shop);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n *\n * @example\n * Handling GraphQL errors.\n * Catch `GraphqlQueryError` errors to see error messages from the API.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { storefront } = await unauthenticated.storefront(shop);\n *\n * try {\n * const response = await storefront.graphql(\n * `#graphql\n * query incorrectQuery {\n * products(first: 10) {\n * nodes {\n * not_a_field\n * }\n * }\n * }`,\n * );\n *\n * return json({ data: await response.json() });\n * } catch (error) {\n * if (error instanceof GraphqlQueryError) {\n * // { errors: { graphQLErrors: [\n * // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n * // ] } }\n * return json({ errors: error.body?.errors }, { status: 500 });\n * }\n * return json({ message: \"An error occurred\" }, { status: 500 });\n * }\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n */\n storefront: StorefrontContext;\n}" + }, + "EnforceSessionStorage": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "EnforceSessionStorage", + "value": "Base & {\n sessionStorage: SessionStorageType;\n}", + "description": "" + }, + "Base": { + "filePath": "../shopify-api/rest/base.ts", + "name": "Base", + "description": "", + "members": [ + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertyDeclaration", + "name": "#session", + "value": "Session", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "GetAccessor", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "MethodDeclaration", + "name": "save", + "value": "({ update }?: SaveArgs) => Promise", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "MethodDeclaration", + "name": "saveAndUpdate", + "value": "() => Promise", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "() => Promise", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "MethodDeclaration", + "name": "serialize", + "value": "(saving?: boolean) => Body", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "MethodDeclaration", + "name": "toJSON", + "value": "() => Body", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "MethodDeclaration", + "name": "request", + "value": "(args: RequestArgs) => Promise>", + "description": "" + } + ], + "value": "export class Base {\n // For instance attributes\n [key: string]: any;\n\n public static Client: typeof RestClient;\n public static config: ConfigInterface;\n\n public static apiVersion: string;\n protected static resourceNames: ResourceNames[] = [];\n\n protected static primaryKey = 'id';\n protected static customPrefix: string | null = null;\n protected static readOnlyAttributes: string[] = [];\n\n protected static hasOne: Record = {};\n protected static hasMany: Record = {};\n\n protected static paths: ResourcePath[] = [];\n\n public static setClassProperties({Client, config}: SetClassPropertiesArgs) {\n this.Client = Client;\n this.config = config;\n }\n\n protected static async baseFind({\n session,\n urlIds,\n params,\n requireIds = false,\n }: BaseFindArgs): Promise> {\n if (requireIds) {\n const hasIds = Object.entries(urlIds).some(([_key, value]) => value);\n\n if (!hasIds) {\n throw new RestResourceError(\n 'No IDs given for request, cannot find path',\n );\n }\n }\n\n const response = await this.request({\n http_method: 'get',\n operation: 'get',\n session,\n urlIds,\n params,\n });\n\n return {\n data: this.createInstancesFromResponse(session, response.body as Body),\n headers: response.headers,\n pageInfo: response.pageInfo,\n };\n }\n\n protected static async request({\n session,\n http_method,\n operation,\n urlIds,\n params,\n body,\n entity,\n }: RequestArgs): Promise> {\n const client = new this.Client({\n session,\n apiVersion: this.apiVersion as ApiVersion,\n });\n\n const path = this.getPath({http_method, operation, urlIds, entity});\n\n const cleanParams: Record = {};\n if (params) {\n for (const key in params) {\n if (params[key] !== null) {\n cleanParams[key] = params[key];\n }\n }\n }\n\n switch (http_method) {\n case 'get':\n return client.get({path, query: cleanParams});\n case 'post':\n return client.post({\n path,\n query: cleanParams,\n data: body!,\n type: DataType.JSON,\n });\n case 'put':\n return client.put({\n path,\n query: cleanParams,\n data: body!,\n type: DataType.JSON,\n });\n case 'delete':\n return client.delete({path, query: cleanParams});\n default:\n throw new Error(`Unrecognized HTTP method \"${http_method}\"`);\n }\n }\n\n protected static getJsonBodyName(): string {\n return this.name.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();\n }\n\n protected static getPath({\n http_method,\n operation,\n urlIds,\n entity,\n }: GetPathArgs): string {\n let match: string | null = null;\n let specificity = -1;\n\n const potentialPaths: ResourcePath[] = [];\n this.paths.forEach((path: ResourcePath) => {\n if (\n http_method !== path.http_method ||\n operation !== path.operation ||\n path.ids.length <= specificity\n ) {\n return;\n }\n\n potentialPaths.push(path);\n\n let pathUrlIds: IdSet = {...urlIds};\n path.ids.forEach((id) => {\n if (!pathUrlIds[id] && entity && entity[id]) {\n pathUrlIds[id] = entity[id];\n }\n });\n\n pathUrlIds = Object.entries(pathUrlIds).reduce(\n (acc: IdSet, [key, value]: [string, string | number | null]) => {\n if (value) {\n acc[key] = value;\n }\n return acc;\n },\n {},\n );\n\n // If we weren't given all of the path's required ids, we can't use it\n const diff = path.ids.reduce(\n (acc: string[], id: string) => (pathUrlIds[id] ? acc : acc.concat(id)),\n [],\n );\n if (diff.length > 0) {\n return;\n }\n\n specificity = path.ids.length;\n match = path.path.replace(\n /(<([^>]+)>)/g,\n (_m1, _m2, id) => `${pathUrlIds[id]}`,\n );\n });\n\n if (!match) {\n const pathOptions = potentialPaths.map((path) => path.path);\n\n throw new RestResourceError(\n `Could not find a path for request. If you are trying to make a request to one of the following paths, ensure all relevant IDs are set. :\\n - ${pathOptions.join(\n '\\n - ',\n )}`,\n );\n }\n\n if (this.customPrefix) {\n return `${this.customPrefix}/${match}`;\n } else {\n return match;\n }\n }\n\n protected static createInstancesFromResponse(\n session: Session,\n data: Body,\n ): T[] {\n let instances: T[] = [];\n this.resourceNames.forEach((resourceName) => {\n const singular = resourceName.singular;\n const plural = resourceName.plural;\n if (data[plural] || Array.isArray(data[singular])) {\n instances = instances.concat(\n (data[plural] || data[singular]).reduce(\n (acc: T[], entry: Body) =>\n acc.concat(this.createInstance(session, entry)),\n [],\n ),\n );\n } else if (data[singular]) {\n instances.push(this.createInstance(session, data[singular]));\n }\n });\n\n return instances;\n }\n\n protected static createInstance(\n session: Session,\n data: Body,\n prevInstance?: T,\n ): T {\n const instance: T = prevInstance\n ? prevInstance\n : new (this as any)({session});\n\n if (data) {\n instance.setData(data);\n }\n\n return instance;\n }\n\n #session: Session;\n\n get session(): Session {\n return this.#session;\n }\n\n constructor({session, fromData}: BaseConstructorArgs) {\n this.#session = session;\n\n if (fromData) {\n this.setData(fromData);\n }\n }\n\n public async save({update = false}: SaveArgs = {}): Promise {\n const {primaryKey, resourceNames} = this.resource();\n const method = this[primaryKey] ? 'put' : 'post';\n\n const data = this.serialize(true);\n\n const response = await this.resource().request({\n http_method: method,\n operation: method,\n session: this.session,\n urlIds: {},\n body: {[this.resource().getJsonBodyName()]: data},\n entity: this,\n });\n\n const flattenResourceNames: string[] = resourceNames.reduce(\n (acc, obj) => {\n return acc.concat(Object.values(obj));\n },\n [],\n );\n\n const matchResourceName = Object.keys(response.body as Body).filter(\n (key: string) => flattenResourceNames.includes(key),\n );\n\n const body: Body | undefined = (response.body as Body)[\n matchResourceName[0]\n ];\n\n if (update && body) {\n this.setData(body);\n }\n }\n\n public async saveAndUpdate(): Promise {\n await this.save({update: true});\n }\n\n public async delete(): Promise {\n await this.resource().request({\n http_method: 'delete',\n operation: 'delete',\n session: this.session,\n urlIds: {},\n entity: this,\n });\n }\n\n public serialize(saving = false): Body {\n const {hasMany, hasOne, readOnlyAttributes} = this.resource();\n\n return Object.entries(this).reduce((acc: Body, [attribute, value]) => {\n if (\n ['#session'].includes(attribute) ||\n (saving && readOnlyAttributes.includes(attribute))\n ) {\n return acc;\n }\n\n if (attribute in hasMany && value) {\n acc[attribute] = value.reduce((attrAcc: Body, entry: Base) => {\n return attrAcc.concat(this.serializeSubAttribute(entry, saving));\n }, []);\n } else if (attribute in hasOne && value) {\n acc[attribute] = this.serializeSubAttribute(value, saving);\n } else {\n acc[attribute] = value;\n }\n\n return acc;\n }, {});\n }\n\n public toJSON(): Body {\n return this.serialize();\n }\n\n public request(args: RequestArgs) {\n return this.resource().request(args);\n }\n\n protected setData(data: Body): void {\n const {hasMany, hasOne} = this.resource();\n\n Object.entries(data).forEach(([attribute, val]) => {\n if (attribute in hasMany) {\n const HasManyResource: typeof Base = hasMany[attribute];\n this[attribute] = [];\n val.forEach((entry: Body) => {\n const obj = new HasManyResource({session: this.session});\n if (entry) {\n obj.setData(entry);\n }\n\n this[attribute].push(obj);\n });\n } else if (attribute in hasOne) {\n const HasOneResource: typeof Base = hasOne[attribute];\n const obj = new HasOneResource({session: this.session});\n if (val) {\n obj.setData(val);\n }\n this[attribute] = obj;\n } else {\n this[attribute] = val;\n }\n });\n }\n\n protected resource(): typeof Base {\n return this.constructor as unknown as typeof Base;\n }\n\n private serializeSubAttribute(attribute: Base, saving: boolean): Body {\n return attribute.serialize\n ? attribute.serialize(saving)\n : this.resource()\n .createInstance(this.session, attribute)\n .serialize(saving);\n }\n}" + }, + "Session": { + "filePath": "../shopify-api/lib/session/session.ts", + "name": "Session", + "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "members": [ + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "PropertyDeclaration", + "name": "id", + "value": "string", + "description": "The unique identifier for the session." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "PropertyDeclaration", + "name": "shop", + "value": "string", + "description": "The Shopify shop domain, such as `example.myshopify.com`." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "PropertyDeclaration", + "name": "state", + "value": "string", + "description": "The state of the session. Used for the OAuth authentication code flow." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "PropertyDeclaration", + "name": "isOnline", + "value": "boolean", + "description": "Whether the access token in the session is online or offline." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "PropertyDeclaration", + "name": "scope", + "value": "string", + "description": "The desired scopes for the access token, at the time the session was created." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "PropertyDeclaration", + "name": "expires", + "value": "Date", + "description": "The date the access token expires." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "PropertyDeclaration", + "name": "accessToken", + "value": "string", + "description": "The access token for the session." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "PropertyDeclaration", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "Information on the user for the session. Only present for online sessions." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "MethodDeclaration", + "name": "isActive", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "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." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeChanged", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "Whether the access token includes the given scopes if they are provided." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeIncluded", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "Whether the access token includes the given scopes." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "MethodDeclaration", + "name": "isExpired", + "value": "(withinMillisecondsOfExpiry?: number) => boolean", + "description": "Whether the access token is expired." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "MethodDeclaration", + "name": "toObject", + "value": "() => SessionParams", + "description": "Converts an object with data into a Session." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(other: Session) => boolean", + "description": "Checks whether the given session is equal to this session." + }, + { + "filePath": "../shopify-api/lib/session/session.ts", + "syntaxKind": "MethodDeclaration", + "name": "toPropertyArray", + "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", + "description": "Converts the session into an array of key-value pairs." + } + ], + "value": "export class Session {\n public static fromPropertyArray(\n entries: [string, string | number | boolean][],\n returnUserData = false,\n ): Session {\n if (!Array.isArray(entries)) {\n throw new InvalidSession(\n 'The parameter is not an array: a Session cannot be created from this object.',\n );\n }\n\n const obj = Object.fromEntries(\n entries\n .filter(([_key, value]) => value !== null && value !== undefined)\n // Sanitize keys\n .map(([key, value]) => {\n switch (key.toLowerCase()) {\n case 'isonline':\n return ['isOnline', value];\n case 'accesstoken':\n return ['accessToken', value];\n case 'onlineaccessinfo':\n return ['onlineAccessInfo', value];\n case 'userid':\n return ['userId', value];\n case 'firstname':\n return ['firstName', value];\n case 'lastname':\n return ['lastName', value];\n case 'accountowner':\n return ['accountOwner', value];\n case 'emailverified':\n return ['emailVerified', value];\n default:\n return [key.toLowerCase(), value];\n }\n }),\n );\n\n const sessionData = {} as SessionParams;\n const onlineAccessInfo = {\n associated_user: {},\n } as OnlineAccessInfo;\n Object.entries(obj).forEach(([key, value]) => {\n switch (key) {\n case 'isOnline':\n if (typeof value === 'string') {\n sessionData[key] = value.toString().toLowerCase() === 'true';\n } else if (typeof value === 'number') {\n sessionData[key] = Boolean(value);\n } else {\n sessionData[key] = value;\n }\n break;\n case 'scope':\n sessionData[key] = value.toString();\n break;\n case 'expires':\n sessionData[key] = value ? new Date(Number(value)) : undefined;\n break;\n case 'onlineAccessInfo':\n onlineAccessInfo.associated_user.id = Number(value);\n break;\n case 'userId':\n if (returnUserData) {\n onlineAccessInfo.associated_user.id = Number(value);\n break;\n }\n case 'firstName':\n if (returnUserData) {\n onlineAccessInfo.associated_user.first_name = String(value);\n break;\n }\n case 'lastName':\n if (returnUserData) {\n onlineAccessInfo.associated_user.last_name = String(value);\n break;\n }\n case 'email':\n if (returnUserData) {\n onlineAccessInfo.associated_user.email = String(value);\n break;\n }\n case 'accountOwner':\n if (returnUserData) {\n onlineAccessInfo.associated_user.account_owner = Boolean(value);\n break;\n }\n case 'locale':\n if (returnUserData) {\n onlineAccessInfo.associated_user.locale = String(value);\n break;\n }\n case 'collaborator':\n if (returnUserData) {\n onlineAccessInfo.associated_user.collaborator = Boolean(value);\n break;\n }\n case 'emailVerified':\n if (returnUserData) {\n onlineAccessInfo.associated_user.email_verified = Boolean(value);\n break;\n }\n // Return any user keys as passed in\n default:\n sessionData[key] = value;\n }\n });\n if (sessionData.isOnline) {\n sessionData.onlineAccessInfo = onlineAccessInfo;\n }\n const session = new Session(sessionData);\n return session;\n }\n\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n public shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n public state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n public isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n public scope?: string;\n /**\n * The date the access token expires.\n */\n public expires?: Date;\n /**\n * The access token for the session.\n */\n public accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n public onlineAccessInfo?: OnlineAccessInfo;\n\n constructor(params: SessionParams) {\n Object.assign(this, params);\n }\n\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n public isActive(scopes: AuthScopes | string | string[] | undefined): boolean {\n const hasAccessToken = Boolean(this.accessToken);\n const isTokenNotExpired = !this.isExpired();\n const isScopeChanged = this.isScopeChanged(scopes);\n return !isScopeChanged && hasAccessToken && isTokenNotExpired;\n }\n\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n public isScopeChanged(\n scopes: AuthScopes | string | string[] | undefined,\n ): boolean {\n if (typeof scopes === 'undefined') {\n return false;\n }\n\n return !this.isScopeIncluded(scopes);\n }\n\n /**\n * Whether the access token includes the given scopes.\n */\n public isScopeIncluded(scopes: AuthScopes | string | string[]): boolean {\n const requiredScopes =\n scopes instanceof AuthScopes ? scopes : new AuthScopes(scopes);\n const sessionScopes = new AuthScopes(this.scope);\n\n return sessionScopes.has(requiredScopes);\n }\n\n /**\n * Whether the access token is expired.\n */\n public isExpired(withinMillisecondsOfExpiry = 0): boolean {\n return Boolean(\n this.expires &&\n this.expires.getTime() - withinMillisecondsOfExpiry < Date.now(),\n );\n }\n\n /**\n * Converts an object with data into a Session.\n */\n public toObject(): SessionParams {\n const object: SessionParams = {\n id: this.id,\n shop: this.shop,\n state: this.state,\n isOnline: this.isOnline,\n };\n\n if (this.scope) {\n object.scope = this.scope;\n }\n if (this.expires) {\n object.expires = this.expires;\n }\n if (this.accessToken) {\n object.accessToken = this.accessToken;\n }\n if (this.onlineAccessInfo) {\n object.onlineAccessInfo = this.onlineAccessInfo;\n }\n return object;\n }\n\n /**\n * Checks whether the given session is equal to this session.\n */\n public equals(other: Session | undefined): boolean {\n if (!other) return false;\n\n const mandatoryPropsMatch =\n this.id === other.id &&\n this.shop === other.shop &&\n this.state === other.state &&\n this.isOnline === other.isOnline;\n\n if (!mandatoryPropsMatch) return false;\n\n const copyA = this.toPropertyArray(true);\n copyA.sort(([k1], [k2]) => (k1 < k2 ? -1 : 1));\n\n const copyB = other.toPropertyArray(true);\n copyB.sort(([k1], [k2]) => (k1 < k2 ? -1 : 1));\n\n return JSON.stringify(copyA) === JSON.stringify(copyB);\n }\n\n /**\n * Converts the session into an array of key-value pairs.\n */\n public toPropertyArray(\n returnUserData = false,\n ): [string, string | number | boolean][] {\n return (\n Object.entries(this)\n .filter(\n ([key, value]) =>\n propertiesToSave.includes(key) &&\n value !== undefined &&\n value !== null,\n )\n // Prepare values for db storage\n .flatMap(([key, value]): [string, string | number | boolean][] => {\n switch (key) {\n case 'expires':\n return [[key, value ? value.getTime() : undefined]];\n case 'onlineAccessInfo':\n // eslint-disable-next-line no-negated-condition\n if (!returnUserData) {\n return [[key, value.associated_user.id]];\n } else {\n return [\n ['userId', value?.associated_user?.id],\n ['firstName', value?.associated_user?.first_name],\n ['lastName', value?.associated_user?.last_name],\n ['email', value?.associated_user?.email],\n ['locale', value?.associated_user?.locale],\n ['emailVerified', value?.associated_user?.email_verified],\n ['accountOwner', value?.associated_user?.account_owner],\n ['collaborator', value?.associated_user?.collaborator],\n ];\n }\n default:\n return [[key, value]];\n }\n })\n // Filter out tuples with undefined values\n .filter(([_key, value]) => value !== undefined)\n );\n }\n}" }, - "GetUnauthenticatedStorefrontContext": { - "filePath": "src/server/unauthenticated/storefront/types.ts", - "name": "GetUnauthenticatedStorefrontContext", + "OnlineAccessInfo": { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "name": "OnlineAccessInfo", "description": "", - "params": [ + "members": [ { - "name": "shop", - "description": "", + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user", + "value": "OnlineAccessUser", + "description": "The user associated with the access token." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user_scope", "value": "string", - "filePath": "src/server/unauthenticated/storefront/types.ts" + "description": "The effective set of scopes for the session." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "expires_in", + "value": "number", + "description": "How long the access token is valid for, in seconds." } ], - "returns": { - "filePath": "src/server/unauthenticated/storefront/types.ts", - "description": "", - "name": "Promise", - "value": "Promise" - }, - "value": "export type GetUnauthenticatedStorefrontContext = (\n shop: string,\n) => Promise;" + "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" }, - "UnauthenticatedStorefrontContext": { - "filePath": "src/server/unauthenticated/storefront/types.ts", - "name": "UnauthenticatedStorefrontContext", + "OnlineAccessUser": { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "name": "OnlineAccessUser", "description": "", "members": [ { - "filePath": "src/server/unauthenticated/storefront/types.ts", + "filePath": "../shopify-api/lib/auth/oauth/types.ts", "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the given shop.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nThis will always be an offline session. You can use this to get shop specific data.", - "examples": [ - { - "title": "Using the offline session", - "description": "Get your app's shop-specific data using the returned offline `session` object.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { session } = await unauthenticated.storefront(shop);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "name": "account_owner", + "value": "boolean", + "description": "Whether the user is the account owner." }, { - "filePath": "src/server/unauthenticated/storefront/types.ts", + "filePath": "../shopify-api/lib/auth/oauth/types.ts", "syntaxKind": "PropertySignature", - "name": "storefront", - "value": "StorefrontContext", - "description": "Method for interacting with the Shopify GraphQL Storefront API for the given store.", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `storefront.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const shop = getShopFromExternalRequest(request);\n const { storefront } = await unauthenticated.storefront(shop);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "title": "app/routes/**\\/.ts" - } - ] - }, - { - "title": "Handling GraphQL errors", - "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { storefront } = await unauthenticated.storefront(shop);\n\n try {\n const response = await storefront.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // { errors: { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] } }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "name": "collaborator", + "value": "boolean", + "description": "Whether the user is a collaborator." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "email", + "value": "string", + "description": "The user's email address." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "email_verified", + "value": "boolean", + "description": "Whether the user has verified their email address." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "first_name", + "value": "string", + "description": "The user's first name." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "number", + "description": "The user's ID." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "last_name", + "value": "string", + "description": "The user's last name." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "locale", + "value": "string", + "description": "The user's locale." } ], - "value": "export interface UnauthenticatedStorefrontContext {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use this to get shop specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // app/routes/**\\/.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.storefront(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Method for interacting with the Shopify GraphQL Storefront API for the given store.\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const shop = getShopFromExternalRequest(request);\n * const { storefront } = await unauthenticated.storefront(shop);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n *\n * @example\n * Handling GraphQL errors.\n * Catch `GraphqlQueryError` errors to see error messages from the API.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { storefront } = await unauthenticated.storefront(shop);\n *\n * try {\n * const response = await storefront.graphql(\n * `#graphql\n * query incorrectQuery {\n * products(first: 10) {\n * nodes {\n * not_a_field\n * }\n * }\n * }`,\n * );\n *\n * return json({ data: await response.json() });\n * } catch (error) {\n * if (error instanceof GraphqlQueryError) {\n * // { errors: { graphQLErrors: [\n * // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n * // ] } }\n * return json({ errors: error.body?.errors }, { status: 500 });\n * }\n * return json({ message: \"An error occurred\" }, { status: 500 });\n * }\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n */\n storefront: StorefrontContext;\n}" - }, - "EnforceSessionStorage": { - "filePath": "src/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "EnforceSessionStorage", - "value": "Base & {\n sessionStorage: SessionStorageType;\n}", - "description": "" + "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" }, - "SingleMerchantApp": { - "filePath": "src/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "SingleMerchantApp", - "value": "ShopifyAppBase & ShopifyAppLogin", - "description": "" + "AuthScopes": { + "filePath": "../shopify-api/lib/auth/scopes/index.ts", + "name": "AuthScopes", + "description": "A class that represents a set of access token scopes.", + "members": [ + { + "filePath": "../shopify-api/lib/auth/scopes/index.ts", + "syntaxKind": "MethodDeclaration", + "name": "has", + "value": "(scope: string | string[] | AuthScopes) => boolean", + "description": "Checks whether the current set of scopes includes the given one." + }, + { + "filePath": "../shopify-api/lib/auth/scopes/index.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(otherScopes: string | string[] | AuthScopes) => boolean", + "description": "Checks whether the current set of scopes equals the given one." + }, + { + "filePath": "../shopify-api/lib/auth/scopes/index.ts", + "syntaxKind": "MethodDeclaration", + "name": "toString", + "value": "() => string", + "description": "Returns a comma-separated string with the current set of scopes." + }, + { + "filePath": "../shopify-api/lib/auth/scopes/index.ts", + "syntaxKind": "MethodDeclaration", + "name": "toArray", + "value": "(returnOriginalScopes?: boolean) => any[]", + "description": "Returns an array with the current set of scopes." + } + ], + "value": "class AuthScopes {\n public static SCOPE_DELIMITER = ',';\n\n private compressedScopes: Set;\n private expandedScopes: Set;\n private originalScopes: Set;\n\n constructor(scopes: string | string[] | AuthScopes | undefined) {\n let scopesArray: string[] = [];\n if (typeof scopes === 'string') {\n scopesArray = scopes.split(\n new RegExp(`${AuthScopes.SCOPE_DELIMITER}\\\\s*`),\n );\n } else if (Array.isArray(scopes)) {\n scopesArray = scopes;\n } else if (scopes) {\n scopesArray = Array.from(scopes.expandedScopes);\n }\n\n scopesArray = scopesArray\n .map((scope) => scope.trim())\n .filter((scope) => scope.length);\n\n const impliedScopes = this.getImpliedScopes(scopesArray);\n\n const scopeSet = new Set(scopesArray);\n const impliedSet = new Set(impliedScopes);\n\n this.compressedScopes = new Set(\n [...scopeSet].filter((x) => !impliedSet.has(x)),\n );\n this.expandedScopes = new Set([...scopeSet, ...impliedSet]);\n this.originalScopes = scopeSet;\n }\n\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n public has(scope: string | string[] | AuthScopes | undefined) {\n let other: AuthScopes;\n\n if (scope instanceof AuthScopes) {\n other = scope;\n } else {\n other = new AuthScopes(scope);\n }\n\n return (\n other.toArray().filter((x) => !this.expandedScopes.has(x)).length === 0\n );\n }\n\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n public equals(otherScopes: string | string[] | AuthScopes | undefined) {\n let other: AuthScopes;\n\n if (otherScopes instanceof AuthScopes) {\n other = otherScopes;\n } else {\n other = new AuthScopes(otherScopes);\n }\n\n return (\n this.compressedScopes.size === other.compressedScopes.size &&\n this.toArray().filter((x) => !other.has(x)).length === 0\n );\n }\n\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n public toString() {\n return this.toArray().join(AuthScopes.SCOPE_DELIMITER);\n }\n\n /**\n * Returns an array with the current set of scopes.\n */\n public toArray(returnOriginalScopes = false) {\n return returnOriginalScopes\n ? [...this.originalScopes]\n : [...this.compressedScopes];\n }\n\n private getImpliedScopes(scopesArray: string[]): string[] {\n return scopesArray.reduce((array: string[], current: string) => {\n const matches = current.match(/^(unauthenticated_)?write_(.*)$/);\n if (matches) {\n array.push(`${matches[1] ? matches[1] : ''}read_${matches[2]}`);\n }\n\n return array;\n }, []);\n }\n}" }, - "ShopifyAppBase": { - "filePath": "src/server/types.ts", - "name": "ShopifyAppBase", + "SessionParams": { + "filePath": "../shopify-api/lib/session/types.ts", + "name": "SessionParams", "description": "", "members": [ { - "filePath": "src/server/types.ts", + "filePath": "../shopify-api/lib/session/types.ts", + "name": "[key: string]", + "value": "any" + }, + { + "filePath": "../shopify-api/lib/session/types.ts", "syntaxKind": "PropertySignature", - "name": "addDocumentResponseHeaders", - "value": "AddDocumentResponseHeaders", - "description": "Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n\n\n\n\n", - "examples": [ - { - "title": "Return headers on all requests", - "description": "Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const addDocumentResponseheaders = shopify.addDocumentResponseheaders;", - "title": "~/shopify.server.ts" - }, - { - "code": "import { addDocumentResponseHeaders } from \"~/shopify.server\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const markup = renderToString(\n \n );\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n addDocumentResponseHeaders(request, responseHeaders);\n\n return new Response(\"\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}", - "title": "entry.server.tsx" - } - ] - } - ] + "name": "accessToken", + "value": "string", + "description": "The access token for the session.", + "isOptional": true }, { - "filePath": "src/server/types.ts", + "filePath": "../shopify-api/lib/session/types.ts", "syntaxKind": "PropertySignature", - "name": "authenticate", - "value": "Authenticate", - "description": "Ways to authenticate requests from different surfaces across Shopify.", - "examples": [ - { - "title": "Authenticate Shopify requests", - "description": "Use the functions in `authenticate` to validate requests coming from Shopify.", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "title": "/app/routes/**\\/*.jsx" - } - ] - } - ] + "name": "expires", + "value": "Date", + "description": "The date the access token expires.", + "isOptional": true }, { - "filePath": "src/server/types.ts", + "filePath": "../shopify-api/lib/session/types.ts", "syntaxKind": "PropertySignature", - "name": "registerWebhooks", - "value": "RegisterWebhooks", - "description": "Register shop-specific webhook subscriptions using the Admin GraphQL API.\n\nIn many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n\n\n\n\n\n\n\nYou should only use this if you need shop-specific webhooks.", - "examples": [ - { - "title": "Registering shop-specific webhooks after install", - "description": "Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)", - "tabs": [ - { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});", - "title": "app/shopify.server.ts" - } - ] - } - ] + "name": "id", + "value": "string", + "description": "The unique identifier for the session." + }, + { + "filePath": "../shopify-api/lib/session/types.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "Whether the access token in the session is online or offline." }, { - "filePath": "src/server/types.ts", + "filePath": "../shopify-api/lib/session/types.ts", "syntaxKind": "PropertySignature", - "name": "sessionStorage", - "value": "SessionStorageType", - "description": "The `SessionStorage` instance you passed in as a config option.", - "isOptional": true, - "examples": [ - { - "title": "Storing sessions with Prisma", - "description": "Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n sessionStorage: new PrismaSessionStorage(prisma),\n // ...etc\n})\n\n// shopify.sessionStorage is an instance of PrismaSessionStorage", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo | StoredOnlineAccessInfo", + "description": "Information on the user for the session. Only present for online sessions.", + "isOptional": true }, { - "filePath": "src/server/types.ts", + "filePath": "../shopify-api/lib/session/types.ts", "syntaxKind": "PropertySignature", - "name": "unauthenticated", - "value": "Unauthenticated>", - "description": "Ways to get Contexts from requests that do not originate from Shopify.", - "examples": [ - { - "title": "Using unauthenticated contexts", - "description": "Create contexts for requests that don't come from Shopify.", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "title": "/app/routes/**\\/*.jsx" - } - ] - } - ] - } - ], - "value": "export interface ShopifyAppBase {\n /**\n * The `SessionStorage` instance you passed in as a config option.\n *\n * @example\n * Storing sessions with Prisma.\n * Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n * import prisma from \"~/db.server\";\n *\n * const shopify = shopifyApp({\n * sessionStorage: new PrismaSessionStorage(prisma),\n * // ...etc\n * })\n *\n * // shopify.sessionStorage is an instance of PrismaSessionStorage\n * ```\n */\n sessionStorage?: SessionStorageType;\n\n /**\n * Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n *\n * {@link https://shopify.dev/docs/apps/store/security/iframe-protection}\n *\n * @example\n * Return headers on all requests.\n * Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.\n *\n * ```\n * // ~/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * export const addDocumentResponseheaders = shopify.addDocumentResponseheaders;\n * ```\n *\n * ```ts\n * // entry.server.tsx\n * import { addDocumentResponseHeaders } from \"~/shopify.server\";\n *\n * export default function handleRequest(\n * request: Request,\n * responseStatusCode: number,\n * responseHeaders: Headers,\n * remixContext: EntryContext\n * ) {\n * const markup = renderToString(\n * \n * );\n *\n * responseHeaders.set(\"Content-Type\", \"text/html\");\n * addDocumentResponseHeaders(request, responseHeaders);\n *\n * return new Response(\"\" + markup, {\n * status: responseStatusCode,\n * headers: responseHeaders,\n * });\n * }\n * ```\n */\n addDocumentResponseHeaders: AddDocumentResponseHeaders;\n\n /**\n * Register shop-specific webhook subscriptions using the Admin GraphQL API.\n *\n * In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n *\n * {@link https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions}\n *\n * You should only use this if you need shop-specific webhooks.\n *\n * @example\n * Registering shop-specific webhooks after install\n * Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)\n * ```ts\n * // app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * PRODUCTS_CREATE: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks/products/create\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * // Register webhooks for the shop\n * // In this example, every shop will have these webhooks\n * // You could wrap this in some custom shop specific conditional logic if needed\n * shopify.registerWebhooks({ session });\n * },\n * },\n * // ...etc\n * });\n * ```\n */\n registerWebhooks: RegisterWebhooks;\n\n /**\n * Ways to authenticate requests from different surfaces across Shopify.\n *\n * @example\n * Authenticate Shopify requests.\n * Use the functions in `authenticate` to validate requests coming from Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n */\n authenticate: Authenticate;\n\n /**\n * Ways to get Contexts from requests that do not originate from Shopify.\n *\n * @example\n * Using unauthenticated contexts.\n * Create contexts for requests that don't come from Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {admin} = await shopify.unauthenticated.admin(shop);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n */\n unauthenticated: Unauthenticated>;\n}" - }, - "ShopifyAppLogin": { - "filePath": "src/server/types.ts", - "name": "ShopifyAppLogin", - "description": "", - "members": [ + "name": "scope", + "value": "string", + "description": "The scopes for the access token.", + "isOptional": true + }, { - "filePath": "src/server/types.ts", + "filePath": "../shopify-api/lib/session/types.ts", "syntaxKind": "PropertySignature", - "name": "login", - "value": "Login", - "description": "Log a merchant in, and redirect them to the app root. Will redirect the merchant to authentication if a shop is present in the URL search parameters or form data.\n\nThis function won't be present when the `distribution` config option is set to `AppDistribution.ShopifyAdmin`, because Admin apps aren't allowed to show a login page.", - "examples": [ - { - "title": "Creating a login page", - "description": "Use `shopify.login` to create a login form, in a route that can handle GET and POST requests.", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const errors = shopify.login(request);\n\n return json(errors);\n}\n\nexport async function action({ request }: ActionFunctionArgs) {\n const errors = shopify.login(request);\n\n return json(errors);\n}\n\nexport default function Auth() {\n const actionData = useActionData();\n const [shop, setShop] = useState(\"\");\n\n return (\n \n \n
\n \n \n Login\n \n \n \n \n
\n
\n
\n );\n}", - "title": "/app/routes/auth/login.tsx" - } - ] - } - ] - } - ], - "value": "export interface ShopifyAppLogin {\n /**\n * Log a merchant in, and redirect them to the app root. Will redirect the merchant to authentication if a shop is\n * present in the URL search parameters or form data.\n *\n * This function won't be present when the `distribution` config option is set to `AppDistribution.ShopifyAdmin`,\n * because Admin apps aren't allowed to show a login page.\n *\n * @example\n * Creating a login page.\n * Use `shopify.login` to create a login form, in a route that can handle GET and POST requests.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/auth/login.tsx\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const errors = shopify.login(request);\n *\n * return json(errors);\n * }\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const errors = shopify.login(request);\n *\n * return json(errors);\n * }\n *\n * export default function Auth() {\n * const actionData = useActionData();\n * const [shop, setShop] = useState(\"\");\n *\n * return (\n * \n * \n *
\n * \n * \n * Login\n * \n * \n * \n * \n *
\n *
\n *
\n * );\n * }\n * ```\n */\n login: Login;\n}" - }, - "Login": { - "filePath": "src/server/types.ts", - "name": "Login", - "description": "", - "params": [ + "name": "shop", + "value": "string", + "description": "The Shopify shop domain." + }, { - "name": "request", - "description": "", - "value": "Request", - "filePath": "src/server/types.ts" + "filePath": "../shopify-api/lib/session/types.ts", + "syntaxKind": "PropertySignature", + "name": "state", + "value": "string", + "description": "The state of the session. Used for the OAuth authentication code flow." } ], - "returns": { - "filePath": "src/server/types.ts", - "description": "", - "name": "Promise", - "value": "Promise" - }, - "value": "type Login = (request: Request) => Promise;" + "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" }, - "LoginError": { - "filePath": "src/server/types.ts", - "name": "LoginError", + "StoredOnlineAccessInfo": { + "filePath": "../shopify-api/lib/session/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "StoredOnlineAccessInfo", + "value": "Omit & {\n associated_user: Partial;\n}", + "description": "" + }, + "SaveArgs": { + "filePath": "../shopify-api/rest/base.ts", + "name": "SaveArgs", "description": "", "members": [ { - "filePath": "src/server/types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "shop", - "value": "LoginErrorType", + "name": "update", + "value": "boolean", "description": "", "isOptional": true } ], - "value": "export interface LoginError {\n shop?: LoginErrorType;\n}" - }, - "LoginErrorType": { - "filePath": "src/server/types.ts", - "syntaxKind": "EnumDeclaration", - "name": "LoginErrorType", - "value": "export enum LoginErrorType {\n MissingShop = 'MISSING_SHOP',\n InvalidShop = 'INVALID_SHOP',\n}", - "members": [ - { - "filePath": "src/server/types.ts", - "name": "MissingShop", - "value": "MISSING_SHOP" - }, - { - "filePath": "src/server/types.ts", - "name": "InvalidShop", - "value": "INVALID_SHOP" - } - ] + "value": "interface SaveArgs {\n update?: boolean;\n}" }, - "AppStoreApp": { - "filePath": "src/server/types.ts", + "Body": { + "filePath": "../shopify-api/rest/types.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "AppStoreApp", - "value": "ShopifyAppBase & ShopifyAppLogin", - "description": "" + "name": "Body", + "value": "Record", + "description": "", + "members": [] }, - "AppConfigArg": { - "filePath": "src/server/config-types.ts", - "name": "AppConfigArg", + "RequestArgs": { + "filePath": "../shopify-api/rest/base.ts", + "name": "RequestArgs", "description": "", "members": [ { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "_logDisabledFutureFlags", - "value": "boolean", - "description": "Whether to log disabled future flags at startup.", - "isOptional": true, - "isPrivate": true + "name": "body", + "value": "Body | null", + "description": "", + "isOptional": true }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "adminApiAccessToken", - "value": "string", - "description": "An app-wide API access token.\n\nOnly applies to custom apps.", + "name": "entity", + "value": "Base | null", + "description": "", "isOptional": true }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "apiKey", + "name": "http_method", "value": "string", - "description": "The API key for your app.\n\nAlso known as Client ID in your Partner Dashboard.", - "isOptional": true + "description": "" }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "apiSecretKey", + "name": "operation", "value": "string", - "description": "The API secret key for your app.\n\nAlso known as Client Secret in your Partner Dashboard." + "description": "" }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "ApiVersion", - "description": "What version of Shopify's Admin API's would you like to use.\n\n\n\n\n", - "isOptional": true, - "defaultValue": "`LATEST_API_VERSION` from `@shopify/shopify-app-remix`", - "examples": [ - { - "title": "Using the latest API Version (Recommended)", - "description": "", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n apiVersion: LATEST_API_VERSION,\n});", - "title": "Example" - } - ] - } - ] + "name": "params", + "value": "ParamSet", + "description": "", + "isOptional": true }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "appUrl", - "value": "string", - "description": "The URL your app is running on.\n\nThe `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL." + "name": "requireIds", + "value": "boolean", + "description": "", + "isOptional": true }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "authPathPrefix", - "value": "string", - "description": "A path that Shopify can reserve for auth related endpoints.\n\nThis must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.", - "isOptional": true, - "defaultValue": "`\"/auth\"`", - "examples": [ - { - "title": "Using the latest API Version (Recommended)", - "description": "", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n apiVersion: LATEST_API_VERSION,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;\n\n// /app/routes/auth/$.jsx\nimport { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n await authenticate.admin(request);\n\n return null\n}", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "name": "session", + "value": "Session", + "description": "" }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/rest/base.ts", "syntaxKind": "PropertySignature", - "name": "billing", - "value": "BillingConfig", - "description": "Billing configurations for the app.", - "isOptional": true - }, + "name": "urlIds", + "value": "IdSet", + "description": "" + } + ], + "value": "interface RequestArgs extends BaseFindArgs {\n http_method: string;\n operation: string;\n body?: Body | null;\n entity?: Base | null;\n}" + }, + "ParamSet": { + "filePath": "../shopify-api/rest/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ParamSet", + "value": "Record", + "description": "", + "members": [] + }, + "IdSet": { + "filePath": "../shopify-api/rest/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "IdSet", + "value": "Record", + "description": "", + "members": [] + }, + "RestRequestReturn": { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "name": "RestRequestReturn", + "description": "", + "members": [ { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "customShopDomains", - "value": "(string | RegExp)[]", - "description": "Override values for Shopify shop domains.", - "isOptional": true + "name": "body", + "value": "T", + "description": "" }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "distribution", - "value": "AppDistribution", - "description": "How your app is distributed. Default is `AppDistribution.AppStore`.\n\nAppStore should be used for public apps that are distributed in the Shopify App Store. SingleMerchant should be used for custom apps managed in the Partner Dashboard. ShopifyAdmin should be used for apps that are managed in the merchant's Shopify Admin.\n\n\n\n\n", - "isOptional": true + "name": "headers", + "value": "Headers", + "description": "" }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "future", - "value": "Future", - "description": "Features that will be introduced in future releases of this package.\n\nYou can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future releases in advance and provide feedback on the new features.", + "name": "pageInfo", + "value": "PageInfo", + "description": "", "isOptional": true - }, + } + ], + "value": "export interface RestRequestReturn {\n body: T;\n headers: Headers;\n pageInfo?: PageInfo;\n}" + }, + "PageInfo": { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "name": "PageInfo", + "description": "", + "members": [ { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "hooks", - "value": "HooksConfig", - "description": "Functions to call at key places during your apps lifecycle.\n\nThese functions are called in the context of the request that triggered them. This means you can access the session.", - "isOptional": true, - "examples": [ - { - "title": "Seeding your database custom data when a merchant installs your app", - "description": "", - "tabs": [ - { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { seedStoreData } from \"~/db/seeds\"\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n seedStoreData({session})\n }\n },\n // ...etc\n});", - "title": "Example" - } - ] - } - ] + "name": "fields", + "value": "string[]", + "description": "", + "isOptional": true }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "isEmbeddedApp", - "value": "boolean", - "description": "Does your app render embedded inside the Shopify Admin or on its own.\n\nUnless you have very specific needs, this should be true.", - "isOptional": true, - "defaultValue": "`true`" + "name": "limit", + "value": "string", + "description": "" }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "isTesting", - "value": "boolean", - "description": "Whether the app is initialised for local testing.", + "name": "nextPage", + "value": "PageInfoParams", + "description": "", "isOptional": true }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "logger", - "value": "{ log?: LogFunction; level?: LogSeverity; httpRequests?: boolean; timestamps?: boolean; }", - "description": "Customization options for Shopify logs.", + "name": "nextPageUrl", + "value": "string", + "description": "", "isOptional": true }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "privateAppStorefrontAccessToken", + "name": "previousPageUrl", "value": "string", - "description": "An app-wide API access token for the storefront API.\n\nOnly applies to custom apps.", + "description": "", "isOptional": true }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "restResources", - "value": "Resources", - "description": "REST resources to access the Admin API.\n\nYou can import these from `@shopify/shopify-api/rest/admin/*`.", + "name": "prevPage", + "value": "PageInfoParams", + "description": "", "isOptional": true - }, + } + ], + "value": "export interface PageInfo {\n limit: string;\n fields?: string[];\n previousPageUrl?: string;\n nextPageUrl?: string;\n prevPage?: PageInfoParams;\n nextPage?: PageInfoParams;\n}" + }, + "PageInfoParams": { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "name": "PageInfoParams", + "description": "", + "members": [ { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "scopes", - "value": "string[] | AuthScopes", - "description": "The scopes your app needs to access the API. Not required if using Shopify managed installation.", - "isOptional": true + "name": "path", + "value": "string", + "description": "" }, { - "filePath": "src/server/config-types.ts", + "filePath": "../shopify-api/lib/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "sessionStorage", - "value": "Storage", - "description": "An adaptor for storing sessions in your database of choice.\n\nShopify provides multiple session storage adaptors and you can create your own.\n\nOptional for apps created in the Shopify Admin.\n\n\n\n\n", - "isOptional": true, + "name": "query", + "value": "SearchParams", + "description": "" + } + ], + "value": "export interface PageInfoParams {\n path: string;\n query: SearchParams;\n}" + }, + "SingleMerchantApp": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "SingleMerchantApp", + "value": "ShopifyAppBase & ShopifyAppLogin", + "description": "" + }, + "ShopifyAppBase": { + "filePath": "src/server/types.ts", + "name": "ShopifyAppBase", + "description": "", + "members": [ + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "addDocumentResponseHeaders", + "value": "AddDocumentResponseHeaders", + "description": "Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n\n\n\n\n", "examples": [ { - "title": "Storing sessions with Prisma", - "description": "Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.", + "title": "Return headers on all requests", + "description": "Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.", "tabs": [ { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n // ... etc\n sessionStorage: new PrismaSessionStorage(prisma),\n});\nexport default shopify;", - "title": "Example" + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const addDocumentResponseheaders = shopify.addDocumentResponseheaders;", + "title": "~/shopify.server.ts" + }, + { + "code": "import { addDocumentResponseHeaders } from \"~/shopify.server\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const markup = renderToString(\n \n );\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n addDocumentResponseHeaders(request, responseHeaders);\n\n return new Response(\"\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}", + "title": "entry.server.tsx" } ] } ] }, { - "filePath": "src/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "useOnlineTokens", - "value": "boolean", - "description": "Whether your app use online or offline tokens.\n\nIf your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.\n\n\n\n\n", - "isOptional": true, - "defaultValue": "`false`" - }, - { - "filePath": "src/server/config-types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "userAgentPrefix", - "value": "string", - "description": "The user agent prefix to use for API requests.", - "isOptional": true + "name": "authenticate", + "value": "Authenticate", + "description": "Ways to authenticate requests from different surfaces across Shopify.", + "examples": [ + { + "title": "Authenticate Shopify requests", + "description": "Use the functions in `authenticate` to validate requests coming from Shopify.", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n const response = admin.graphql(`{ shop { name } }`)\n\n return json(await response.json());\n}", + "title": "/app/routes/**\\/*.jsx" + } + ] + } + ] }, { - "filePath": "src/server/config-types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "webhooks", - "value": "WebhookConfig", - "description": "The config for the shop-specific webhooks your app needs.\n\nUse this to configure shop-specific webhooks. In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n\n\n\n\n\n\n\nYou should only use this if you need shop-specific webhooks. If you do need shop-specific webhooks this can be in used in conjunction with the afterAuth hook, loaders or processes such as background jobs.", - "isOptional": true, + "name": "registerWebhooks", + "value": "RegisterWebhooks", + "description": "Register shop-specific webhook subscriptions using the Admin GraphQL API.\n\nIn many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n\n\n\n\n\n\n\nYou should only use this if you need shop-specific webhooks.", "examples": [ { - "title": "Registering shop-specific webhooks", - "description": "", + "title": "Registering shop-specific webhooks after install", + "description": "Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)", "tabs": [ { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // But you could wrap this in some custom shop specific conditional logic\n shopify.registerWebhooks({ session });\n }\n },\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});", "title": "app/shopify.server.ts" } ] - }, + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "sessionStorage", + "value": "SessionStorageType", + "description": "The `SessionStorage` instance you passed in as a config option.", + "isOptional": true, + "examples": [ { - "title": "Registering app-specific webhooks (Recommended)", - "description": "", + "title": "Storing sessions with Prisma", + "description": "Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.", "tabs": [ { - "code": "# shopify.app.toml\n[webhooks]\napi_version = \"2024-07\"\n\n [[webhooks.subscriptions]]\n topics = [\"products/create\"]\n uri = \"/webhooks/products/create\"", - "title": "Example" + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n sessionStorage: new PrismaSessionStorage(prisma),\n // ...etc\n})\n\n// shopify.sessionStorage is an instance of PrismaSessionStorage", + "title": "/app/shopify.server.ts" } ] - }, + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "unauthenticated", + "value": "Unauthenticated>", + "description": "Ways to get Contexts from requests that do not originate from Shopify.", + "examples": [ { - "title": "Authenticating a webhook request", - "description": "", + "title": "Using unauthenticated contexts", + "description": "Create contexts for requests that don't come from Shopify.", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic, shop, session, payload } = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n // Handle the webhook\n console.log(`${TOPIC} webhook received with`, JSON.stringify(payload))\n\n throw new Response();\n};", - "title": "/app/routes/webhooks.ts" + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n const response = admin.graphql(`{ shop { currencyCode } }`)\n\n return json(await response.json());\n}", + "title": "/app/routes/**\\/*.jsx" } ] } ] } ], - "value": "export interface AppConfigArg<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n Storage extends SessionStorage = SessionStorage,\n Future extends FutureFlagOptions = FutureFlagOptions,\n> extends Omit<\n ApiConfigArg>,\n | 'hostName'\n | 'hostScheme'\n | 'isEmbeddedApp'\n | 'apiVersion'\n | 'isCustomStoreApp'\n | 'future'\n > {\n /**\n * The URL your app is running on.\n *\n * The `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL.\n */\n appUrl: string;\n\n /**\n * An adaptor for storing sessions in your database of choice.\n *\n * Shopify provides multiple session storage adaptors and you can create your own.\n *\n * Optional for apps created in the Shopify Admin.\n *\n * {@link https://github.com/Shopify/shopify-app-js/blob/main/README.md#session-storage-options}\n *\n * @example\n * Storing sessions with Prisma.\n * Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.\n * ```ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n *\n * import prisma from \"~/db.server\";\n *\n * const shopify = shopifyApp({\n * // ... etc\n * sessionStorage: new PrismaSessionStorage(prisma),\n * });\n * export default shopify;\n * ```\n */\n sessionStorage?: Storage;\n\n /**\n * Whether your app use online or offline tokens.\n *\n * If your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/access-modes}\n *\n * @defaultValue `false`\n */\n useOnlineTokens?: boolean;\n\n /**\n * The config for the shop-specific webhooks your app needs.\n * \n * Use this to configure shop-specific webhooks. In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n * \n * {@link https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions}\n * \n * You should only use this if you need shop-specific webhooks. If you do need shop-specific webhooks this can be in used in conjunction with the afterAuth hook, loaders or processes such as background jobs.\n *\n * @example\n * Registering shop-specific webhooks.\n * ```ts\n * // app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * PRODUCTS_CREATE: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks/products/create\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * // Register webhooks for the shop\n * // In this example, every shop will have these webhooks\n * // But you could wrap this in some custom shop specific conditional logic\n * shopify.registerWebhooks({ session });\n * }\n * },\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * \n * @example\n * Registering app-specific webhooks (Recommended)\n * ```toml\n * # shopify.app.toml\n * [webhooks]\n * api_version = \"2024-07\"\n\n * [[webhooks.subscriptions]]\n * topics = [\"products/create\"]\n * uri = \"/webhooks/products/create\"\n * \n * ```\n * \n * @example\n * Authenticating a webhook request\n *\n * ```ts\n * // /app/routes/webhooks.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import db from \"../db.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { topic, shop, session, payload } = await authenticate.webhook(request);\n * \n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n * \n * // Handle the webhook\n * console.log(`${TOPIC} webhook received with`, JSON.stringify(payload))\n *\n * throw new Response();\n * };\n * ```\n */\n webhooks?: WebhookConfig;\n\n /**\n * Functions to call at key places during your apps lifecycle.\n *\n * These functions are called in the context of the request that triggered them. This means you can access the session.\n *\n * @example\n * Seeding your database custom data when a merchant installs your app.\n * ```ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { seedStoreData } from \"~/db/seeds\"\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * seedStoreData({session})\n * }\n * },\n * // ...etc\n * });\n * ```\n */\n hooks?: HooksConfig;\n\n /**\n * Does your app render embedded inside the Shopify Admin or on its own.\n *\n * Unless you have very specific needs, this should be true.\n *\n * @defaultValue `true`\n */\n isEmbeddedApp?: boolean;\n\n /**\n * How your app is distributed. Default is `AppDistribution.AppStore`.\n *\n * AppStore should be used for public apps that are distributed in the Shopify App Store.\n * SingleMerchant should be used for custom apps managed in the Partner Dashboard.\n * ShopifyAdmin should be used for apps that are managed in the merchant's Shopify Admin.\n *\n * {@link https://shopify.dev/docs/apps/distribution}\n */\n distribution?: AppDistribution;\n\n /**\n * What version of Shopify's Admin API's would you like to use.\n *\n * {@link https://shopify.dev/docs/api/}\n *\n * @defaultValue `LATEST_API_VERSION` from `@shopify/shopify-app-remix`\n *\n * @example\n * Using the latest API Version (Recommended)\n * ```ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * apiVersion: LATEST_API_VERSION,\n * });\n * ```\n */\n apiVersion?: ApiVersion;\n\n /**\n * A path that Shopify can reserve for auth related endpoints.\n *\n * This must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.\n *\n * @default `\"/auth\"`\n *\n * @example\n * Using the latest API Version (Recommended)\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * apiVersion: LATEST_API_VERSION,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n *\n * // /app/routes/auth/$.jsx\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * await authenticate.admin(request);\n *\n * return null\n * }\n * ```\n */\n authPathPrefix?: string;\n\n /**\n * Features that will be introduced in future releases of this package.\n *\n * You can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future\n * releases in advance and provide feedback on the new features.\n */\n future?: Future;\n}" - }, - "BillingConfig": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfig", - "description": "Billing configuration options, indexed by an app-specific plan name.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "[plan: string]", - "value": "BillingConfigItem" - } - ], - "value": "export interface BillingConfig {\n /**\n * An individual billing plan.\n */\n [plan: string]: BillingConfigItem;\n}" - }, - "BillingConfigItem": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "BillingConfigItem", - "value": "FeatureEnabled extends true ? BillingConfigOneTimePlan | BillingConfigSubscriptionLineItemPlan : BillingConfigLegacyItem", - "description": "" + "value": "export interface ShopifyAppBase {\n /**\n * The `SessionStorage` instance you passed in as a config option.\n *\n * @example\n * Storing sessions with Prisma.\n * Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n * import prisma from \"~/db.server\";\n *\n * const shopify = shopifyApp({\n * sessionStorage: new PrismaSessionStorage(prisma),\n * // ...etc\n * })\n *\n * // shopify.sessionStorage is an instance of PrismaSessionStorage\n * ```\n */\n sessionStorage?: SessionStorageType;\n\n /**\n * Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n *\n * {@link https://shopify.dev/docs/apps/store/security/iframe-protection}\n *\n * @example\n * Return headers on all requests.\n * Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.\n *\n * ```\n * // ~/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * export const addDocumentResponseheaders = shopify.addDocumentResponseheaders;\n * ```\n *\n * ```ts\n * // entry.server.tsx\n * import { addDocumentResponseHeaders } from \"~/shopify.server\";\n *\n * export default function handleRequest(\n * request: Request,\n * responseStatusCode: number,\n * responseHeaders: Headers,\n * remixContext: EntryContext\n * ) {\n * const markup = renderToString(\n * \n * );\n *\n * responseHeaders.set(\"Content-Type\", \"text/html\");\n * addDocumentResponseHeaders(request, responseHeaders);\n *\n * return new Response(\"\" + markup, {\n * status: responseStatusCode,\n * headers: responseHeaders,\n * });\n * }\n * ```\n */\n addDocumentResponseHeaders: AddDocumentResponseHeaders;\n\n /**\n * Register shop-specific webhook subscriptions using the Admin GraphQL API.\n *\n * In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n *\n * {@link https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions}\n *\n * You should only use this if you need shop-specific webhooks.\n *\n * @example\n * Registering shop-specific webhooks after install\n * Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)\n * ```ts\n * // app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * PRODUCTS_CREATE: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks/products/create\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * // Register webhooks for the shop\n * // In this example, every shop will have these webhooks\n * // You could wrap this in some custom shop specific conditional logic if needed\n * shopify.registerWebhooks({ session });\n * },\n * },\n * // ...etc\n * });\n * ```\n */\n registerWebhooks: RegisterWebhooks;\n\n /**\n * Ways to authenticate requests from different surfaces across Shopify.\n *\n * @example\n * Authenticate Shopify requests.\n * Use the functions in `authenticate` to validate requests coming from Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n * const response = admin.graphql(`{ shop { name } }`)\n *\n * return json(await response.json());\n * }\n * ```\n */\n authenticate: Authenticate;\n\n /**\n * Ways to get Contexts from requests that do not originate from Shopify.\n *\n * @example\n * Using unauthenticated contexts.\n * Create contexts for requests that don't come from Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {admin} = await shopify.unauthenticated.admin(shop);\n * const response = admin.graphql(`{ shop { currencyCode } }`)\n *\n * return json(await response.json());\n * }\n * ```\n */\n unauthenticated: Unauthenticated>;\n}" }, - "BillingConfigOneTimePlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigOneTimePlan", + "ShopifyAppLogin": { + "filePath": "src/server/types.ts", + "name": "ShopifyAppLogin", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "Amount to charge for this plan." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "currencyCode", - "value": "string", - "description": "Currency code for this plan." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "interval", - "value": "BillingInterval.OneTime", - "description": "Interval for this plan.\n\nMust be set to `OneTime`." + "name": "login", + "value": "Login", + "description": "Log a merchant in, and redirect them to the app root. Will redirect the merchant to authentication if a shop is present in the URL search parameters or form data.\n\nThis function won't be present when the `distribution` config option is set to `AppDistribution.ShopifyAdmin`, because Admin apps aren't allowed to show a login page.", + "examples": [ + { + "title": "Creating a login page", + "description": "Use `shopify.login` to create a login form, in a route that can handle GET and POST requests.", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const errors = shopify.login(request);\n\n return json(errors);\n}\n\nexport async function action({ request }: ActionFunctionArgs) {\n const errors = shopify.login(request);\n\n return json(errors);\n}\n\nexport default function Auth() {\n const actionData = useActionData();\n const [shop, setShop] = useState(\"\");\n\n return (\n \n \n
\n \n \n Login\n \n \n \n \n
\n
\n
\n );\n}", + "title": "/app/routes/auth/login.tsx" + } + ] + } + ] } ], - "value": "export interface BillingConfigOneTimePlan extends BillingConfigPlan {\n /**\n * Interval for this plan.\n *\n * Must be set to `OneTime`.\n */\n interval: BillingInterval.OneTime;\n}" + "value": "export interface ShopifyAppLogin {\n /**\n * Log a merchant in, and redirect them to the app root. Will redirect the merchant to authentication if a shop is\n * present in the URL search parameters or form data.\n *\n * This function won't be present when the `distribution` config option is set to `AppDistribution.ShopifyAdmin`,\n * because Admin apps aren't allowed to show a login page.\n *\n * @example\n * Creating a login page.\n * Use `shopify.login` to create a login form, in a route that can handle GET and POST requests.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/auth/login.tsx\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const errors = shopify.login(request);\n *\n * return json(errors);\n * }\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const errors = shopify.login(request);\n *\n * return json(errors);\n * }\n *\n * export default function Auth() {\n * const actionData = useActionData();\n * const [shop, setShop] = useState(\"\");\n *\n * return (\n * \n * \n *
\n * \n * \n * Login\n * \n * \n * \n * \n *
\n *
\n *
\n * );\n * }\n * ```\n */\n login: Login;\n}" }, - "BillingConfigSubscriptionLineItemPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionLineItemPlan", + "Login": { + "filePath": "src/server/types.ts", + "name": "Login", "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "lineItems", - "value": "(BillingConfigRecurringLineItem | BillingConfigUsageLineItem)[]", - "description": "The line items for this plan." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "replacementBehavior", - "value": "BillingReplacementBehavior", - "description": "The replacement behavior to use for this plan.", - "isOptional": true - }, + "params": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "trialDays", - "value": "number", - "description": "How many trial days to give before charging for this plan.", - "isOptional": true + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/types.ts" } ], - "value": "export interface BillingConfigSubscriptionLineItemPlan {\n /**\n * The replacement behavior to use for this plan.\n */\n replacementBehavior?: BillingReplacementBehavior;\n /**\n * How many trial days to give before charging for this plan.\n */\n trialDays?: number;\n /**\n * The line items for this plan.\n */\n lineItems: (BillingConfigRecurringLineItem | BillingConfigUsageLineItem)[];\n}" + "returns": { + "filePath": "src/server/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "type Login = (request: Request) => Promise;" }, - "BillingConfigRecurringLineItem": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigRecurringLineItem", + "LoginError": { + "filePath": "src/server/types.ts", + "name": "LoginError", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "The amount to charge for this line item." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "currencyCode", - "value": "string", - "description": "The currency code for this line item." - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "discount", - "value": "BillingConfigSubscriptionPlanDiscount", - "description": "An optional discount to apply for this line item.", + "name": "shop", + "value": "LoginErrorType", + "description": "", "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "interval", - "value": "BillingInterval.Every30Days | BillingInterval.Annual", - "description": "The recurring interval for this line item.\n\nMust be either `Every30Days` or `Annual`." } ], - "value": "export interface BillingConfigRecurringLineItem extends BillingConfigLineItem {\n /**\n * The recurring interval for this line item.\n *\n * Must be either `Every30Days` or `Annual`.\n */\n interval: BillingInterval.Every30Days | BillingInterval.Annual;\n /**\n * An optional discount to apply for this line item.\n */\n discount?: BillingConfigSubscriptionPlanDiscount;\n}" + "value": "export interface LoginError {\n shop?: LoginErrorType;\n}" }, - "BillingConfigSubscriptionPlanDiscount": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionPlanDiscount", - "description": "", + "LoginErrorType": { + "filePath": "src/server/types.ts", + "syntaxKind": "EnumDeclaration", + "name": "LoginErrorType", + "value": "export enum LoginErrorType {\n MissingShop = 'MISSING_SHOP',\n InvalidShop = 'INVALID_SHOP',\n}", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "durationLimitInIntervals", - "value": "number", - "description": "The number of intervals to apply the discount for.", - "isOptional": true + "filePath": "src/server/types.ts", + "name": "MissingShop", + "value": "MISSING_SHOP" }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "value", - "value": "BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage", - "description": "The discount to apply." + "filePath": "src/server/types.ts", + "name": "InvalidShop", + "value": "INVALID_SHOP" } - ], - "value": "export interface BillingConfigSubscriptionPlanDiscount {\n /**\n * The number of intervals to apply the discount for.\n */\n durationLimitInIntervals?: number;\n /**\n * The discount to apply.\n */\n value: BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage;\n}" + ] }, - "BillingConfigUsageLineItem": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigUsageLineItem", + "AppStoreApp": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AppStoreApp", + "value": "ShopifyAppBase & ShopifyAppLogin", + "description": "" + }, + "AppConfigArg": { + "filePath": "src/server/config-types.ts", + "name": "AppConfigArg", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "The capped amount or the maximum amount to be charged in the interval." + "name": "_logDisabledFutureFlags", + "value": "boolean", + "description": "Whether to log disabled future flags at startup.", + "isOptional": true, + "isPrivate": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "currencyCode", + "name": "adminApiAccessToken", "value": "string", - "description": "The currency code for this line item." + "description": "An app-wide API access token.\n\nOnly applies to custom apps.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "interval", - "value": "BillingInterval.Usage", - "description": "The usage interval for this line item.\n\nMust be set to `Usage`." + "name": "apiKey", + "value": "string", + "description": "The API key for your app.\n\nAlso known as Client ID in your Partner Dashboard.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "terms", + "name": "apiSecretKey", "value": "string", - "description": "Usage terms for this line item." - } - ], - "value": "export interface BillingConfigUsageLineItem extends BillingConfigLineItem {\n /**\n * The usage interval for this line item.\n *\n * Must be set to `Usage`.\n */\n interval: BillingInterval.Usage;\n /**\n * The capped amount or the maximum amount to be charged in the interval.\n */\n amount: number;\n /**\n * Usage terms for this line item.\n */\n terms: string;\n}" - }, - "BillingReplacementBehavior": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "BillingReplacementBehavior", - "value": "export declare enum BillingReplacementBehavior {\n ApplyImmediately = \"APPLY_IMMEDIATELY\",\n ApplyOnNextBillingCycle = \"APPLY_ON_NEXT_BILLING_CYCLE\",\n Standard = \"STANDARD\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "ApplyImmediately", - "value": "APPLY_IMMEDIATELY" + "description": "The API secret key for your app.\n\nAlso known as Client Secret in your Partner Dashboard." }, { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "ApplyOnNextBillingCycle", - "value": "APPLY_ON_NEXT_BILLING_CYCLE" + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "What version of Shopify's Admin API's would you like to use.\n\n\n\n\n", + "isOptional": true, + "defaultValue": "`LATEST_API_VERSION` from `@shopify/shopify-app-remix`", + "examples": [ + { + "title": "Using the latest API Version (Recommended)", + "description": "", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n apiVersion: LATEST_API_VERSION,\n});", + "title": "Example" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Standard", - "value": "STANDARD" - } - ] - }, - "BillingConfigLegacyItem": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "BillingConfigLegacyItem", - "value": "BillingConfigOneTimePlan | BillingConfigSubscriptionPlan | BillingConfigUsagePlan", - "description": "" - }, - "BillingConfigSubscriptionPlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigSubscriptionPlan", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "Amount to charge for this plan." + "name": "appUrl", + "value": "string", + "description": "The URL your app is running on.\n\nThe `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL." }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "currencyCode", + "name": "authPathPrefix", "value": "string", - "description": "Currency code for this plan." + "description": "A path that Shopify can reserve for auth related endpoints.\n\nThis must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.", + "isOptional": true, + "defaultValue": "`\"/auth\"`", + "examples": [ + { + "title": "Using the latest API Version (Recommended)", + "description": "", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n apiVersion: LATEST_API_VERSION,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;\n\n// /app/routes/auth/$.jsx\nimport { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n await authenticate.admin(request);\n\n return null\n}", + "title": "/app/shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "discount", - "value": "BillingConfigSubscriptionPlanDiscount", - "description": "The discount to apply to this plan.", + "name": "billing", + "value": "BillingConfig", + "description": "Billing configurations for the app.", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "customShopDomains", + "value": "(string | RegExp)[]", + "description": "Override values for Shopify shop domains.", "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "interval", - "value": "Exclude", - "description": "Recurring interval for this plan.\n\nMust be either `Every30Days` or `Annual`." + "name": "distribution", + "value": "AppDistribution", + "description": "How your app is distributed. Default is `AppDistribution.AppStore`.\n\nAppStore should be used for public apps that are distributed in the Shopify App Store. SingleMerchant should be used for custom apps managed in the Partner Dashboard. ShopifyAdmin should be used for apps that are managed in the merchant's Shopify Admin.\n\n\n\n\n", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "replacementBehavior", - "value": "BillingReplacementBehavior", - "description": "The behavior to use when replacing an existing subscription with a new one.", + "name": "future", + "value": "Future", + "description": "Features that will be introduced in future releases of this package.\n\nYou can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future releases in advance and provide feedback on the new features.", "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "trialDays", - "value": "number", - "description": "How many trial days to give before charging for this plan.", - "isOptional": true - } - ], - "value": "export interface BillingConfigSubscriptionPlan extends BillingConfigPlan {\n /**\n * Recurring interval for this plan.\n *\n * Must be either `Every30Days` or `Annual`.\n */\n interval: Exclude;\n /**\n * How many trial days to give before charging for this plan.\n */\n trialDays?: number;\n /**\n * The behavior to use when replacing an existing subscription with a new one.\n */\n replacementBehavior?: BillingReplacementBehavior;\n /**\n * The discount to apply to this plan.\n */\n discount?: BillingConfigSubscriptionPlanDiscount;\n}" - }, - "RecurringBillingIntervals": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RecurringBillingIntervals", - "value": "Exclude", - "description": "" - }, - "BillingConfigUsagePlan": { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", - "name": "BillingConfigUsagePlan", - "description": "", - "members": [ + "name": "hooks", + "value": "HooksConfig, Resources>", + "description": "Functions to call at key places during your apps lifecycle.\n\nThese functions are called in the context of the request that triggered them. This means you can access the session.", + "isOptional": true, + "examples": [ + { + "title": "Seeding your database custom data when a merchant installs your app", + "description": "", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { seedStoreData } from \"~/db/seeds\"\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n seedStoreData({session})\n }\n },\n // ...etc\n});", + "title": "Example" + } + ] + } + ] + }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "amount", - "value": "number", - "description": "Amount to charge for this plan." + "name": "isEmbeddedApp", + "value": "boolean", + "description": "Does your app render embedded inside the Shopify Admin or on its own.\n\nUnless you have very specific needs, this should be true.", + "isOptional": true, + "defaultValue": "`true`" }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "currencyCode", - "value": "string", - "description": "Currency code for this plan." + "name": "isTesting", + "value": "boolean", + "description": "Whether the app is initialised for local testing.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "interval", - "value": "BillingInterval.Usage", - "description": "Interval for this plan.\n\nMust be set to `Usage`." + "name": "logger", + "value": "{ log?: LogFunction; level?: LogSeverity; httpRequests?: boolean; timestamps?: boolean; }", + "description": "Customization options for Shopify logs.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "replacementBehavior", - "value": "BillingReplacementBehavior", - "description": "The behavior to use when replacing an existing subscription with a new one.", + "name": "privateAppStorefrontAccessToken", + "value": "string", + "description": "An app-wide API access token for the storefront API.\n\nOnly applies to custom apps.", "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "trialDays", - "value": "number", - "description": "How many trial days to give before charging for this plan.", + "name": "restResources", + "value": "Resources", + "description": "REST resources to access the Admin API.\n\nYou can import these from `@shopify/shopify-api/rest/admin/*`.", "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "usageTerms", - "value": "string", - "description": "Usage terms for this plan." - } - ], - "value": "export interface BillingConfigUsagePlan extends BillingConfigPlan {\n /**\n * Interval for this plan.\n *\n * Must be set to `Usage`.\n */\n interval: BillingInterval.Usage;\n /**\n * Usage terms for this plan.\n */\n usageTerms: string;\n /**\n * How many trial days to give before charging for this plan.\n */\n trialDays?: number;\n /**\n * The behavior to use when replacing an existing subscription with a new one.\n */\n replacementBehavior?: BillingReplacementBehavior;\n}" - }, - "HooksConfig": { - "filePath": "src/server/config-types.ts", - "name": "HooksConfig", - "description": "", - "members": [ + "name": "scopes", + "value": "string[] | AuthScopes", + "description": "The scopes your app needs to access the API. Not required if using Shopify managed installation.", + "isOptional": true + }, { "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "afterAuth", - "value": "(options: AfterAuthOptions) => void | Promise", - "description": "A function to call after a merchant installs your app", + "name": "sessionStorage", + "value": "Storage", + "description": "An adaptor for storing sessions in your database of choice.\n\nShopify provides multiple session storage adaptors and you can create your own.\n\nOptional for apps created in the Shopify Admin.\n\n\n\n\n", "isOptional": true, "examples": [ { - "title": "Seeding data when a merchant installs your app", - "description": "", + "title": "Storing sessions with Prisma", + "description": "Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.", "tabs": [ { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { seedStoreData } from \"~/db/seeds\"\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n seedStoreData({session})\n }\n },\n // ...etc\n});", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n // ... etc\n sessionStorage: new PrismaSessionStorage(prisma),\n});\nexport default shopify;", "title": "Example" } ] } ] - } - ], - "value": "interface HooksConfig {\n /**\n * A function to call after a merchant installs your app\n *\n * @param context - An object with context about the request that triggered the hook.\n * @param context.session - The session of the merchant that installed your app. This is the output of sessionStorage.loadSession in case people want to load their own.\n * @param context.admin - An object with access to the Shopify Admin API's.\n *\n * @example\n * Seeding data when a merchant installs your app.\n * ```ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { seedStoreData } from \"~/db/seeds\"\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * seedStoreData({session})\n * }\n * },\n * // ...etc\n * });\n * ```\n */\n afterAuth?: (options: AfterAuthOptions) => void | Promise;\n}" - }, - "AfterAuthOptions": { - "filePath": "src/server/config-types.ts", - "name": "AfterAuthOptions", - "description": "", - "members": [ + }, { "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "" + "name": "useOnlineTokens", + "value": "boolean", + "description": "Whether your app use online or offline tokens.\n\nIf your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.\n\n\n\n\n", + "isOptional": true, + "defaultValue": "`false`" }, { "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "" - } - ], - "value": "export interface AfterAuthOptions<\n R extends ShopifyRestResources = ShopifyRestResources,\n> {\n session: Session;\n admin: AdminApiContext;\n}" - }, - "LogFunction": { - "filePath": "../shopify-api/dist/ts/lib/base-types.d.ts", - "name": "LogFunction", - "description": "A function used by the library to log events related to Shopify.", - "params": [ - { - "name": "severity", - "description": "", - "value": "LogSeverity", - "filePath": "../shopify-api/dist/ts/lib/base-types.d.ts" + "name": "userAgentPrefix", + "value": "string", + "description": "The user agent prefix to use for API requests.", + "isOptional": true }, { - "name": "msg", - "description": "", - "value": "string", - "filePath": "../shopify-api/dist/ts/lib/base-types.d.ts" + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "webhooks", + "value": "WebhookConfig", + "description": "The config for the shop-specific webhooks your app needs.\n\nUse this to configure shop-specific webhooks. In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n\n\n\n\n\n\n\nYou should only use this if you need shop-specific webhooks. If you do need shop-specific webhooks this can be in used in conjunction with the afterAuth hook, loaders or processes such as background jobs.", + "isOptional": true, + "examples": [ + { + "title": "Registering shop-specific webhooks", + "description": "", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // But you could wrap this in some custom shop specific conditional logic\n shopify.registerWebhooks({ session });\n }\n },\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "app/shopify.server.ts" + } + ] + }, + { + "title": "Registering app-specific webhooks (Recommended)", + "description": "", + "tabs": [ + { + "code": "# shopify.app.toml\n[webhooks]\napi_version = \"2024-07\"\n\n [[webhooks.subscriptions]]\n topics = [\"products/create\"]\n uri = \"/webhooks/products/create\"", + "title": "Example" + } + ] + }, + { + "title": "Authenticating a webhook request", + "description": "", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic, shop, session, payload } = await authenticate.webhook(request);\n\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (!session) {\n throw new Response();\n }\n\n // Handle the webhook\n console.log(`${TOPIC} webhook received with`, JSON.stringify(payload))\n\n throw new Response();\n};", + "title": "/app/routes/webhooks.ts" + } + ] + } + ] } ], - "returns": { - "filePath": "../shopify-api/dist/ts/lib/base-types.d.ts", - "description": "", - "name": "void", - "value": "void" - }, - "value": "export type LogFunction = (severity: LogSeverity, msg: string) => void;" + "value": "export interface AppConfigArg<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n Storage extends SessionStorage = SessionStorage,\n Future extends FutureFlagOptions = FutureFlagOptions,\n> extends Omit<\n ApiConfigArg>,\n | 'hostName'\n | 'hostScheme'\n | 'isEmbeddedApp'\n | 'apiVersion'\n | 'isCustomStoreApp'\n | 'future'\n > {\n /**\n * The URL your app is running on.\n *\n * The `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL.\n */\n appUrl: string;\n\n /**\n * An adaptor for storing sessions in your database of choice.\n *\n * Shopify provides multiple session storage adaptors and you can create your own.\n *\n * Optional for apps created in the Shopify Admin.\n *\n * {@link https://github.com/Shopify/shopify-app-js/blob/main/README.md#session-storage-options}\n *\n * @example\n * Storing sessions with Prisma.\n * Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.\n * ```ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n *\n * import prisma from \"~/db.server\";\n *\n * const shopify = shopifyApp({\n * // ... etc\n * sessionStorage: new PrismaSessionStorage(prisma),\n * });\n * export default shopify;\n * ```\n */\n sessionStorage?: Storage;\n\n /**\n * Whether your app use online or offline tokens.\n *\n * If your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/access-modes}\n *\n * @defaultValue `false`\n */\n useOnlineTokens?: boolean;\n\n /**\n * The config for the shop-specific webhooks your app needs.\n * \n * Use this to configure shop-specific webhooks. In many cases defining app-specific webhooks in the `shopify.app.toml` will be sufficient and easier to manage. Please see:\n * \n * {@link https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions}\n * \n * You should only use this if you need shop-specific webhooks. If you do need shop-specific webhooks this can be in used in conjunction with the afterAuth hook, loaders or processes such as background jobs.\n *\n * @example\n * Registering shop-specific webhooks.\n * ```ts\n * // app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * PRODUCTS_CREATE: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks/products/create\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * // Register webhooks for the shop\n * // In this example, every shop will have these webhooks\n * // But you could wrap this in some custom shop specific conditional logic\n * shopify.registerWebhooks({ session });\n * }\n * },\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * \n * @example\n * Registering app-specific webhooks (Recommended)\n * ```toml\n * # shopify.app.toml\n * [webhooks]\n * api_version = \"2024-07\"\n\n * [[webhooks.subscriptions]]\n * topics = [\"products/create\"]\n * uri = \"/webhooks/products/create\"\n * \n * ```\n * \n * @example\n * Authenticating a webhook request\n *\n * ```ts\n * // /app/routes/webhooks.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import db from \"../db.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { topic, shop, session, payload } = await authenticate.webhook(request);\n * \n * // Webhook requests can trigger after an app is uninstalled\n * // If the app is already uninstalled, the session may be undefined.\n * if (!session) {\n * throw new Response();\n * }\n * \n * // Handle the webhook\n * console.log(`${TOPIC} webhook received with`, JSON.stringify(payload))\n *\n * throw new Response();\n * };\n * ```\n */\n webhooks?: WebhookConfig;\n\n /**\n * Functions to call at key places during your apps lifecycle.\n *\n * These functions are called in the context of the request that triggered them. This means you can access the session.\n *\n * @example\n * Seeding your database custom data when a merchant installs your app.\n * ```ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { seedStoreData } from \"~/db/seeds\"\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * seedStoreData({session})\n * }\n * },\n * // ...etc\n * });\n * ```\n */\n hooks?: HooksConfig, Resources>;\n\n /**\n * Does your app render embedded inside the Shopify Admin or on its own.\n *\n * Unless you have very specific needs, this should be true.\n *\n * @defaultValue `true`\n */\n isEmbeddedApp?: boolean;\n\n /**\n * How your app is distributed. Default is `AppDistribution.AppStore`.\n *\n * AppStore should be used for public apps that are distributed in the Shopify App Store.\n * SingleMerchant should be used for custom apps managed in the Partner Dashboard.\n * ShopifyAdmin should be used for apps that are managed in the merchant's Shopify Admin.\n *\n * {@link https://shopify.dev/docs/apps/distribution}\n */\n distribution?: AppDistribution;\n\n /**\n * What version of Shopify's Admin API's would you like to use.\n *\n * {@link https://shopify.dev/docs/api/}\n *\n * @defaultValue `LATEST_API_VERSION` from `@shopify/shopify-app-remix`\n *\n * @example\n * Using the latest API Version (Recommended)\n * ```ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * apiVersion: LATEST_API_VERSION,\n * });\n * ```\n */\n apiVersion?: ApiVersion;\n\n /**\n * A path that Shopify can reserve for auth related endpoints.\n *\n * This must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.\n *\n * @default `\"/auth\"`\n *\n * @example\n * Using the latest API Version (Recommended)\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * apiVersion: LATEST_API_VERSION,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n *\n * // /app/routes/auth/$.jsx\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * await authenticate.admin(request);\n *\n * return null\n * }\n * ```\n */\n authPathPrefix?: string;\n\n /**\n * Features that will be introduced in future releases of this package.\n *\n * You can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future\n * releases in advance and provide feedback on the new features.\n */\n future?: Future;\n}" }, - "LogSeverity": { + "ApiVersion": { "filePath": "../shopify-api/dist/ts/lib/types.d.ts", "syntaxKind": "EnumDeclaration", - "name": "LogSeverity", - "value": "export declare enum LogSeverity {\n Error = 0,\n Warning = 1,\n Info = 2,\n Debug = 3\n}", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n April24 = \"2024-04\",\n July24 = \"2024-07\",\n October24 = \"2024-10\",\n Unstable = \"unstable\"\n}", "members": [ { "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Error", - "value": 0 + "name": "October22", + "value": "2022-10" }, { "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Warning", - "value": 1 + "name": "January23", + "value": "2023-01" }, { "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Info", - "value": 2 + "name": "April23", + "value": "2023-04" }, { "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Debug", - "value": 3 + "name": "July23", + "value": "2023-07" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "October23", + "value": "2023-10" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "January24", + "value": "2024-01" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "April24", + "value": "2024-04" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "July24", + "value": "2024-07" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "October24", + "value": "2024-10" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" } ] }, - "WebhookConfig": { - "filePath": "src/server/config-types.ts", + "BillingConfig": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfig", + "description": "Billing configuration options, indexed by an app-specific plan name.", + "members": [ + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "[plan: string]", + "value": "BillingConfigItem" + } + ], + "value": "export interface BillingConfig {\n /**\n * An individual billing plan.\n */\n [plan: string]: BillingConfigItem;\n}" + }, + "BillingConfigItem": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "WebhookConfig", - "value": "Record", - "description": "", - "members": [] - } - } - }, - { - "title": "Future flags", - "description": "Set future flags using the `future` configuration field to opt in to upcoming breaking changes.\n\nWith this feature, you can prepare for major releases ahead of time, as well as try out new features before they are released.", - "type": "FutureFlags", - "typeDefinitions": { - "FutureFlags": { - "filePath": "src/server/future/flags.ts", - "name": "FutureFlags", + "name": "BillingConfigItem", + "value": "FeatureEnabled extends true ? BillingConfigOneTimePlan | BillingConfigSubscriptionLineItemPlan : BillingConfigLegacyItem", + "description": "" + }, + "BillingConfigOneTimePlan": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigOneTimePlan", "description": "", "members": [ { - "filePath": "src/server/future/flags.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "unstable_newEmbeddedAuthStrategy", - "value": "boolean", - "description": "When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange). This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n\nLearn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).", - "isOptional": true, - "defaultValue": "false" + "name": "amount", + "value": "number", + "description": "Amount to charge for this plan." }, { - "filePath": "src/server/future/flags.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "wip_optionalScopesApi", - "value": "boolean", - "description": "When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.", - "isOptional": true, - "defaultValue": "false" + "name": "currencyCode", + "value": "string", + "description": "Currency code for this plan." + }, + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.OneTime", + "description": "Interval for this plan.\n\nMust be set to `OneTime`." } ], - "value": "export interface FutureFlags {\n /**\n * When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).\n * This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n *\n * Learn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).\n *\n * @default false\n */\n unstable_newEmbeddedAuthStrategy?: boolean;\n\n /**\n * When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.\n *\n * @default false\n */\n wip_optionalScopesApi?: boolean;\n}" - } - } - } - ], - "jsDocTypeExamples": [ - "ShopifyAppGeneratedType", - "ShopifyAppBase", - "ShopifyAppLogin" - ], - "related": [ - { - "name": "Authenticated contexts", - "subtitle": "Authenticate requests coming from Shopify.", - "url": "/docs/api/shopify-app-remix/authenticate" - }, - { - "name": "Unauthenticated contexts", - "subtitle": "Interact with the API on non-Shopify requests.", - "url": "/docs/api/shopify-app-remix/unauthenticated" - } - ], - "defaultExample": { - "description": "", - "codeblock": { - "title": "The minimum viable configuration", - "tabs": [ - { - "title": "/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n apiKey: process.env.SHOPIFY_API_KEY!,\n apiSecretKey: process.env.SHOPIFY_API_SECRET!,\n scopes: process.env.SCOPES?.split(\",\")!,\n appUrl: process.env.SHOPIFY_APP_URL!,\n});\nexport default shopify;", - "language": "typescript" - } - ] - } - }, - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "addDocumentResponseHeaders", - "examples": [ - { - "description": "Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.", - "codeblock": { - "title": "Return headers on all requests", - "tabs": [ - { - "title": "~/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const addDocumentResponseheaders = shopify.addDocumentResponseheaders;", - "language": "typescript" - }, - { - "title": "entry.server.tsx", - "code": "import { addDocumentResponseHeaders } from \"~/shopify.server\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const markup = renderToString(\n <RemixServer context={remixContext} url={request.url} />\n );\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n addDocumentResponseHeaders(request, responseHeaders);\n\n return new Response(\"<!DOCTYPE html>\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "authenticate", - "examples": [ - { - "description": "Use the functions in `authenticate` to validate requests coming from Shopify.", - "codeblock": { - "title": "Authenticate Shopify requests", - "tabs": [ - { - "title": "/app/shopify.server.ts", - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", - "language": "typescript" - }, - { - "title": "/app/routes/**\\/*.jsx", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "registerWebhooks", - "examples": [ - { - "description": "Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)", - "codeblock": { - "title": "Registering shop-specific webhooks after install", - "tabs": [ - { - "title": "app/shopify.server.ts", - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "sessionStorage", - "examples": [ - { - "description": "Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.", - "codeblock": { - "title": "Storing sessions with Prisma", - "tabs": [ - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n sessionStorage: new PrismaSessionStorage(prisma),\n // ...etc\n})\n\n// shopify.sessionStorage is an instance of PrismaSessionStorage", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "unauthenticated", - "examples": [ - { - "description": "Create contexts for requests that don't come from Shopify.", - "codeblock": { - "title": "Using unauthenticated contexts", - "tabs": [ - { - "title": "/app/shopify.server.ts", - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", - "language": "typescript" - }, - { - "title": "/app/routes/**\\/*.jsx", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "login", - "examples": [ - { - "description": "Use `shopify.login` to create a login form, in a route that can handle GET and POST requests.", - "codeblock": { - "title": "Creating a login page", - "tabs": [ - { - "title": "/app/shopify.server.ts", - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", - "language": "typescript" - }, - { - "title": "/app/routes/auth/login.tsx", - "code": "import shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const errors = shopify.login(request);\n\n return json(errors);\n}\n\nexport async function action({ request }: ActionFunctionArgs) {\n const errors = shopify.login(request);\n\n return json(errors);\n}\n\nexport default function Auth() {\n const actionData = useActionData<typeof action>();\n const [shop, setShop] = useState(\"\");\n\n return (\n <Page>\n <Card>\n <Form method=\"post\">\n <FormLayout>\n <Text variant=\"headingMd\" as=\"h2\">\n Login\n </Text>\n <TextField\n type=\"text\"\n name=\"shop\"\n label=\"Shop domain\"\n helpText=\"e.g: my-shop-domain.myshopify.com\"\n value={shop}\n onChange={setShop}\n autoComplete=\"on\"\n error={actionData?.errors.shop}\n />\n <Button submit primary>\n Submit\n </Button>\n </FormLayout>\n </Form>\n </Card>\n </Page>\n );\n}", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Unauthenticated admin", - "description": "Allows interacting with the Admin API when working outside of Shopify requests.\nThis enables apps to integrate with 3rd party services and perform background tasks.\n\n> Caution:\n> This function doesn't perform **any** validation and shouldn't rely on raw user input.\n\nWhen using this function, consider the following:\n\n#### Background tasks\n\nApps should ensure that the shop domain is authenticated when enqueueing jobs.\n\n#### 3rd party service requests\n\nApps must obtain the shop domain from the 3rd party service in a secure way.", - "category": "Unauthenticated", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "unauthenticated.admin", - "description": "Creates an unauthenticated Admin context.", - "type": "GetUnauthenticatedAdminContext", - "typeDefinitions": { - "GetUnauthenticatedAdminContext": { - "filePath": "src/server/unauthenticated/admin/types.ts", - "name": "GetUnauthenticatedAdminContext", - "description": "", - "params": [ + "value": "export interface BillingConfigOneTimePlan extends BillingConfigPlan {\n /**\n * Interval for this plan.\n *\n * Must be set to `OneTime`.\n */\n interval: BillingInterval.OneTime;\n}" + }, + "BillingInterval": { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "BillingInterval", + "value": "export declare enum BillingInterval {\n OneTime = \"ONE_TIME\",\n Every30Days = \"EVERY_30_DAYS\",\n Annual = \"ANNUAL\",\n Usage = \"USAGE\"\n}", + "members": [ { - "name": "shop", - "description": "", - "value": "string", - "filePath": "src/server/unauthenticated/admin/types.ts" + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "OneTime", + "value": "ONE_TIME" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Every30Days", + "value": "EVERY_30_DAYS" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Annual", + "value": "ANNUAL" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Usage", + "value": "USAGE" } - ], - "returns": { - "filePath": "src/server/unauthenticated/admin/types.ts", - "description": "", - "name": "Promise>", - "value": "Promise>" - }, - "value": "export type GetUnauthenticatedAdminContext<\n Resources extends ShopifyRestResources,\n> = (shop: string) => Promise>;" + ] }, - "UnauthenticatedAdminContext": { - "filePath": "src/server/unauthenticated/admin/types.ts", - "name": "UnauthenticatedAdminContext", + "BillingConfigSubscriptionLineItemPlan": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionLineItemPlan", "description": "", "members": [ { - "filePath": "src/server/unauthenticated/admin/types.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the given store.", - "examples": [ - { - "title": "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", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await unauthenticated.admin(request);\n\n const response = await admin.rest.get(\n {\n path: \"/customers/count.json\"\n }\n );\n const customers = await response.json();\n\n return json({ customers });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\n\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", - "title": "/app/shopify.server.ts" - } - ] - }, - { - "title": "Querying the GraphQL API", - "description": "Use `admin.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await unauthenticated.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "name": "lineItems", + "value": "(BillingConfigRecurringLineItem | BillingConfigUsageLineItem)[]", + "description": "The line items for this plan." }, { - "filePath": "src/server/unauthenticated/admin/types.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the given shop.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nThis will always be an offline session. You can use to get shop-specific data.", - "examples": [ - { - "title": "Using the offline session", - "description": "Get your app's shop-specific data using the returned offline `session` object.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { session } = await unauthenticated.admin(shop);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - } - ] + "name": "replacementBehavior", + "value": "BillingReplacementBehavior", + "description": "The replacement behavior to use for this plan.", + "isOptional": true + }, + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "trialDays", + "value": "number", + "description": "How many trial days to give before charging for this plan.", + "isOptional": true } ], - "value": "export interface UnauthenticatedAdminContext<\n Resources extends ShopifyRestResources,\n> {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use to get shop-specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.admin(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the given store.\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await unauthenticated.admin(request);\n *\n * const response = await admin.rest.get(\n * {\n * path: \"/customers/count.json\"\n * }\n * );\n * const customers = await response.json();\n *\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n *\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await unauthenticated.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n */\n admin: AdminApiContext;\n}" + "value": "export interface BillingConfigSubscriptionLineItemPlan {\n /**\n * The replacement behavior to use for this plan.\n */\n replacementBehavior?: BillingReplacementBehavior;\n /**\n * How many trial days to give before charging for this plan.\n */\n trialDays?: number;\n /**\n * The line items for this plan.\n */\n lineItems: (BillingConfigRecurringLineItem | BillingConfigUsageLineItem)[];\n}" }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "BillingConfigRecurringLineItem": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigRecurringLineItem", + "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "The amount to charge for this line item." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "currencyCode", "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." + "description": "The currency code for this line item." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "discount", + "value": "BillingConfigSubscriptionPlanDiscount", + "description": "An optional discount to apply for this line item.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.Every30Days | BillingInterval.Annual", + "description": "The recurring interval for this line item.\n\nMust be either `Every30Days` or `Annual`." + } + ], + "value": "export interface BillingConfigRecurringLineItem extends BillingConfigLineItem {\n /**\n * The recurring interval for this line item.\n *\n * Must be either `Every30Days` or `Annual`.\n */\n interval: BillingInterval.Every30Days | BillingInterval.Annual;\n /**\n * An optional discount to apply for this line item.\n */\n discount?: BillingConfigSubscriptionPlanDiscount;\n}" + }, + "BillingConfigSubscriptionPlanDiscount": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscount", + "description": "", + "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "durationLimitInIntervals", + "value": "number", + "description": "The number of intervals to apply the discount for.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." - }, + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "value", + "value": "BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage", + "description": "The discount to apply." + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscount {\n /**\n * The number of intervals to apply the discount for.\n */\n durationLimitInIntervals?: number;\n /**\n * The discount to apply.\n */\n value: BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage;\n}" + }, + "BillingConfigSubscriptionPlanDiscountAmount": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscountAmount", + "description": "", + "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", - "value": "string", - "description": "The access token for the session." + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "The amount to discount.\n\nCannot be set if `percentage` is set." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "percentage", + "value": "never", + "description": "The percentage to discount.\n\nCannot be set if `amount` is set.", + "isOptional": true + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscountAmount {\n /**\n * The amount to discount.\n *\n * Cannot be set if `percentage` is set.\n */\n amount: number;\n /**\n * The percentage to discount.\n *\n * Cannot be set if `amount` is set.\n */\n percentage?: never;\n}" + }, + "BillingConfigSubscriptionPlanDiscountPercentage": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscountPercentage", + "description": "", + "members": [ + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "never", + "description": "The amount to discount.\n\nCannot be set if `percentage` is set.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "percentage", + "value": "number", + "description": "The percentage to discount.\n\nCannot be set if `amount` is set." + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscountPercentage {\n /**\n * The amount to discount.\n *\n * Cannot be set if `percentage` is set.\n */\n amount?: never;\n /**\n * The percentage to discount.\n *\n * Cannot be set if `amount` is set.\n */\n percentage: number;\n}" + }, + "BillingConfigUsageLineItem": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigUsageLineItem", + "description": "", + "members": [ + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "The capped amount or the maximum amount to be charged in the interval." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "currencyCode", + "value": "string", + "description": "The currency code for this line item." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.Usage", + "description": "The usage interval for this line item.\n\nMust be set to `Usage`." }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "terms", + "value": "string", + "description": "Usage terms for this line item." + } + ], + "value": "export interface BillingConfigUsageLineItem extends BillingConfigLineItem {\n /**\n * The usage interval for this line item.\n *\n * Must be set to `Usage`.\n */\n interval: BillingInterval.Usage;\n /**\n * The capped amount or the maximum amount to be charged in the interval.\n */\n amount: number;\n /**\n * Usage terms for this line item.\n */\n terms: string;\n}" + }, + "BillingReplacementBehavior": { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "BillingReplacementBehavior", + "value": "export declare enum BillingReplacementBehavior {\n ApplyImmediately = \"APPLY_IMMEDIATELY\",\n ApplyOnNextBillingCycle = \"APPLY_ON_NEXT_BILLING_CYCLE\",\n Standard = \"STANDARD\"\n}", + "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "ApplyImmediately", + "value": "APPLY_IMMEDIATELY" }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "ApplyOnNextBillingCycle", + "value": "APPLY_ON_NEXT_BILLING_CYCLE" }, { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Standard", + "value": "STANDARD" } - ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" + ] }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", + "BillingConfigLegacyItem": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingConfigLegacyItem", + "value": "BillingConfigOneTimePlan | BillingConfigSubscriptionPlan | BillingConfigUsagePlan", + "description": "" + }, + "BillingConfigSubscriptionPlan": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlan", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." + "name": "amount", + "value": "number", + "description": "Amount to charge for this plan." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "associated_user_scope", + "name": "currencyCode", "value": "string", - "description": "The effective set of scopes for the session." + "description": "Currency code for this plan." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "expires_in", + "name": "discount", + "value": "BillingConfigSubscriptionPlanDiscount", + "description": "The discount to apply to this plan.", + "isOptional": true + }, + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "Exclude", + "description": "Recurring interval for this plan.\n\nMust be either `Every30Days` or `Annual`." + }, + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "replacementBehavior", + "value": "BillingReplacementBehavior", + "description": "The behavior to use when replacing an existing subscription with a new one.", + "isOptional": true + }, + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "trialDays", "value": "number", - "description": "How long the access token is valid for, in seconds." + "description": "How many trial days to give before charging for this plan.", + "isOptional": true } ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" + "value": "export interface BillingConfigSubscriptionPlan extends BillingConfigPlan {\n /**\n * Recurring interval for this plan.\n *\n * Must be either `Every30Days` or `Annual`.\n */\n interval: Exclude;\n /**\n * How many trial days to give before charging for this plan.\n */\n trialDays?: number;\n /**\n * The behavior to use when replacing an existing subscription with a new one.\n */\n replacementBehavior?: BillingReplacementBehavior;\n /**\n * The discount to apply to this plan.\n */\n discount?: BillingConfigSubscriptionPlanDiscount;\n}" }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", + "RecurringBillingIntervals": { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RecurringBillingIntervals", + "value": "Exclude", + "description": "" + }, + "BillingConfigUsagePlan": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigUsagePlan", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." + "name": "amount", + "value": "number", + "description": "Amount to charge for this plan." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "email", + "name": "currencyCode", "value": "string", - "description": "The user's email address." + "description": "Currency code for this plan." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "email_verified", - "value": "boolean", - "description": "Whether the user has verified their email address." + "name": "interval", + "value": "BillingInterval.Usage", + "description": "Interval for this plan.\n\nMust be set to `Usage`." }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." + "name": "replacementBehavior", + "value": "BillingReplacementBehavior", + "description": "The behavior to use when replacing an existing subscription with a new one.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "id", + "name": "trialDays", "value": "number", - "description": "The user's ID." + "description": "How many trial days to give before charging for this plan.", + "isOptional": true }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "last_name", + "name": "usageTerms", "value": "string", - "description": "The user's last name." + "description": "Usage terms for this plan." + } + ], + "value": "export interface BillingConfigUsagePlan extends BillingConfigPlan {\n /**\n * Interval for this plan.\n *\n * Must be set to `Usage`.\n */\n interval: BillingInterval.Usage;\n /**\n * Usage terms for this plan.\n */\n usageTerms: string;\n /**\n * How many trial days to give before charging for this plan.\n */\n trialDays?: number;\n /**\n * The behavior to use when replacing an existing subscription with a new one.\n */\n replacementBehavior?: BillingReplacementBehavior;\n}" + }, + "HooksConfig": { + "filePath": "src/server/config-types.ts", + "name": "HooksConfig", + "description": "", + "members": [ + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "afterAuth", + "value": "(options: AfterAuthOptions) => void | Promise", + "description": "A function to call after a merchant installs your app", + "isOptional": true, + "examples": [ + { + "title": "Seeding data when a merchant installs your app", + "description": "", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { seedStoreData } from \"~/db/seeds\"\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n seedStoreData({session})\n }\n },\n // ...etc\n});", + "title": "Example" + } + ] + } + ] + } + ], + "value": "interface HooksConfig<\n ConfigArg extends AppConfigArg = AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * A function to call after a merchant installs your app\n *\n * @param context - An object with context about the request that triggered the hook.\n * @param context.session - The session of the merchant that installed your app. This is the output of sessionStorage.loadSession in case people want to load their own.\n * @param context.admin - An object with access to the Shopify Admin API's.\n *\n * @example\n * Seeding data when a merchant installs your app.\n * ```ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { seedStoreData } from \"~/db/seeds\"\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * seedStoreData({session})\n * }\n * },\n * // ...etc\n * });\n * ```\n */\n afterAuth?: (\n options: AfterAuthOptions,\n ) => void | Promise;\n}" + }, + "AfterAuthOptions": { + "filePath": "src/server/config-types.ts", + "name": "AfterAuthOptions", + "description": "", + "members": [ + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "" }, { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", + "filePath": "src/server/config-types.ts", "syntaxKind": "PropertySignature", - "name": "locale", - "value": "string", - "description": "The user's locale." + "name": "session", + "value": "Session", + "description": "" } ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" + "value": "export interface AfterAuthOptions<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n session: Session;\n admin: AdminApiContext;\n}" }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", + "LogSeverity": { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "LogSeverity", + "value": "export declare enum LogSeverity {\n Error = 0,\n Warning = 1,\n Info = 2,\n Debug = 3\n}", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Error", + "value": 0 }, { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Warning", + "value": 1 }, { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Info", + "value": 2 }, { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Debug", + "value": 3 } - ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" + ] }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", + "WebhookConfig": { + "filePath": "src/server/config-types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "WebhookConfig", + "value": "Record", + "description": "", + "members": [] + } + } + }, + { + "title": "Future flags", + "description": "Set future flags using the `future` configuration field to opt in to upcoming breaking changes.\n\nWith this feature, you can prepare for major releases ahead of time, as well as try out new features before they are released.", + "type": "FutureFlags", + "typeDefinitions": { + "FutureFlags": { + "filePath": "src/server/future/flags.ts", + "name": "FutureFlags", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "accessToken", - "value": "string", - "description": "The access token for the session.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." + "name": "removeRest", + "value": "boolean", + "description": "When enabled, methods for interacting with the admin REST API will not be returned.\n\nThis affects:\n\n* `authenticate.admin(request)` * `authenticate.webhook(request)` * `authenticate.flow(request)` * `authenticate.appProxy(request)` * `authenticate.fulfillmentService(request)` * `unauthenticated.admin(shop)`\n\nIn a future release we will remove REST from the package completely.\n\nPlease see: [https://www.shopify.com/ca/partners/blog/all-in-on-graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)", + "isOptional": true, + "defaultValue": "false" }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", - "name": "isOnline", + "name": "unstable_newEmbeddedAuthStrategy", "value": "boolean", - "description": "Whether the access token in the session is online or offline." + "description": "When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange). This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n\nLearn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).", + "isOptional": true, + "defaultValue": "false" }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/future/flags.ts", "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true - }, + "name": "wip_optionalScopesApi", + "value": "boolean", + "description": "When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.", + "isOptional": true, + "defaultValue": "false" + } + ], + "value": "export interface FutureFlags {\n /**\n * When enabled, embedded apps will fetch access tokens via [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).\n * This assumes the app has scopes declared for [Shopify managing installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n *\n * Learn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).\n *\n * @default false\n */\n unstable_newEmbeddedAuthStrategy?: boolean;\n\n /**\n * When enabled, the Scopes API will be available. This feature is in development and requires special permissions from Shopify for now.\n *\n * @default false\n */\n wip_optionalScopesApi?: boolean;\n\n /**\n * When enabled, methods for interacting with the admin REST API will not be returned.\n *\n * This affects:\n *\n * * `authenticate.admin(request)`\n * * `authenticate.webhook(request)`\n * * `authenticate.flow(request)`\n * * `authenticate.appProxy(request)`\n * * `authenticate.fulfillmentService(request)`\n * * `unauthenticated.admin(shop)`\n *\n * In a future release we will remove REST from the package completely.\n *\n * Please see: [https://www.shopify.com/ca/partners/blog/all-in-on-graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql)\n *\n * @default false\n */\n removeRest?: boolean;\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "ShopifyAppGeneratedType", + "ShopifyAppBase", + "ShopifyAppLogin" + ], + "related": [ + { + "name": "Authenticated contexts", + "subtitle": "Authenticate requests coming from Shopify.", + "url": "/docs/api/shopify-app-remix/authenticate" + }, + { + "name": "Unauthenticated contexts", + "subtitle": "Interact with the API on non-Shopify requests.", + "url": "/docs/api/shopify-app-remix/unauthenticated" + } + ], + "defaultExample": { + "description": "", + "codeblock": { + "title": "The minimum viable configuration", + "tabs": [ + { + "title": "/shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n apiKey: process.env.SHOPIFY_API_KEY!,\n apiSecretKey: process.env.SHOPIFY_API_SECRET!,\n scopes: process.env.SCOPES?.split(\",\")!,\n appUrl: process.env.SHOPIFY_APP_URL!,\n});\nexport default shopify;", + "language": "typescript" + } + ] + } + }, + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "addDocumentResponseHeaders", + "examples": [ + { + "description": "Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.", + "codeblock": { + "title": "Return headers on all requests", + "tabs": [ + { + "title": "~/shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const addDocumentResponseheaders = shopify.addDocumentResponseheaders;", + "language": "typescript" + }, + { + "title": "entry.server.tsx", + "code": "import { addDocumentResponseHeaders } from \"~/shopify.server\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const markup = renderToString(\n <RemixServer context={remixContext} url={request.url} />\n );\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n addDocumentResponseHeaders(request, responseHeaders);\n\n return new Response(\"<!DOCTYPE html>\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "authenticate", + "examples": [ + { + "description": "Use the functions in `authenticate` to validate requests coming from Shopify.", + "codeblock": { + "title": "Authenticate Shopify requests", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", + "language": "typescript" + }, + { + "title": "/app/routes/**\\/*.jsx", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n const response = admin.graphql(`{ shop { name } }`)\n\n return json(await response.json());\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "registerWebhooks", + "examples": [ + { + "description": "Trigger the registration to create the shop-specific webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](https://shopify.dev/docs/api/shopify-app-remix/v3/guide-webhooks)", + "codeblock": { + "title": "Registering shop-specific webhooks after install", + "tabs": [ + { + "title": "app/shopify.server.ts", + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n PRODUCTS_CREATE: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks/products/create\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "sessionStorage", + "examples": [ + { + "description": "Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.", + "codeblock": { + "title": "Storing sessions with Prisma", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n sessionStorage: new PrismaSessionStorage(prisma),\n // ...etc\n})\n\n// shopify.sessionStorage is an instance of PrismaSessionStorage", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "unauthenticated", + "examples": [ + { + "description": "Create contexts for requests that don't come from Shopify.", + "codeblock": { + "title": "Using unauthenticated contexts", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", + "language": "typescript" + }, + { + "title": "/app/routes/**\\/*.jsx", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n const response = admin.graphql(`{ shop { currencyCode } }`)\n\n return json(await response.json());\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "login", + "examples": [ + { + "description": "Use `shopify.login` to create a login form, in a route that can handle GET and POST requests.", + "codeblock": { + "title": "Creating a login page", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;", + "language": "typescript" + }, + { + "title": "/app/routes/auth/login.tsx", + "code": "import shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const errors = shopify.login(request);\n\n return json(errors);\n}\n\nexport async function action({ request }: ActionFunctionArgs) {\n const errors = shopify.login(request);\n\n return json(errors);\n}\n\nexport default function Auth() {\n const actionData = useActionData<typeof action>();\n const [shop, setShop] = useState(\"\");\n\n return (\n <Page>\n <Card>\n <Form method=\"post\">\n <FormLayout>\n <Text variant=\"headingMd\" as=\"h2\">\n Login\n </Text>\n <TextField\n type=\"text\"\n name=\"shop\"\n label=\"Shop domain\"\n helpText=\"e.g: my-shop-domain.myshopify.com\"\n value={shop}\n onChange={setShop}\n autoComplete=\"on\"\n error={actionData?.errors.shop}\n />\n <Button submit primary>\n Submit\n </Button>\n </FormLayout>\n </Form>\n </Card>\n </Page>\n );\n}", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "Unauthenticated admin", + "description": "Allows interacting with the Admin API when working outside of Shopify requests.\nThis enables apps to integrate with 3rd party services and perform background tasks.\n\n> Caution:\n> This function doesn't perform **any** validation and shouldn't rely on raw user input.\n\nWhen using this function, consider the following:\n\n#### Background tasks\n\nApps should ensure that the shop domain is authenticated when enqueueing jobs.\n\n#### 3rd party service requests\n\nApps must obtain the shop domain from the 3rd party service in a secure way.", + "category": "Unauthenticated", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "unauthenticated.admin", + "description": "Creates an unauthenticated Admin context.", + "type": "GetUnauthenticatedAdminContext", + "typeDefinitions": { + "GetUnauthenticatedAdminContext": { + "filePath": "src/server/unauthenticated/admin/types.ts", + "name": "GetUnauthenticatedAdminContext", + "description": "", + "params": [ { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "scope", + "name": "shop", + "description": "", "value": "string", - "description": "The scopes for the access token.", - "isOptional": true - }, + "filePath": "src/server/unauthenticated/admin/types.ts" + } + ], + "returns": { + "filePath": "src/server/unauthenticated/admin/types.ts", + "description": "", + "name": "Promise>", + "value": "Promise>" + }, + "value": "export type GetUnauthenticatedAdminContext<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n> = (\n shop: string,\n) => Promise>;" + }, + "UnauthenticatedAdminContext": { + "filePath": "src/server/unauthenticated/admin/types.ts", + "name": "UnauthenticatedAdminContext", + "description": "", + "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the given store.", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `admin.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await unauthenticated.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", + "title": "/app/shopify.server.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." + "name": "session", + "value": "Session", + "description": "The session for the given shop.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nThis will always be an offline session. You can use to get shop-specific data.", + "examples": [ + { + "title": "Using the offline session", + "description": "Get your app's shop-specific data using the returned offline `session` object.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { session } = await unauthenticated.admin(shop);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + } + ] } ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" - }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" + "value": "export interface UnauthenticatedAdminContext<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources,\n> {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use to get shop-specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.admin(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the given store.\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await unauthenticated.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n */\n admin: AdminApiContext;\n}" } } } @@ -12784,24 +9014,6 @@ { "title": "admin", "examples": [ - { - "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", - "codeblock": { - "title": "Performing a GET request to the REST API", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await unauthenticated.admin(request);\n\n const response = await admin.rest.get(\n {\n path: \"/customers/count.json\"\n }\n );\n const customers = await response.json();\n\n return json({ customers });\n};", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\n\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", - "language": "typescript" - } - ] - } - }, { "description": "Use `admin.graphql` to make query / mutation requests.", "codeblock": { @@ -12814,7 +9026,7 @@ }, { "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", "language": "typescript" } ] @@ -12853,409 +9065,87 @@ { "title": "unauthenticated.storefront", "description": "Creates an unauthenticated Storefront context.", - "type": "GetUnauthenticatedStorefrontContext", - "typeDefinitions": { - "GetUnauthenticatedStorefrontContext": { - "filePath": "src/server/unauthenticated/storefront/types.ts", - "name": "GetUnauthenticatedStorefrontContext", - "description": "", - "params": [ - { - "name": "shop", - "description": "", - "value": "string", - "filePath": "src/server/unauthenticated/storefront/types.ts" - } - ], - "returns": { - "filePath": "src/server/unauthenticated/storefront/types.ts", - "description": "", - "name": "Promise", - "value": "Promise" - }, - "value": "export type GetUnauthenticatedStorefrontContext = (\n shop: string,\n) => Promise;" - }, - "UnauthenticatedStorefrontContext": { - "filePath": "src/server/unauthenticated/storefront/types.ts", - "name": "UnauthenticatedStorefrontContext", - "description": "", - "members": [ - { - "filePath": "src/server/unauthenticated/storefront/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the given shop.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nThis will always be an offline session. You can use this to get shop specific data.", - "examples": [ - { - "title": "Using the offline session", - "description": "Get your app's shop-specific data using the returned offline `session` object.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { session } = await unauthenticated.storefront(shop);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "app/routes/**\\/.ts" - } - ] - } - ] - }, - { - "filePath": "src/server/unauthenticated/storefront/types.ts", - "syntaxKind": "PropertySignature", - "name": "storefront", - "value": "StorefrontContext", - "description": "Method for interacting with the Shopify GraphQL Storefront API for the given store.", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `storefront.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const shop = getShopFromExternalRequest(request);\n const { storefront } = await unauthenticated.storefront(shop);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "title": "app/routes/**\\/.ts" - } - ] - }, - { - "title": "Handling GraphQL errors", - "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { storefront } = await unauthenticated.storefront(shop);\n\n try {\n const response = await storefront.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // { errors: { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] } }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", - "title": "/app/shopify.server.ts" - } - ] - } - ] - } - ], - "value": "export interface UnauthenticatedStorefrontContext {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use this to get shop specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // app/routes/**\\/.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.storefront(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Method for interacting with the Shopify GraphQL Storefront API for the given store.\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const shop = getShopFromExternalRequest(request);\n * const { storefront } = await unauthenticated.storefront(shop);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n *\n * @example\n * Handling GraphQL errors.\n * Catch `GraphqlQueryError` errors to see error messages from the API.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { storefront } = await unauthenticated.storefront(shop);\n *\n * try {\n * const response = await storefront.graphql(\n * `#graphql\n * query incorrectQuery {\n * products(first: 10) {\n * nodes {\n * not_a_field\n * }\n * }\n * }`,\n * );\n *\n * return json({ data: await response.json() });\n * } catch (error) {\n * if (error instanceof GraphqlQueryError) {\n * // { errors: { graphQLErrors: [\n * // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n * // ] } }\n * return json({ errors: error.body?.errors }, { status: 500 });\n * }\n * return json({ message: \"An error occurred\" }, { status: 500 });\n * }\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n */\n storefront: StorefrontContext;\n}" - }, - "Session": { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "name": "Session", - "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain, such as `example.myshopify.com`." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "scope", - "value": "string", - "description": "The desired scopes for the access token, at the time the session was created." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "expires", - "value": "Date", - "description": "The date the access token expires." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "accessToken", - "value": "string", - "description": "The access token for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "PropertyDeclaration", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isActive", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "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." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeChanged", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes if they are provided." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isScopeIncluded", - "value": "(scopes: string | string[] | AuthScopes) => boolean", - "description": "Whether the access token includes the given scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "isExpired", - "value": "(withinMillisecondsOfExpiry?: number) => boolean", - "description": "Whether the access token is expired." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toObject", - "value": "() => SessionParams", - "description": "Converts an object with data into a Session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(other: Session) => boolean", - "description": "Checks whether the given session is equal to this session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/session.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toPropertyArray", - "value": "(returnUserData?: boolean) => [string, string | number | boolean][]", - "description": "Converts the session into an array of key-value pairs." - } - ], - "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][], returnUserData?: boolean): Session;\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain, such as `example.myshopify.com`.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The desired scopes for the access token, at the time the session was created.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n /**\n * Whether the session is active. Active sessions have an access token that is not expired, and has has the given\n * scopes if scopes is equal to a truthy value.\n */\n isActive(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes if they are provided.\n */\n isScopeChanged(scopes: AuthScopes | string | string[] | undefined): boolean;\n /**\n * Whether the access token includes the given scopes.\n */\n isScopeIncluded(scopes: AuthScopes | string | string[]): boolean;\n /**\n * Whether the access token is expired.\n */\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n /**\n * Converts an object with data into a Session.\n */\n toObject(): SessionParams;\n /**\n * Checks whether the given session is equal to this session.\n */\n equals(other: Session | undefined): boolean;\n /**\n * Converts the session into an array of key-value pairs.\n */\n toPropertyArray(returnUserData?: boolean): [string, string | number | boolean][];\n}" - }, - "OnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessInfo", - "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user", - "value": "OnlineAccessUser", - "description": "The user associated with the access token." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "associated_user_scope", - "value": "string", - "description": "The effective set of scopes for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires_in", - "value": "number", - "description": "How long the access token is valid for, in seconds." - } - ], - "value": "export interface OnlineAccessInfo {\n /**\n * How long the access token is valid for, in seconds.\n */\n expires_in: number;\n /**\n * The effective set of scopes for the session.\n */\n associated_user_scope: string;\n /**\n * The user associated with the access token.\n */\n associated_user: OnlineAccessUser;\n}" - }, - "OnlineAccessUser": { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "name": "OnlineAccessUser", + "type": "GetUnauthenticatedStorefrontContext", + "typeDefinitions": { + "GetUnauthenticatedStorefrontContext": { + "filePath": "src/server/unauthenticated/storefront/types.ts", + "name": "GetUnauthenticatedStorefrontContext", "description": "", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "account_owner", - "value": "boolean", - "description": "Whether the user is the account owner." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "collaborator", - "value": "boolean", - "description": "Whether the user is a collaborator." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email", - "value": "string", - "description": "The user's email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "email_verified", - "value": "boolean", - "description": "Whether the user has verified their email address." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "first_name", - "value": "string", - "description": "The user's first name." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "number", - "description": "The user's ID." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "last_name", - "value": "string", - "description": "The user's last name." - }, + "params": [ { - "filePath": "../shopify-api/dist/ts/lib/auth/oauth/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "locale", + "name": "shop", + "description": "", "value": "string", - "description": "The user's locale." - } - ], - "value": "export interface OnlineAccessUser {\n /**\n * The user's ID.\n */\n id: number;\n /**\n * The user's first name.\n */\n first_name: string;\n /**\n * The user's last name.\n */\n last_name: string;\n /**\n * The user's email address.\n */\n email: string;\n /**\n * Whether the user has verified their email address.\n */\n email_verified: boolean;\n /**\n * Whether the user is the account owner.\n */\n account_owner: boolean;\n /**\n * The user's locale.\n */\n locale: string;\n /**\n * Whether the user is a collaborator.\n */\n collaborator: boolean;\n}" - }, - "AuthScopes": { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "name": "AuthScopes", - "description": "A class that represents a set of access token scopes.", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "has", - "value": "(scope: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes includes the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "equals", - "value": "(otherScopes: string | string[] | AuthScopes) => boolean", - "description": "Checks whether the current set of scopes equals the given one." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toString", - "value": "() => string", - "description": "Returns a comma-separated string with the current set of scopes." - }, - { - "filePath": "../shopify-api/dist/ts/lib/auth/scopes/index.d.ts", - "syntaxKind": "MethodDeclaration", - "name": "toArray", - "value": "(returnOriginalScopes?: boolean) => string[]", - "description": "Returns an array with the current set of scopes." + "filePath": "src/server/unauthenticated/storefront/types.ts" } ], - "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n private originalScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n /**\n * Checks whether the current set of scopes includes the given one.\n */\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Checks whether the current set of scopes equals the given one.\n */\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n /**\n * Returns a comma-separated string with the current set of scopes.\n */\n toString(): string;\n /**\n * Returns an array with the current set of scopes.\n */\n toArray(returnOriginalScopes?: boolean): string[];\n private getImpliedScopes;\n}" + "returns": { + "filePath": "src/server/unauthenticated/storefront/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type GetUnauthenticatedStorefrontContext = (\n shop: string,\n) => Promise;" }, - "SessionParams": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "SessionParams", + "UnauthenticatedStorefrontContext": { + "filePath": "src/server/unauthenticated/storefront/types.ts", + "name": "UnauthenticatedStorefrontContext", "description": "", "members": [ { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "name": "[key: string]", - "value": "any" - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "accessToken", - "value": "string", - "description": "The access token for the session.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "expires", - "value": "Date", - "description": "The date the access token expires.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "id", - "value": "string", - "description": "The unique identifier for the session." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "isOnline", - "value": "boolean", - "description": "Whether the access token in the session is online or offline." - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "onlineAccessInfo", - "value": "OnlineAccessInfo | StoredOnlineAccessInfo", - "description": "Information on the user for the session. Only present for online sessions.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "PropertySignature", - "name": "scope", - "value": "string", - "description": "The scopes for the access token.", - "isOptional": true - }, - { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The Shopify shop domain." + "name": "session", + "value": "Session", + "description": "The session for the given shop.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nThis will always be an offline session. You can use this to get shop specific data.", + "examples": [ + { + "title": "Using the offline session", + "description": "Get your app's shop-specific data using the returned offline `session` object.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { unauthenticated } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { session } = await unauthenticated.storefront(shop);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "app/routes/**\\/.ts" + } + ] + } + ] }, { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "syntaxKind": "PropertySignature", - "name": "state", - "value": "string", - "description": "The state of the session. Used for the OAuth authentication code flow." + "name": "storefront", + "value": "StorefrontContext", + "description": "Method for interacting with the Shopify GraphQL Storefront API for the given store.", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `storefront.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const shop = getShopFromExternalRequest(request);\n const { storefront } = await unauthenticated.storefront(shop);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "title": "app/routes/**\\/.ts" + } + ] + }, + { + "title": "Handling GraphQL errors", + "description": "Catch `GraphqlQueryError` errors to see error messages from the API.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const shop = getShopFromExternalRequest(request);\n const { storefront } = await unauthenticated.storefront(shop);\n\n try {\n const response = await storefront.graphql(\n `#graphql\n query incorrectQuery {\n products(first: 10) {\n nodes {\n not_a_field\n }\n }\n }`,\n );\n\n return json({ data: await response.json() });\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n // { errors: { graphQLErrors: [\n // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n // ] } }\n return json({ errors: error.body?.errors }, { status: 500 });\n }\n return json({ message: \"An error occurred\" }, { status: 500 });\n }\n}", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...\n});\nexport default shopify;\nexport const unauthenticated = shopify.unauthenticated;", + "title": "/app/shopify.server.ts" + } + ] + } + ] } ], - "value": "export interface SessionParams {\n /**\n * The unique identifier for the session.\n */\n readonly id: string;\n /**\n * The Shopify shop domain.\n */\n shop: string;\n /**\n * The state of the session. Used for the OAuth authentication code flow.\n */\n state: string;\n /**\n * Whether the access token in the session is online or offline.\n */\n isOnline: boolean;\n /**\n * The scopes for the access token.\n */\n scope?: string;\n /**\n * The date the access token expires.\n */\n expires?: Date;\n /**\n * The access token for the session.\n */\n accessToken?: string;\n /**\n * Information on the user for the session. Only present for online sessions.\n */\n onlineAccessInfo?: OnlineAccessInfo | StoredOnlineAccessInfo;\n /**\n * Additional properties of the session allowing for extension\n */\n [key: string]: any;\n}" - }, - "StoredOnlineAccessInfo": { - "filePath": "../shopify-api/dist/ts/lib/session/types.d.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "StoredOnlineAccessInfo", - "value": "Omit & {\n associated_user: Partial;\n}", - "description": "" + "value": "export interface UnauthenticatedStorefrontContext {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use this to get shop specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // app/routes/**\\/.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.storefront(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Method for interacting with the Shopify GraphQL Storefront API for the given store.\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const shop = getShopFromExternalRequest(request);\n * const { storefront } = await unauthenticated.storefront(shop);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n *\n * @example\n * Handling GraphQL errors.\n * Catch `GraphqlQueryError` errors to see error messages from the API.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { storefront } = await unauthenticated.storefront(shop);\n *\n * try {\n * const response = await storefront.graphql(\n * `#graphql\n * query incorrectQuery {\n * products(first: 10) {\n * nodes {\n * not_a_field\n * }\n * }\n * }`,\n * );\n *\n * return json({ data: await response.json() });\n * } catch (error) {\n * if (error instanceof GraphqlQueryError) {\n * // { errors: { graphQLErrors: [\n * // { message: \"Field 'not_a_field' doesn't exist on type 'Product'\" }\n * // ] } }\n * return json({ errors: error.body?.errors }, { status: 500 });\n * }\n * return json({ message: \"An error occurred\" }, { status: 500 });\n * }\n * }\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...\n * });\n * export default shopify;\n * export const unauthenticated = shopify.unauthenticated;\n * ```\n */\n storefront: StorefrontContext;\n}" }, "StorefrontContext": { "filePath": "src/server/clients/storefront/types.ts", @@ -13364,64 +9254,6 @@ } ], "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n /**\n * The variables to pass to the operation.\n */\n variables?: ApiClientRequestOptions['variables'];\n /**\n * The version of the API to use for the request.\n */\n apiVersion?: ApiVersion;\n /**\n * Additional headers to include in the request.\n */\n headers?: Record;\n /**\n * The total number of times to try the request if it fails.\n */\n tries?: number;\n}" - }, - "ApiVersion": { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "syntaxKind": "EnumDeclaration", - "name": "ApiVersion", - "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n April24 = \"2024-04\",\n July24 = \"2024-07\",\n October24 = \"2024-10\",\n Unstable = \"unstable\"\n}", - "members": [ - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October22", - "value": "2022-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January23", - "value": "2023-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April23", - "value": "2023-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July23", - "value": "2023-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October23", - "value": "2023-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "January24", - "value": "2024-01" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "April24", - "value": "2024-04" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "July24", - "value": "2024-07" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "October24", - "value": "2024-10" - }, - { - "filePath": "../shopify-api/dist/ts/lib/types.d.ts", - "name": "Unstable", - "value": "unstable" - } - ] } } } diff --git a/packages/apps/shopify-app-remix/docs/generated/generated_static_pages.json b/packages/apps/shopify-app-remix/docs/generated/generated_static_pages.json index fb44b7d303..6ca0ec4599 100644 --- a/packages/apps/shopify-app-remix/docs/generated/generated_static_pages.json +++ b/packages/apps/shopify-app-remix/docs/generated/generated_static_pages.json @@ -78,8 +78,8 @@ { "type": "Generic", "anchorLink": "rest-api", - "title": "Using the REST API", - "sectionContent": "Once a request is authenticated, `authenticate.admin` will return an `admin` object that contains a REST client that can interact with the [REST Admin API](/docs/api/admin-rest).\n\nYou can also import a set of resource classes from the `@shopify/shopify-api` package, which is included in `@shopify/shopify-app-remix`.\n\nThese classes map to the individual REST endpoints, and will be returned under `admin.rest.resources`.", + "title": "Using the REST API (Deprecated)", + "sectionContent": "**Shopify is [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql). In the next major release, the REST API will be removed from the `@shopify/shopify-app-remix` package.If the `removeRest` [future flag](/docs/api/shopify-app-remix/v3/guide-future-flags) is true, then the REST API will not be available.**\n\nOnce a request is authenticated, `authenticate.admin` will return an `admin` object that contains a REST client that can interact with the [REST Admin API](/docs/api/admin-rest).\n\nYou can also import a set of resource classes from the `@shopify/shopify-api` package, which is included in `@shopify/shopify-app-remix`.\n\nThese classes map to the individual REST endpoints, and will be returned under `admin.rest.resources`.", "codeblock": { "title": "Interacting with the REST API", "tabs": [ @@ -234,7 +234,7 @@ "type": "Generic", "anchorLink": "breaking-changes", "title": "Breaking changes", - "sectionContent": "Similarly to unstable APIs, breaking changes will be introduced behind a future flag, but the prefix will be the next major version (e.g. `v3_`).\n\nThis allows apps to prepare for the next major version ahead of time, and to gradually adopt the new APIs.\n\nWhen the next major version is released, the future flag will be removed, and the old code it changes will be removed. Apps that adopted the flag before then will continue to work the same way with no new changes." + "sectionContent": "Similarly to unstable APIs, breaking changes will be introduced behind a future flag.\n\nThis allows apps to prepare for the next major version ahead of time, and to gradually adopt the new APIs.\n\nWhen the next major version is released, the future flag will be removed, and the old code it changes will be removed. Apps that adopted the flag before then will continue to work the same way with no new changes." }, { "type": "GenericList", @@ -247,6 +247,12 @@ "value": "", "description": "Embedded apps will fetch access tokens via token exchange. This assumes the app has declared scopes for Shopify managed installations.\n\nLearn more about this [new embedded app auth strategy](https://shopify.dev/docs/api/shopify-app-remix#embedded-auth-strategy).", "isOptional": true + }, + { + "name": "removeRest", + "value": "", + "description": "Methods for interacting with the admin REST API will not be returned\n\nThis affects:\n\n* `authenticate.admin(request)`\n* `authenticate.webhook(request)`\n* `authenticate.flow(request)`\n* `authenticate.appProxy(request)`\n* `authenticate.fulfillmentService(request)`\n* `unauthenticated.admin(shop)`\n\nLearn more about this change by reading [all-in on graphql](https://www.shopify.com/ca/partners/blog/all-in-on-graphql).", + "isOptional": true } ] } diff --git a/packages/apps/shopify-app-remix/src/react/components/AppProxyForm/AppProxyForm.tsx b/packages/apps/shopify-app-remix/src/react/components/AppProxyForm/AppProxyForm.tsx index bfff042375..ca69c36ceb 100644 --- a/packages/apps/shopify-app-remix/src/react/components/AppProxyForm/AppProxyForm.tsx +++ b/packages/apps/shopify-app-remix/src/react/components/AppProxyForm/AppProxyForm.tsx @@ -8,15 +8,19 @@ export interface AppProxyFormProps extends FormProps { } /** - * Sets up a Remix `
` component that works when rendered behind an app proxy. + * Sets up a Remix `` component that works when rendered on an app proxy page. * * Supports any properties accepted by the `` component. * + * Because Remix doesn't support URL rewriting, any route using this component should match the pathname of the proxy + * URL exactly, and end in a trailing slash (e.g., `https:///apps/proxy/`), or set the Remix Form prop + * `navigate` to `false`. + * * @example * Render a form element in a proxied route. * Use an `AppProxyForm` within an `AppProxy` to create a form. * ```ts - * // /app/routes/**\/*.ts + * // /app/routes/apps/appProxy.ts * import { * AppProxyProvider, * AppProxyForm, @@ -46,12 +50,11 @@ export interface AppProxyFormProps extends FormProps { * * export default function App() { * const { appUrl } = useLoaderData(); - * const data = useActionData(); * * return ( * - * - * + * + * * * * diff --git a/packages/apps/shopify-app-remix/src/react/components/AppProxyProvider/AppProxyProvider.doc.ts b/packages/apps/shopify-app-remix/src/react/components/AppProxyProvider/AppProxyProvider.doc.ts index da5bb20457..24a9513386 100644 --- a/packages/apps/shopify-app-remix/src/react/components/AppProxyProvider/AppProxyProvider.doc.ts +++ b/packages/apps/shopify-app-remix/src/react/components/AppProxyProvider/AppProxyProvider.doc.ts @@ -25,13 +25,13 @@ const data: ReferenceEntityTemplateSchema = { { name: 'AppProxyForm', subtitle: 'Render form elements in proxies.', - url: '/docs/api/shopify-app-remix/app-proxy-components/appproxy-form', + url: '/docs/api/shopify-app-remix/app-proxy-components/appproxyform', type: 'remix', }, { name: 'AppProxyLink', subtitle: 'Render link elements in proxies.', - url: '/docs/api/shopify-app-remix/app-proxy-components/appproxy-link', + url: '/docs/api/shopify-app-remix/app-proxy-components/appproxylink', type: 'remix', }, ],