From 533a23d90b9317bbe9e5449feb77dce61e37dba2 Mon Sep 17 00:00:00 2001 From: Si Le Date: Tue, 10 Dec 2024 11:43:07 -0500 Subject: [PATCH 1/5] Changes Money.amount type to string following GraphQL schema Decimal type --- .changeset/green-zoos-lie.md | 6 + .../apps/shopify-api/lib/billing/types.ts | 6 +- .../docs/generated/generated_docs_data.json | 12 +- .../docs/temp/generated_docs_data.json | 9276 +++++++++++++++++ .../docs/temp/generated_static_pages.json | 640 ++ .../authenticate/admin/billing/types.ts | 2 +- 6 files changed, 9932 insertions(+), 10 deletions(-) create mode 100644 .changeset/green-zoos-lie.md create mode 100644 packages/apps/shopify-app-remix/docs/temp/generated_docs_data.json create mode 100644 packages/apps/shopify-app-remix/docs/temp/generated_static_pages.json diff --git a/.changeset/green-zoos-lie.md b/.changeset/green-zoos-lie.md new file mode 100644 index 0000000000..b33f6f86de --- /dev/null +++ b/.changeset/green-zoos-lie.md @@ -0,0 +1,6 @@ +--- +'@shopify/shopify-app-remix': minor +'@shopify/shopify-api': minor +--- + +Fixes Money.amount type from number to string to follow GraphQL schema Decimal type diff --git a/packages/apps/shopify-api/lib/billing/types.ts b/packages/apps/shopify-api/lib/billing/types.ts index 847d86078e..3ec97df801 100644 --- a/packages/apps/shopify-api/lib/billing/types.ts +++ b/packages/apps/shopify-api/lib/billing/types.ts @@ -427,7 +427,7 @@ export interface UsageAppPlan { } export interface Money { - amount: number; + amount: string; currencyCode: string; } @@ -506,7 +506,7 @@ export interface BillingCreateUsageRecordParams { /** * The amount to charge for this usage record. */ - amount: number; + amount: string; /** * The currency code for this usage record. */ @@ -542,7 +542,7 @@ export interface UsageRecord { /** * The amount to charge for this usage record. */ - amount: number; + amount: string; /** * The currency code for this usage record. */ 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 3a8d39e8cd..ab5dba7d92 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 @@ -672,7 +672,7 @@ "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "price", - "value": "{ amount: number; currencyCode: string; }", + "value": "{ amount: string; currencyCode: string; }", "description": "The price of the app usage record." }, { @@ -684,7 +684,7 @@ "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}" + "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: string;\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", @@ -1747,7 +1747,7 @@ "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "price", - "value": "{ amount: number; currencyCode: string; }", + "value": "{ amount: string; currencyCode: string; }", "description": "The price of the app usage record." }, { @@ -1759,7 +1759,7 @@ "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}" + "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: string;\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", @@ -5249,7 +5249,7 @@ "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "price", - "value": "{ amount: number; currencyCode: string; }", + "value": "{ amount: string; currencyCode: string; }", "description": "The price of the app usage record." }, { @@ -5261,7 +5261,7 @@ "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}" + "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: string;\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", diff --git a/packages/apps/shopify-app-remix/docs/temp/generated_docs_data.json b/packages/apps/shopify-app-remix/docs/temp/generated_docs_data.json new file mode 100644 index 0000000000..ab5dba7d92 --- /dev/null +++ b/packages/apps/shopify-app-remix/docs/temp/generated_docs_data.json @@ -0,0 +1,9276 @@ +[ + { + "name": "AppProvider", + "description": "Sets up the Polaris `AppProvider` and injects the App Bridge script.\n\nThis component extends the [`AppProvider`](https://polaris.shopify.com/components/utilities/app-provider) component from Polaris, and accepts all of its props except for `linkComponent`, which is overridden to use the Remix `Link` component.\n\n\n\n\n\n\n\n\n\n", + "category": "Entrypoints", + "type": "component", + "isVisualComponent": false, + "definitions": [ + { + "title": "AppProviderProps", + "description": "Props for the `AppProvider` component.", + "type": "AppProviderProps", + "typeDefinitions": { + "AppProviderProps": { + "filePath": "src/react/components/AppProvider/AppProvider.tsx", + "name": "AppProviderProps", + "description": "", + "members": [ + { + "filePath": "src/react/components/AppProvider/AppProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "__APP_BRIDGE_URL", + "value": "string", + "description": "Used internally by Shopify. You don't need to set this.", + "isOptional": true, + "isPrivate": true + }, + { + "filePath": "src/react/components/AppProvider/AppProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "apiKey", + "value": "string", + "description": "The API key for your Shopify app. This is the `Client ID` from the Partner Dashboard.\n\nWhen using the Shopify CLI, this is the `SHOPIFY_API_KEY` environment variable. If you're using the environment variable, then you need to pass it from the loader to the component." + }, + { + "filePath": "src/react/components/AppProvider/AppProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "children", + "value": "React.ReactNode", + "description": "Inner content of the application", + "isOptional": true + }, + { + "filePath": "src/react/components/AppProvider/AppProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "features", + "value": "FeaturesConfig", + "description": "For toggling features", + "isOptional": true + }, + { + "filePath": "src/react/components/AppProvider/AppProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "i18n", + "value": "TranslationDictionary | TranslationDictionary[]", + "description": "The internationalization (i18n) configuration for your Polaris provider.\n\n\n\n\n", + "isOptional": true + }, + { + "filePath": "src/react/components/AppProvider/AppProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "isEmbeddedApp", + "value": "boolean", + "description": "Whether the app is loaded inside the Shopify Admin. Default is `true`.\n\n\n\n\n", + "isOptional": true + }, + { + "filePath": "src/react/components/AppProvider/AppProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "theme", + "value": "ThemeName", + "description": "", + "isOptional": true + } + ], + "value": "export interface AppProviderProps\n extends Omit {\n /**\n * The API key for your Shopify app. This is the `Client ID` from the Partner Dashboard.\n *\n * When using the Shopify CLI, this is the `SHOPIFY_API_KEY` environment variable. If you're using the environment\n * variable, then you need to pass it from the loader to the component.\n */\n apiKey: string;\n /**\n * Whether the app is loaded inside the Shopify Admin. Default is `true`.\n *\n * {@link https://shopify.dev/docs/apps/admin/embedded-app-home}\n */\n isEmbeddedApp?: boolean;\n /**\n * The internationalization (i18n) configuration for your Polaris provider.\n *\n * {@link https://polaris.shopify.com/components/utilities/app-provider}\n */\n i18n?: PolarisAppProviderProps['i18n'];\n /**\n * Used internally by Shopify. You don't need to set this.\n * @private\n */\n __APP_BRIDGE_URL?: string;\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "AppProviderGeneratedType" + ], + "related": [ + { + "name": "App bridge", + "subtitle": "Learn more about App Bridge.", + "url": "/docs/api/app-bridge-library", + "type": "shopify" + }, + { + "name": "Polaris", + "subtitle": "Learn more about Polaris.", + "url": "/docs/apps/tools/polaris", + "type": "shopify" + } + ], + "examples": { + "description": "", + "examples": [ + { + "description": "Wrap your app in the `AppProvider` component and pass in your API key.", + "codeblock": { + "title": "Set up AppProvider", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import {authenticate} from '~/shopify.server';\nimport {AppProvider} from '@shopify/shopify-app-remix/react';\n\nexport async function loader({ request }) {\n await authenticate.admin(request);\n\n return json({ apiKey: process.env.SHOPIFY_API_KEY });\n}\n\nexport default function App() {\n const { apiKey } = useLoaderData();\n\n return (\n <AppProvider isEmbeddedApp apiKey={apiKey}>\n <Outlet />\n </AppProvider>\n );\n}", + "language": "typescript" + } + ] + } + }, + { + "description": "Pass in a different locale for Polaris to translate its components.", + "codeblock": { + "title": "Localize Polaris components", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import {authenticate} from '~/shopify.server';\nimport {AppProvider} from '@shopify/shopify-app-remix/react';\n\nexport async function loader({ request }) {\n await authenticate.admin(request);\n\n return json({\n apiKey: process.env.SHOPIFY_API_KEY,\n polarisTranslations: require(\"@shopify/polaris/locales/fr.json\"),\n });\n}\n\nexport default function App() {\n const { apiKey, polarisTranslations } = useLoaderData();\n\n return (\n <AppProvider apiKey={apiKey} i18n={polarisTranslations}>\n <Outlet />\n </AppProvider>\n );\n}", + "language": "typescript" + } + ] + } + } + ] + } + }, + { + "name": "AppProxyForm", + "description": "Sets up a Remix `
` component that works when rendered behind an app proxy.\n\nSupports any properties accepted by the `` component.", + "category": "App proxy components", + "type": "component", + "isVisualComponent": false, + "definitions": [], + "jsDocTypeExamples": [ + "AppProxyFormGeneratedType" + ], + "related": [ + { + "name": "authenticate.public.appProxy", + "subtitle": "Authenticating app proxy requests.", + "url": "/docs/api/shopify-app-remix/authenticate/public/app-proxy", + "type": "remix" + }, + { + "name": "AppProxyProvider", + "subtitle": "Enable JavaScript in pages loaded through app proxies.", + "url": "/docs/api/shopify-app-remix/app-proxy-components/appproxyprovider", + "type": "remix" + } + ], + "defaultExample": { + "description": "Use an `AppProxyForm` within an `AppProxy` to create a form.", + "codeblock": { + "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}", + "language": "typescript" + } + ] + } + } + }, + { + "name": "AppProxyLink", + "description": "Sets up an `` HTML element that works when rendered behind an app proxy.\n\nSupports any properties accepted by the `` HTML element.", + "category": "App proxy components", + "type": "component", + "isVisualComponent": false, + "definitions": [], + "jsDocTypeExamples": [ + "AppProxyLinkGeneratedType" + ], + "related": [ + { + "name": "authenticate.public.appProxy", + "subtitle": "Authenticating app proxy requests.", + "url": "/docs/api/shopify-app-remix/authenticate/public/app-proxy", + "type": "remix" + }, + { + "name": "AppProxyProvider", + "subtitle": "Enable JavaScript in pages loaded through app proxies.", + "url": "/docs/api/shopify-app-remix/app-proxy-components/appproxyprovider", + "type": "remix" + } + ], + "defaultExample": { + "description": "Use an `AppProxyLink` within an `AppProxyProvider` to link to a different proxied route.", + "codeblock": { + "title": "Link to a different route", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import {authenticate} from '~/shopify.server';\nimport {AppProxyProvider, AppProxyLink} from '@shopify/shopify-app-remix/react';\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 default function App() {\n const { appUrl } = useLoaderData();\n\n return (\n <AppProxyProvider appUrl={appUrl}>\n <AppProxyLink href=\"/other-proxy-route\">Link to another route</AppProxyLink>\n </AppProxyProvider>\n );\n}", + "language": "typescript" + } + ] + } + } + }, + { + "name": "AppProxyProvider", + "description": "Sets up a page to render behind a Shopify app proxy, enabling JavaScript and CSS to be loaded from the app.\n\nAlso provides components that enable using other components such as links and forms within proxies.\n\n> Caution: 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/`).", + "category": "Entrypoints", + "type": "component", + "isVisualComponent": false, + "definitions": [ + { + "title": "AppProxyProviderProps", + "description": "Props for the `AppProxyProvider` component.", + "type": "AppProxyProviderProps", + "typeDefinitions": { + "AppProxyProviderProps": { + "filePath": "src/react/components/AppProxyProvider/AppProxyProvider.tsx", + "name": "AppProxyProviderProps", + "description": "", + "members": [ + { + "filePath": "src/react/components/AppProxyProvider/AppProxyProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "appUrl", + "value": "string", + "description": "The URL where the app is hosted. You can set this from the `SHOPIFY_APP_URL` environment variable." + }, + { + "filePath": "src/react/components/AppProxyProvider/AppProxyProvider.tsx", + "syntaxKind": "PropertySignature", + "name": "children", + "value": "React.ReactNode", + "description": "The children to render.", + "isOptional": true + } + ], + "value": "export interface AppProxyProviderProps {\n /**\n * The URL where the app is hosted. You can set this from the `SHOPIFY_APP_URL` environment variable.\n */\n appUrl: string;\n\n /**\n * The children to render.\n */\n children?: React.ReactNode;\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "AppProxyProviderGeneratedType" + ], + "related": [ + { + "name": "authenticate.public.appProxy", + "subtitle": "Authenticate app proxy requests.", + "url": "/docs/api/shopify-app-remix/authenticate/public/app-proxy", + "type": "remix" + }, + { + "name": "AppProxyForm", + "subtitle": "Render form elements in proxies.", + "url": "/docs/api/shopify-app-remix/app-proxy-components/appproxy-form", + "type": "remix" + }, + { + "name": "AppProxyLink", + "subtitle": "Render link elements in proxies.", + "url": "/docs/api/shopify-app-remix/app-proxy-components/appproxy-link", + "type": "remix" + } + ], + "defaultExample": { + "description": "Wrap your route component in the `AppProxyProvider` component and pass in your app URL.", + "codeblock": { + "title": "Wrap a route with an AppProxyProvider component", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import {authenticate} from '~/shopify.server';\nimport {AppProxyProvider} from '@shopify/shopify-app-remix/react';\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 default function App() {\n const { appUrl } = useLoaderData();\n\n return (\n <AppProxyProvider appUrl={appUrl}>\n Page content\n </AppProxyProvider>\n );\n}", + "language": "typescript" + } + ] + } + } + }, + { + "name": "Admin", + "description": "Contains methods for authenticating and interacting with the Admin API.\n\nThis function can handle requests for apps embedded in the Admin, Admin extensions, or non-embedded apps.", + "category": "Authenticate", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "authenticate.admin", + "description": "Authenticates requests coming from the Shopify admin.\n\nThe shape of the returned object changes depending on the `isEmbeddedApp` config.", + "type": "AuthenticateAdmin", + "typeDefinitions": { + "AuthenticateAdmin": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "AuthenticateAdmin", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/admin/types.ts" + } + ], + "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>;" + }, + "AdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminContext", + "value": "EmbeddedTypedAdminContext & ScopesContext", + "description": "" + }, + "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": "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": "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 NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" + }, + "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}" + }, + "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}" + }, + "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: string; 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: string;\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 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}" + }, + "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", + "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": "" + }, + "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": "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}" + }, + "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}" + }, + "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: string; 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: string;\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 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 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": "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.", + "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": [ + { + "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 ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (\n request: Request,\n) => 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, 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/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.", + "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}" + }, + "FulfillmentServicePayload": { + "filePath": "src/server/authenticate/fulfillment-service/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "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": [ + "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, session } = await authenticate.fulfillmentService(request);\n\n console.log(session.id)\n\n return new Response();\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.", + "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\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": "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 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": "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 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", + "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}" + } + } + } + ], + "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 admin GraphQL API.", + "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}" + } + } + } + ], + "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}" + } + } + } + ], + "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 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", + "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 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": "/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" + } + ] + } + } + ] + }, + { + "title": "webhookId", + "examples": [ + { + "description": "Get the webhook ID.", + "codeblock": { + "title": "Webhook ID", + "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};", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "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": "" + }, + "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/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", + "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" + } + ], + "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, 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}" + }, + "AdminApiContextWithoutRest": { + "filePath": "src/server/clients/admin/types.ts", + "name": "AdminApiContextWithoutRest", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/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", + "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 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}" + }, + "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}" + }, + "AdminApiContextWithRest": { + "filePath": "src/server/clients/admin/types.ts", + "name": "AdminApiContextWithRest", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/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", + "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": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "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 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}" + }, + "RestClientWithResources": { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RestClientWithResources", + "value": "RemixRestClient & {resources: Resources}", + "description": "" + }, + "RemixRestClient": { + "filePath": "src/server/clients/admin/rest.ts", + "name": "RemixRestClient", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "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": "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": "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}" + } + } + } + ], + "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" + } + ] + } + ] + }, + "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": "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" + } + ] + }, + "AdminApp": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminApp", + "value": "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": "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": "src/server/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" + } + ] + } + ] + }, + { + "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": "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": "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": "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" + } + ] + } + ] + } + ] + }, + "AddDocumentResponseHeaders": { + "filePath": "src/server/types.ts", + "name": "AddDocumentResponseHeaders", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/types.ts" + }, + { + "name": "headers", + "description": "", + "value": "Headers", + "filePath": "src/server/types.ts" + } + ], + "returns": { + "filePath": "src/server/types.ts", + "description": "", + "name": "void", + "value": "void" + }, + "value": "type AddDocumentResponseHeaders = (request: Request, headers: Headers) => void;" + }, + "Authenticate": { + "filePath": "src/server/types.ts", + "name": "Authenticate", + "description": "", + "members": [ + { + "filePath": "src/server/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 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": "src/server/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.", + "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": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "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/types.ts", + "syntaxKind": "PropertySignature", + "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/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": "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": "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}" + }, + "AuthenticateAdmin": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "AuthenticateAdmin", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/admin/types.ts" + } + ], + "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>;" + }, + "AdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminContext", + "value": "EmbeddedTypedAdminContext & ScopesContext", + "description": "" + }, + "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": "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": "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 NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" + }, + "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}" + }, + "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}" + }, + "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: string; 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: string;\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 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}" + }, + "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", + "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": "" + }, + "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}" + }, + "RestResourcesType": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RestResourcesType", + "value": "Config['restResources'] extends ShopifyRestResources\n ? Config['restResources']\n : ShopifyRestResources", + "description": "" + }, + "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 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": "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.", + "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}" + }, + "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 ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (\n request: Request,\n) => 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, 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/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.", + "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}" + }, + "FulfillmentServicePayload": { + "filePath": "src/server/authenticate/fulfillment-service/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "FulfillmentServicePayload", + "value": "Record & {\n kind: string;\n}", + "description": "" + }, + "AuthenticatePublic": { + "filePath": "src/server/authenticate/public/types.ts", + "name": "AuthenticatePublic", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/types.ts", + "syntaxKind": "PropertySignature", + "name": "appProxy", + "value": "AuthenticateAppProxy", + "description": "Authenticate a request from an app proxy", + "examples": [ + { + "title": "Authenticating an app proxy request", + "description": "", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport 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};", + "title": "/app/routes/public/widgets.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/types.ts", + "syntaxKind": "PropertySignature", + "name": "checkout", + "value": "AuthenticateCheckout", + "description": "Authenticate a request from a checkout extension", + "examples": [ + { + "title": "Authenticating a checkout extension request", + "description": "", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport 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};", + "title": "/app/routes/public/widgets.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/types.ts", + "syntaxKind": "PropertySignature", + "name": "customerAccount", + "value": "AuthenticateCustomerAccount", + "description": "Authenticate a request from a customer account extension", + "examples": [ + { + "title": "Authenticating a customer account extension request", + "description": "", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport 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};", + "title": "/app/routes/public/widgets.ts" + } + ] + } + ] + } + ], + "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", + "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": "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 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": "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 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", + "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}" + }, + "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}" + }, + "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}" + }, + "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 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", + "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 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", + "name": "RegisterWebhooks", + "description": "", + "params": [ + { + "name": "options", + "description": "", + "value": "RegisterWebhooksOptions", + "filePath": "src/server/types.ts" + } + ], + "returns": { + "filePath": "src/server/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "type RegisterWebhooks = (\n options: RegisterWebhooksOptions,\n) => Promise;" + }, + "RegisterWebhooksOptions": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "RegisterWebhooksOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The Shopify session used to register webhooks using the Admin API." + } + ], + "value": "export interface RegisterWebhooksOptions {\n /**\n * The Shopify session used to register webhooks using the Admin API.\n */\n session: Session;\n}" + }, + "SessionStorageType": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "SessionStorageType", + "value": "Config['sessionStorage'] extends SessionStorage\n ? Config['sessionStorage']\n : SessionStorage", + "description": "" + }, + "Unauthenticated": { + "filePath": "src/server/unauthenticated/types.ts", + "name": "Unauthenticated", + "description": "", + "members": [ + { + "filePath": "src/server/unauthenticated/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "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": [ + { + "title": "Responding to a request not controlled by Shopify", + "description": "", + "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 = await admin.graphql(\"{ shop { name} }\")\n\n return json(await response.json());\n}", + "title": "/app/routes/**\\/*.jsx" + } + ] + } + ] + }, + { + "filePath": "src/server/unauthenticated/types.ts", + "syntaxKind": "PropertySignature", + "name": "storefront", + "value": "GetUnauthenticatedStorefrontContext", + "description": "Get a storefront 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": [ + { + "title": "Responding to a request not controlled by Shopify", + "description": "", + "tabs": [ + { + "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 {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}", + "title": "/app/routes/**\\/*.jsx" + } + ] + } + ] + } + ], + "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", + "name": "GetUnauthenticatedAdminContext", + "description": "", + "params": [ + { + "name": "shop", + "description": "", + "value": "string", + "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": "src/server/unauthenticated/admin/types.ts", + "syntaxKind": "PropertySignature", + "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": "src/server/unauthenticated/admin/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 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 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}" + }, + "OnlineAccessInfo": { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "name": "OnlineAccessInfo", + "description": "", + "members": [ + { + "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", + "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." + } + ], + "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/lib/auth/oauth/types.ts", + "name": "OnlineAccessUser", + "description": "", + "members": [ + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "name": "account_owner", + "value": "boolean", + "description": "Whether the user is the account owner." + }, + { + "filePath": "../shopify-api/lib/auth/oauth/types.ts", + "syntaxKind": "PropertySignature", + "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 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/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}" + }, + "SessionParams": { + "filePath": "../shopify-api/lib/session/types.ts", + "name": "SessionParams", + "description": "", + "members": [ + { + "filePath": "../shopify-api/lib/session/types.ts", + "name": "[key: string]", + "value": "any" + }, + { + "filePath": "../shopify-api/lib/session/types.ts", + "syntaxKind": "PropertySignature", + "name": "accessToken", + "value": "string", + "description": "The access token for the session.", + "isOptional": true + }, + { + "filePath": "../shopify-api/lib/session/types.ts", + "syntaxKind": "PropertySignature", + "name": "expires", + "value": "Date", + "description": "The date the access token expires.", + "isOptional": true + }, + { + "filePath": "../shopify-api/lib/session/types.ts", + "syntaxKind": "PropertySignature", + "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": "../shopify-api/lib/session/types.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/lib/session/types.ts", + "syntaxKind": "PropertySignature", + "name": "scope", + "value": "string", + "description": "The scopes for the access token.", + "isOptional": true + }, + { + "filePath": "../shopify-api/lib/session/types.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "The Shopify shop domain." + }, + { + "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." + } + ], + "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/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": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "name": "update", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "interface SaveArgs {\n update?: boolean;\n}" + }, + "Body": { + "filePath": "../shopify-api/rest/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "Body", + "value": "Record", + "description": "", + "members": [] + }, + "RequestArgs": { + "filePath": "../shopify-api/rest/base.ts", + "name": "RequestArgs", + "description": "", + "members": [ + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "name": "body", + "value": "Body | null", + "description": "", + "isOptional": true + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "name": "entity", + "value": "Base | null", + "description": "", + "isOptional": true + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "name": "http_method", + "value": "string", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "name": "operation", + "value": "string", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "name": "params", + "value": "ParamSet", + "description": "", + "isOptional": true + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "name": "requireIds", + "value": "boolean", + "description": "", + "isOptional": true + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../shopify-api/rest/base.ts", + "syntaxKind": "PropertySignature", + "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": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "body", + "value": "T", + "description": "" + }, + { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "Headers", + "description": "" + }, + { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "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": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "fields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "limit", + "value": "string", + "description": "" + }, + { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "nextPage", + "value": "PageInfoParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "nextPageUrl", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "previousPageUrl", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "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": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "path", + "value": "string", + "description": "" + }, + { + "filePath": "../shopify-api/lib/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "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": "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": "src/server/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" + } + ] + } + ] + }, + { + "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": "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": "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": "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 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}" + }, + "ShopifyAppLogin": { + "filePath": "src/server/types.ts", + "name": "ShopifyAppLogin", + "description": "", + "members": [ + { + "filePath": "src/server/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": "request", + "description": "", + "value": "Request", + "filePath": "src/server/types.ts" + } + ], + "returns": { + "filePath": "src/server/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "type Login = (request: Request) => Promise;" + }, + "LoginError": { + "filePath": "src/server/types.ts", + "name": "LoginError", + "description": "", + "members": [ + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "LoginErrorType", + "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" + } + ] + }, + "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": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "_logDisabledFutureFlags", + "value": "boolean", + "description": "Whether to log disabled future flags at startup.", + "isOptional": true, + "isPrivate": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "adminApiAccessToken", + "value": "string", + "description": "An app-wide API access token.\n\nOnly applies to custom apps.", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "apiKey", + "value": "string", + "description": "The API key for your app.\n\nAlso known as Client ID in your Partner Dashboard.", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "apiSecretKey", + "value": "string", + "description": "The API secret key for your app.\n\nAlso known as Client Secret in your Partner Dashboard." + }, + { + "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": "src/server/config-types.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." + }, + { + "filePath": "src/server/config-types.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" + } + ] + } + ] + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "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": "src/server/config-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 + }, + { + "filePath": "src/server/config-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.", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "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": "src/server/config-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`" + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "isTesting", + "value": "boolean", + "description": "Whether the app is initialised for local testing.", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "logger", + "value": "{ log?: LogFunction; level?: LogSeverity; httpRequests?: boolean; timestamps?: boolean; }", + "description": "Customization options for Shopify logs.", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "privateAppStorefrontAccessToken", + "value": "string", + "description": "An app-wide API access token for the storefront API.\n\nOnly applies to custom apps.", + "isOptional": true + }, + { + "filePath": "src/server/config-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/*`.", + "isOptional": true + }, + { + "filePath": "src/server/config-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 + }, + { + "filePath": "src/server/config-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, + "examples": [ + { + "title": "Storing sessions with Prisma", + "description": "Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.", + "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" + } + ] + } + ] + }, + { + "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", + "syntaxKind": "PropertySignature", + "name": "userAgentPrefix", + "value": "string", + "description": "The user agent prefix to use for API requests.", + "isOptional": true + }, + { + "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" + } + ] + } + ] + } + ], + "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}" + }, + "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" + } + ] + }, + "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": "" + }, + "FeatureEnabled": { + "filePath": "../shopify-api/dist/ts/future/flags.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "FeatureEnabled", + "value": "Future extends FutureFlags ? Future[Flag] extends true ? true : false : false", + "description": "" + }, + "FutureFlags": { + "filePath": "../shopify-api/dist/ts/future/flags.d.ts", + "name": "FutureFlags", + "description": "Future flags are used to enable features that are not yet available by default.", + "members": [ + { + "filePath": "../shopify-api/dist/ts/future/flags.d.ts", + "syntaxKind": "PropertySignature", + "name": "customerAddressDefaultFix", + "value": "boolean", + "description": "Change the CustomerAddress classes to expose a `is_default` property instead of `default` when fetching data. This resolves a conflict with the default() method in that class.", + "isOptional": true + }, + { + "filePath": "../shopify-api/dist/ts/future/flags.d.ts", + "syntaxKind": "PropertySignature", + "name": "lineItemBilling", + "value": "boolean", + "description": "Enable line item billing, to make billing configuration more similar to the GraphQL API.", + "isOptional": true + }, + { + "filePath": "../shopify-api/dist/ts/future/flags.d.ts", + "syntaxKind": "PropertySignature", + "name": "unstable_managedPricingSupport", + "value": "boolean", + "description": "Enable support for managed pricing, so apps can check for payments without needing a billing config.", + "isOptional": true + }, + { + "filePath": "../shopify-api/dist/ts/future/flags.d.ts", + "syntaxKind": "PropertySignature", + "name": "v10_lineItemBilling", + "value": "boolean", + "description": "Enable line item billing, to make billing configuration more similar to the GraphQL API. Default enabling of this feature has been moved to v11. Use lineItemBilling instead.", + "isOptional": true + } + ], + "value": "export interface FutureFlags {\n /**\n * Enable line item billing, to make billing configuration more similar to the GraphQL API. Default enabling of this\n * feature has been moved to v11. Use lineItemBilling instead.\n */\n v10_lineItemBilling?: boolean;\n /**\n * Enable line item billing, to make billing configuration more similar to the GraphQL API.\n */\n lineItemBilling?: boolean;\n /**\n * Enable support for managed pricing, so apps can check for payments without needing a billing config.\n */\n unstable_managedPricingSupport?: boolean;\n /**\n * Change the CustomerAddress classes to expose a `is_default` property instead of `default` when fetching data. This\n * resolves a conflict with the default() method in that class.\n */\n customerAddressDefaultFix?: boolean;\n}" + }, + "BillingConfigOneTimePlan": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigOneTimePlan", + "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", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.OneTime", + "description": "Interval for this plan.\n\nMust be set to `OneTime`." + } + ], + "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": [ + { + "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" + } + ] + }, + "BillingConfigSubscriptionLineItemPlan": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionLineItemPlan", + "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 + }, + { + "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 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}" + }, + "BillingConfigRecurringLineItem": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigRecurringLineItem", + "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", + "syntaxKind": "PropertySignature", + "name": "discount", + "value": "BillingConfigSubscriptionPlanDiscount", + "description": "An optional discount to apply for this line item.", + "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}" + }, + "BillingConfigSubscriptionPlanDiscount": { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscount", + "description": "", + "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": "../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/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}" + }, + "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/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", + "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/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/types.d.ts", + "name": "ApplyImmediately", + "value": "APPLY_IMMEDIATELY" + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "ApplyOnNextBillingCycle", + "value": "APPLY_ON_NEXT_BILLING_CYCLE" + }, + { + "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", + "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", + "syntaxKind": "PropertySignature", + "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 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": [ + { + "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", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.Usage", + "description": "Interval for this plan.\n\nMust be set to `Usage`." + }, + { + "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 many trial days to give before charging for this plan.", + "isOptional": true + }, + { + "filePath": "../shopify-api/dist/ts/lib/billing/types.d.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": [ + { + "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": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + } + ], + "value": "export interface AfterAuthOptions<\n ConfigArg extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n session: Session;\n admin: AdminApiContext;\n}" + }, + "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/types.d.ts", + "name": "Error", + "value": 0 + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Warning", + "value": 1 + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Info", + "value": 2 + }, + { + "filePath": "../shopify-api/dist/ts/lib/types.d.ts", + "name": "Debug", + "value": 3 + } + ] + }, + "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": "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", + "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" + } + ], + "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, 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": [ + { + "name": "shop", + "description": "", + "value": "string", + "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": "src/server/unauthenticated/admin/types.ts", + "syntaxKind": "PropertySignature", + "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": "src/server/unauthenticated/admin/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 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 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}" + } + } + } + ], + "jsDocTypeExamples": [ + "UnauthenticatedAdminContext" + ], + "related": [ + { + "name": "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 `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 { 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}", + "language": "typescript" + }, + { + "title": "/app/shopify.server.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;", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "session", + "examples": [ + { + "description": "Get your app's shop-specific data using the returned offline `session` object.", + "codeblock": { + "title": "Using the offline session", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "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};", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "Unauthenticated storefront", + "description": "Allows interacting with the Storefront 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.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}" + }, + "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}" + } + } + } + ], + "jsDocTypeExamples": [ + "UnauthenticatedStorefrontContext" + ], + "related": [ + { + "name": "API context", + "subtitle": "Interact with the Storefront API.", + "url": "/docs/api/shopify-app-remix/apis/storefront-api" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "session", + "examples": [ + { + "description": "Get your app's shop-specific data using the returned offline `session` object.", + "codeblock": { + "title": "Using the offline session", + "tabs": [ + { + "title": "app/routes/**\\/.ts", + "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};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "storefront", + "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 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}", + "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 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}", + "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 unauthenticated = shopify.unauthenticated;", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + } +] \ No newline at end of file diff --git a/packages/apps/shopify-app-remix/docs/temp/generated_static_pages.json b/packages/apps/shopify-app-remix/docs/temp/generated_static_pages.json new file mode 100644 index 0000000000..6ca0ec4599 --- /dev/null +++ b/packages/apps/shopify-app-remix/docs/temp/generated_static_pages.json @@ -0,0 +1,640 @@ +[ + { + "id": "guide-admin", + "title": "Interacting with Shopify Admin", + "description": "Once you [set up your backend](/docs/api/shopify-app-remix#shopify-app), you can use the [`authenticate.admin` function](/docs/api/shopify-app-remix/authenticate/admin) to integrate your app with Shopify Admin.\n\nThis function works for both embedded and non-embedded apps, and ensures the app is installed on the current store.\n\nIt returns a context with functions to enable loaders and actions to respond to any requests made by or in Shopify Admin.\n\nThis page goes over the basics of authenticating those requests, and some of the things you can do with it, like querying the Admin API.", + "sections": [ + { + "type": "Generic", + "anchorLink": "auth", + "title": "Authenticating requests", + "sectionContent": "To authenticate admin requests you can call `authenticate.admin(request)` in a loader or an action.\n\nIf there's a session for this user, then this loader will return null. If there's no session for the user, then the loader will throw the appropriate redirect Response.\n\n> Tip: If you are authenticating more than one route, then we recommend using [Remix layout routes](https://remix.run/docs/en/1.18.1/file-conventions/routes-files#layout-routes) to automatically authenticate them.", + "codeblock": { + "title": "Authenticating requests", + "tabs": [ + { + "title": "/app/routes/**/*.tsx", + "code": "import {LoaderFunction, ActionFunction} from '@remix-run/node';\n\nimport {authenticate} from '~/shopify.server';\n\nexport const loader: LoaderFunction = async ({request}) => {\n await authenticate.admin(request);\n\n // App logic\n\n return null;\n};\n\nexport const action: ActionFunction = async ({request}) => {\n await authenticate.admin(request);\n\n // App logic\n\n return null;\n};\n", + "language": "tsx" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "headers", + "title": "Headers", + "sectionContent": "The OAuth process can't happen inside the admin iframe, and this package is capable of detecting that scenario and properly redirecting using the [Remix `ErrorBoundary`](https://remix.run/docs/en/main/guides/errors) export to set the correct headers for App Bridge.\n\nUse the abstractions provided by this package in your authenticated routes, to automatically set up the error and headers boundaries to redirect outside the iframe when needed.\n\n> Tip: You can also add this to a [Remix layout](https://remix.run/docs/en/main/file-conventions/route-files-v2) if you want to authenticate more than one route, but make sure to call the Shopify boundary methods whenever you need to add your own exports.", + "codeblock": { + "title": "Configure header boundaries", + "tabs": [ + { + "title": "/app/routes/**/*.tsx", + "code": "import {useRouteError} from '@remix-run/react';\nimport {boundary} from '@shopify/shopify-app-remix';\n\nexport function ErrorBoundary() {\n return boundary.error(useRouteError());\n}\n\nexport const headers = (headersArgs) => {\n return boundary.headers(headersArgs);\n};\n", + "language": "tsx" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "cors-auth", + "title": "Authenticating cross-origin admin requests", + "sectionContent": "If your Remix server is authenticating an admin extension, then a request from the extension to Remix will be cross-origin.\n\nHere `authenticate.admin` provides a `cors` function to add the required cross-origin headers.", + "codeblock": { + "title": "Add cross-origin headers", + "tabs": [ + { + "title": "/app/routes/**/*.tsx", + "code": "import {json, LoaderFunction} from '@remix-run/node';\n\nimport {authenticate} from '~/shopify.server';\n\nexport const loader: LoaderFunction = async ({request}) => {\n const {cors} = await authenticate.admin(request);\n\n // App logic\n\n return cors(json({my: 'data'}));\n};\n", + "language": "tsx" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "graphql-api", + "title": "Using the GraphQL API", + "sectionContent": "Once a request is authenticated, `authenticate.admin` will return an `admin` object that contains a GraphQL client that can interact with the [GraphQL Admin API](/docs/api/admin-graphql).", + "codeblock": { + "title": "Make GraphQL requests", + "tabs": [ + { + "title": "/app/routes/**/*.tsx", + "code": "import {ActionFunction, json} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action: ActionFunction = async ({request}) => {\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: {\n title: 'New product',\n variants: [{price: 100}],\n },\n },\n },\n );\n const parsedResponse = await response.json();\n\n return json({data: parsedResponse.data});\n};\n", + "language": "tsx" + } + ] + }, + "sectionCard": [ + { + "url": "/docs/api/shopify-app-remix/guide-graphql-types", + "name": "Typing GraphQL operations", + "type": "tutorial" + } + ] + }, + { + "type": "Generic", + "anchorLink": "rest-api", + "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": [ + { + "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});\n\nexport const authenticate = shopify.authenticate;\n", + "language": "tsx" + }, + { + "title": "/app/routes/**/*.tsx", + "code": "import {LoaderFunction, json} from '@remix-run/node';\nimport {useRouteError} from '@remix-run/react';\n\nimport {authenticate} from '~/shopify.server';\n\nexport const loader: LoaderFunction = async ({request}) => {\n const {admin, session} = await authenticate.admin(request);\n\n // Use REST resources\n const data = await admin.rest.resources.Product.count({session});\n\n // Or use the REST client\n const response = await admin.rest.get({path: 'products/count'});\n const data = response.body;\n\n return json({productCount: data.count});\n};\n", + "language": "tsx" + } + ] + } + }, + { + "type": "Resource", + "title": "Resources", + "anchorLink": "resources", + "resources": [ + { + "name": "authenticate.admin", + "url": "/docs/api/shopify-app-remix/authenticate/admin" + } + ] + } + ] + }, + { + "id": "guide-custom-apps", + "title": "Custom apps", + "description": "You can use this package to build apps that are distributed in the Shopify Admin, also known as merchant custom apps.\nThese apps do not Authorize by OAuth, and instead use a access token that has been generated by the Shopify Admin.\n\n> Note: Before creating a new app to be distributed with the shopify admin, you should be familiar with the [limitations](/docs/apps/launch/distribution#capabilities-and-requirements) of the different distribution types", + "sections": [ + { + "type": "Generic", + "anchorLink": "config-credentials", + "title": "Configure your app", + "sectionContent": "After you have [created and configured your app](https://help.shopify.com/en/manual/apps/app-types/custom-apps) in\nthe Shopify Admin update your code with the API Key, API Secret Key, and the access token.", + "codeblock": { + "title": "Configure your app credentials", + "tabs": [ + { + "title": "shopify.server.ts", + "code": "const shopify = shopifyApp({\n apiKey: \"your-api-key\",\n apiSecretKey: \"your-api-secret-key\",\n adminApiAccessToken:\"shpat_1234567890\",\n ...\n}\n", + "language": "ts" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "config-settings", + "title": "Configure your app settings", + "sectionContent": "Configure `shopifyApp` with the following values\n1. `distribution` - `AppDistribution.ShopifyAdmin`\n1. `appUrl` - `https://localhost:3000` or other configured port\n1. `isEmbeddedApp` - `false`, merchant custom apps cannot be embedded", + "codeblock": { + "title": "Configure app settings", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "code": "const shopify = shopifyApp({\n apiKey: \"your-api-key\",\n apiSecretKey: \"your-api-secret-key\",\n adminApiAccessToken:\"shpat_1234567890\",\n distribution: AppDistribution.ShopifyAdmin,\n appUrl: \"https://localhost:3000\",\n isEmbeddedApp: false,\n ...\n}\n", + "language": "tsx" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "local-dev", + "title": "Run your app locally", + "sectionContent": "Merchant custom apps are not compatible with the Shopify CLI, so you must start your app directly.\nAfter your app is running you can access it at the following URL: `http://localhost:3000/app?shop=my-shop.myshopify.com`", + "codeblock": { + "title": "Run your app", + "tabs": [ + { + "title": "npm", + "language": "sh", + "code": "npm exec remix vite:dev\n" + }, + { + "title": "yarn", + "language": "sh", + "code": "yarn remix vite:dev\n" + }, + { + "title": "pnpm", + "language": "sh", + "code": "pnpm exec remix vite:dev\n" + } + ] + } + }, + { + "type": "Resource", + "title": "Resources", + "anchorLink": "resources", + "resources": [ + { + "name": "Custom apps", + "subtitle": "Create a new custom app", + "url": "https://help.shopify.com/en/manual/apps/app-types/custom-apps", + "type": "shopify" + }, + { + "name": "App distribution", + "subtitle": "Understand the different distribution types", + "url": "/docs/apps/launch/distribution", + "type": "shopify" + } + ] + } + ] + }, + { + "id": "guide-future-flags", + "title": "Future flags", + "description": "Similarly to how [Remix approaches breaking changes](https://remix.run/docs/en/main/start/future-flags), the `@shopify/shopify-app-remix` package also uses future flags.\n\nBigger features and breaking changes are initially added behind a future flag. This means that they're disabled by default, and must be manually enabled by setting the appropriate flag in the `future` option of the `shopifyApp` function.\n\nThis allows apps to gradually adopt new features, and prepare for breaking changes and major releases ahead of time.", + "sections": [ + { + "type": "Generic", + "anchorLink": "configuration", + "title": "Setting future flags", + "sectionContent": "To opt in to a feature, simply enable the appropriate flag in the `future` option of the `shopifyApp` function.\n\nOnce a flag is set, the returned `shopify` object will start using the new APIs, including using any new types. That allows apps to rely on TypeScript to use a feature regardless of a flag being enabled or not.", + "codeblock": { + "title": "Enable future flags", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "language": "ts", + "code": "import {shopifyApp} from '@shopify/shopify-app-remix/server';\n\nexport const shopify = shopifyApp({\n // ...\n future: {\n unstable_newFeature: true,\n },\n});\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "unstable-apis", + "title": "Unstable APIs", + "sectionContent": "When introducing new features to the package for which we want to gather feedback, we will add them behind a future flag, starting with the `unstable_` prefix.\n\nThat allows early adopters to try them out individually, without having to install a release candidate package.\n\nWhen the feature is ready for release, the future flag will be removed and it will be available by default.\n\nIn this example, `shopify` has a new function called `newFeature`. If the future flag is disabled, TypeScript will be unaware of the new function, and the app will fail to compile if it tries to use it.", + "codeblock": { + "title": "Use unstable APIs", + "tabs": [ + { + "title": "/app/routes/*.tsx", + "language": "ts", + "code": "import type {LoaderFunctionArgs} from '@remix-run/node';\n\nimport {shopify} from '~/shopify.server';\n\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n const result = shopify.newFeature(params);\n\n return null;\n};\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "breaking-changes", + "title": "Breaking 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", + "anchorLink": "flags", + "title": "Supported flags", + "sectionContent": "These are the future flags supported in the current version.", + "listItems": [ + { + "name": "unstable_newEmbeddedAuthStrategy", + "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 + } + ] + } + ] + }, + { + "id": "guide-graphql-types", + "title": "Typing GraphQL operations", + "description": "The GraphQL clients provided in this package can use [Codegen](https://the-guild.dev/graphql/codegen) to automatically parse and create types for your queries and mutations.\n\nBy installing a few packages in your app, you can use the `graphql-codegen` script, which will look for strings with the `#graphql` tag and extract types from them.\n\nIf your IDE supports it, you will also get syntax highlighting and auto-complete features when writing your queries.", + "sections": [ + { + "type": "Markdown", + "anchorLink": "example", + "title": "See it in action", + "sectionContent": "\nIn this example, we use the `graphql-codegen` script to parse a query in the `/app/routes/new.tsx` file.\n\nNote how VSCode shows the types for both the return type of `response.json()`, and the `variables` option in the `graphql` function.\n\n\n " + }, + { + "type": "Generic", + "anchorLink": "install", + "title": "Installing packages", + "sectionContent": "To use the `graphql-codegen` script, you will need to install a few packages in your app.\n\nThey will include the scripts to run, and the types that will be overridden by the results of parsing your operations.", + "codeblock": { + "title": "Installing packages", + "tabs": [ + { + "title": "npm", + "language": "sh", + "code": "npm add --save-dev @shopify/api-codegen-preset\nnpm add @shopify/admin-api-client @shopify/storefront-api-client\n" + }, + { + "title": "yarn", + "language": "sh", + "code": "yarn add --dev @shopify/api-codegen-preset\nyarn add @shopify/admin-api-client @shopify/storefront-api-client\n" + }, + { + "title": "pnpm", + "language": "sh", + "code": "pnpm add --save-dev @shopify/api-codegen-preset\npnpm add @shopify/admin-api-client @shopify/storefront-api-client\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "configure", + "title": "Setting up .graphqlrc.ts", + "sectionContent": "Before you can parse operations, you'll need to create a `.graphqlrc.ts` file in your project, and configure it to use the `@shopify/api-codegen-preset`.\n\n> Caution: Parsing will not work on `.graphql` documents, because the preset can only apply types from JavaScript and TypeScript const strings.", + "codeblock": { + "title": "Codegen configuration example", + "tabs": [ + { + "title": "/.graphqlrc.ts", + "language": "ts", + "code": "import {shopifyApiProject, ApiType} from '@shopify/api-codegen-preset';\n\nexport default {\n // For syntax highlighting / auto-complete when writing operations\n schema: 'https://shopify.dev/admin-graphql-direct-proxy/2023-10',\n documents: ['./app/**/*.{js,ts,jsx,tsx}'],\n projects: {\n // To produce variable / return types for Admin API operations\n default: shopifyApiProject({\n apiType: ApiType.Admin,\n apiVersion: '2023-10',\n documents: ['./app/**/*.{js,ts,jsx,tsx}'],\n outputDir: './app/types',\n }),\n },\n};\n" + } + ] + }, + "sectionCard": [ + { + "url": "https://github.com/Shopify/shopify-app-js/tree/main/packages/api-clients/api-codegen-preset#configuration", + "name": "Configuration options", + "subtitle": "Learn more about the available configurations.", + "type": "github" + } + ] + }, + { + "type": "Generic", + "anchorLink": "set-up-script", + "title": "Setting up the script", + "sectionContent": "To generate types, you'll need to add an entry for `graphql-codegen` in the `\"scripts\"` section of your `package.json` file.", + "codeblock": { + "title": "Setting up the script", + "tabs": [ + { + "title": "/package.json", + "language": "json", + "code": "{\n \"scripts\": {\n \"graphql-codegen\": \"graphql-codegen\"\n }\n}\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "run", + "title": "Generating types", + "sectionContent": "When you run `graphql-codegen`, it will look in all your configured documents for strings marked with a `#graphql` tag.\n\nIDEs that support the `.graphqlrc.ts` file will provide syntax highlighting for your operations, as well as typing.\n\n> Tip: You can pass in a `--watch` flag to the script, which will update your types every time you save a file.", + "codeblock": { + "title": "Running graphql-codegen", + "tabs": [ + { + "title": "npm", + "language": "sh", + "code": "npm run graphql-codegen\n" + }, + { + "title": "yarn", + "language": "sh", + "code": "yarn graphql-codegen\n" + }, + { + "title": "pnpm", + "language": "sh", + "code": "pnpm graphql-codegen\n" + } + ] + } + }, + { + "type": "Resource", + "title": "Resources", + "anchorLink": "resources", + "resources": [ + { + "name": "Admin API", + "url": "/docs/api/shopify-app-remix/apis/admin-api", + "type": "shopify", + "subtitle": "Make requests to the Admin API" + }, + { + "name": "Storefront API", + "url": "/docs/api/shopify-app-remix/apis/storefront-api", + "type": "shopify", + "subtitle": "Make requests to the Storefront API" + } + ] + } + ] + }, + { + "id": "shopify-app-remix", + "title": "Shopify App package for Remix", + "description": "The [@shopify/shopify-app-remix](https://www.npmjs.com/package/@shopify/shopify-app-remix) package enables Remix apps to authenticate with Shopify and make API calls. It uses [App Bridge](/docs/api/app-bridge-library) to enable apps to embed themselves in the Shopify Admin.\n\nIn this page we'll go over the main components you need to integrate an app with Shopify.", + "sections": [ + { + "type": "Generic", + "anchorLink": "quick-start", + "title": "Quick start", + "sectionContent": "The quickest way to create a new app is using the Shopify CLI. You can use your preferred package manager for that.\n\nCheck out the [getting started guide](/docs/apps/getting-started), or the [app template](https://github.com/Shopify/shopify-app-template-remix) for a complete example.", + "codeblock": { + "title": "Create an app", + "tabs": [ + { + "title": "npm", + "language": "sh", + "code": "npm init @shopify/app@latest\n" + }, + { + "title": "yarn", + "language": "sh", + "code": "yarn create @shopify/app\n" + }, + { + "title": "pnpm", + "language": "sh", + "code": "pnpm create @shopify/app\n" + } + ] + }, + "sectionCard": [ + { + "name": "Build an app", + "subtitle": "Navigate to", + "url": "/docs/apps/getting-started/build-qr-code-app", + "type": "tutorial" + } + ] + }, + { + "type": "Generic", + "anchorLink": "installation", + "title": "Installation", + "sectionContent": "If you're not using the CLI, then you can use the examples in this page to set up an existing app to use this package. Start by installing it using your preferred package manager.", + "codeblock": { + "title": "Install package", + "tabs": [ + { + "title": "npm", + "language": "sh", + "code": "npm i --save @shopify/shopify-app-remix\n" + }, + { + "title": "yarn", + "language": "sh", + "code": "yarn add @shopify/shopify-app-remix\n" + }, + { + "title": "pnpm", + "language": "sh", + "code": "pnpm add @shopify/shopify-app-remix\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "shopify-app", + "title": "Backend setup", + "sectionContent": "Using the `shopifyApp` function, you can create an object that enables your app's backend to authenticate requests coming from Shopify, and interacting with Shopify APIs.\n\nThese functions make it easy for your app stays up to date, benefitting from the current best practices and security updates.\n\n> Caution: When running on a node environment, you'll also need to import the node adapter, as per the example. This will ensure your app is using the appropriate implementation of the Web [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [crypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) APIs.", + "sectionCard": [ + { + "name": "shopifyApp", + "url": "/docs/api/shopify-app-remix/entrypoints/shopifyapp", + "type": "clicode" + } + ], + "codeblock": { + "title": "Configure ShopifyApp", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "language": "ts", + "code": "import '@shopify/shopify-app-remix/server/adapters/node';\nimport {\n LATEST_API_VERSION,\n shopifyApp,\n} 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 appUrl: process.env.SHOPIFY_APP_URL!,\n scopes: ['read_products'],\n apiVersion: LATEST_API_VERSION,\n});\nexport default shopify;\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "headers", + "title": "Response headers", + "sectionContent": "When loading inside the Shopify Admin, your app will need to add the required `Content-Security-Policy` header directives, as per [our documentation](/docs/apps/store/security/iframe-protection). To do that, this package provides the `shopify.addDocumentResponseHeaders` method.\n\nYou should return these headers from any endpoint that renders HTML in your app. Most likely you'll want to add this to every HTML response by updating the `entry.server.tsx` file:", + "codeblock": { + "title": "Add required headers", + "tabs": [ + { + "title": "/app/entry.server.tsx", + "language": "tsx", + "code": "import shopify from './shopify.server';\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext,\n) {\n shopify.addDocumentResponseHeaders(request, responseHeaders);\n\n // ..etc\n}\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "boundaries", + "title": "Error boundaries", + "sectionContent": "The OAuth process can't happen inside the admin iframe, and this package is capable of detecting that scenario and properly redirecting using the [Remix `ErrorBoundary`](https://remix.run/docs/en/main/guides/errors) export to set the correct headers for App Bridge.\n\nUse the abstractions provided by this package in your authenticated routes, to automatically set up the error and headers boundaries to redirect outside the iframe when needed.\n\n> Tip: You can also add this to a [Remix layout](https://remix.run/docs/en/main/file-conventions/route-files-v2) if you want to authenticate more than one route, but make sure to call the Shopify boundary methods whenever you need to add your own exports.", + "codeblock": { + "title": "Configure header boundaries", + "tabs": [ + { + "title": "/app/routes/**/*.tsx", + "language": "tsx", + "code": "import {boundary} from '@shopify/shopify-app-remix/server';\n\nexport function ErrorBoundary() {\n return boundary.error(useRouteError());\n}\n\nexport const headers = (headersArgs) => {\n return boundary.headers(headersArgs);\n};\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "auth-route", + "title": "OAuth route", + "sectionContent": "> Tip: This is only applicable to non-embedded apps or legacy embedded apps that are **not** using the [new embedded app authorization strategy](#embedded-auth-strategy) for OAuth and installation flow. If you're building an embedded app, we **strongly** recommend using the [new embedded app authorization strategy](#embedded-auth-strategy)\n\nTo install an app or refresh tokens, you'll need to set up an [OAuth](docs/apps/auth/oauth) route. To do that, set up a [splat route](https://remix.run/docs/en/main/guides/routing#splats) that calls `authenticate.admin`.\n\nWhen that function is called, the package will start the OAuth process, and handle the callback from Shopify after it completes.\n\nThe default route is `/app/routes/auth/$.tsx`, but you can configure this route using the `authPathPrefix` option.", + "codeblock": { + "title": "Add OAuth route", + "tabs": [ + { + "title": "/app/routes/auth/$.tsx", + "language": "ts", + "code": "import {LoaderFunctionArgs} from '@remix-run/node';\n\nimport shopify from '~/shopify.server';\n\nexport async function loader({request}: LoaderFunctionArgs) {\n await shopify.authenticate.admin(request);\n\n // App logic goes here\n\n return null;\n}\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "embedded-auth-strategy", + "title": "New embedded app authorization strategy", + "sectionContent": "> Tip: This is available for embedded apps that are using [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation).\n> If you're building an embedded app, we **strongly** recommend using this feature that utilizes Shopify managed install with [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange).\n\n We have introduced a new authorization and installation strategy for **embedded apps** that eliminates the redirects that were previously necessary. It replaces the legacy [authorization Code install and grant flow](https://shopify.dev/docs/apps/auth/get-access-tokens/authorization-code-grant).\n\nIt takes advantage of [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation) to handle automatic app installations and scope updates, while using [token exchange](https://shopify.dev/docs/apps/auth/get-access-tokens/token-exchange) to get an access token for the logged-in user.\n\n If you wish to learn about scopes management and APIs, please read through [Manage access scopes](https://shopify.dev/docs/apps/build/authentication-authorization/app-installation/manage-access-scopes)\n\n > Note: Newly created Remix apps from the template after February 1st 2024 has this feature enabled by default.\n\n1. Enable [Shopify managed installation](https://shopify.dev/docs/apps/auth/installation#shopify-managed-installation) by configuring your scopes [through the Shopify CLI](https://shopify.dev/docs/apps/tools/cli/configuration).\n2. Enable the future flag `unstable_newEmbeddedAuthStrategy` in your app's server configuration file.\n3. Enjoy no-redirect OAuth flow, and app installation process.", + "codeblock": { + "title": "Enabling the new embedded auth strategy", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "language": "ts", + "code": "// ... imports\nconst shopify = shopifyApp({\n // .. and the rest of the config\n isEmbeddedApp: true,\n future: {\n unstable_newEmbeddedAuthStrategy: true,\n },\n)};\n\n// ... exports\n" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "app-provider", + "title": "AppProvider", + "sectionContent": "In order to use all of the features from App Bridge, you'll need to use the `AppProvider` component in your app's routes.\n\nThis component will set up App Bridge and Polaris so you can integrate your app into the Shopify Admin, and it helps us ensure your app stays up to date with Shopify requirements.\n\nTo do this pass the `process.env.SHOPIFY_API_KEY` to the frontend via the loader.", + "sectionCard": [ + { + "name": "App bridge", + "subtitle": "Learn more about App Bridge.", + "url": "/docs/api/app-bridge-library", + "type": "shopify" + }, + { + "name": "Polaris", + "subtitle": "Learn more about Polaris.", + "url": "https://polaris.shopify.com", + "type": "shopify" + }, + { + "name": "AppProvider", + "url": "/docs/api/shopify-app-remix/entrypoints/appprovider", + "type": "clicode" + } + ], + "codeblock": { + "title": "Add AppProvider", + "tabs": [ + { + "title": "/app/root.tsx", + "language": "tsx", + "code": "import {LoaderFunctionArgs} from '@remix-run/node';\nimport {AppProvider} from '@shopify/shopify-app-remix/react';\n\nimport shopify from '~/shopify.server';\n\nexport async function loader({request}: LoaderFunctionArgs) {\n await shopify.authenticate.admin(request);\n\n return json({\n apiKey: process.env.SHOPIFY_API_KEY,\n });\n}\n\nexport default function App() {\n const {apiKey} = useLoaderData<typeof loader>();\n\n return (\n <html>\n <head>\n <Meta />\n <Links />\n </head>\n <body>\n <AppProvider apiKey={apiKey} isEmbeddedApp>\n <Outlet />\n </AppProvider>\n </body>\n </html>\n );\n}\n" + } + ] + } + } + ] + }, + { + "id": "guide-webhooks", + "title": "Subscribing to webhooks", + "description": "Your app must respond to [mandatory webhook topics](/docs/apps/webhooks/configuration/mandatory-webhooks). In addition, your app can register [optional webhook topics](/docs/api/admin-rest/current/resources/webhook#event-topics).\n\nThere are app-specific and shop-specific webhooks. We recommend app-specific webhooks for most apps, but there are reasons to register shop-specific webhooks. For more information, please read [App-specific vs shop-specific webhooks](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions).", + "sections": [ + { + "type": "Generic", + "anchorLink": "config", + "title": "App-specific webhooks (recommended)", + "sectionContent": "The easiest way to configure webhooks is to use app-specific webhooks in `shopify.app.toml`. You can find more info in the [webhooks documentation](/docs/apps/webhooks/getting-started-declarative).\n\nTo set up a simple HTTPS webhook subscription, you can follow these steps:\n1. Add the topic to subscribe to in `shopify.app.toml`. In this example we subscribe to the `APP_UNINSTALLED` topic.\n1. Review the required scopes for the webhook topics, and update your [app scopes](/docs/apps/tools/cli/configuration#access_scopes) as necessary.\n1. Run `shopify app deploy` from the CLI to save your webhooks configuration.", + "codeblock": { + "title": "Configure app-specific webhooks", + "tabs": [ + { + "title": "shopify.app.toml", + "code": "[webhooks]\napi_version = \"2024-04\"\n\n [[webhooks.subscriptions]]\n topics = [ \"app/uninstalled\" ]\n uri = \"/webhooks\"\n compliance_topics = [ \"customers/data_request\", \"customers/redact\", \"shop/redact\" ]\n", + "language": "tsx" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "config", + "title": "Shop-specific webhooks", + "sectionContent": "Shop-specific webhooks are useful when you need to subscribe to different webhook topics for different shops, or when a topic is not supported by app-specific webhooks.Configure `shopifyApp` and to setup shop-specific webhook subscriptions with the following steps:\n1. The webhooks you want to subscribe to. In this example we subscribe to the `APP_UNINSTALLED` topic.\n1. The code to register the `APP_UNINSTALLED` topic after a merchant installs you app. Here `shopifyApp` provides an `afterAuth` hook.\n1. Review the required scopes for the webhook topics, and update your [app scopes](/docs/apps/tools/cli/configuration#access_scopes) as necessary.\n\n> Note: You can't register mandatory topics using this package, you must [configure those in the Partner Dashboard](/docs/apps/webhooks/configuration/mandatory-webhooks) instead.", + "codeblock": { + "title": "Configure shop-specific webhooks", + "tabs": [ + { + "title": "/app/shopify.server.ts", + "code": "import {shopifyApp, DeliveryMethod} from '@shopify/shopify-app-remix/server';\n\nconst shopify = shopifyApp({\n apiKey: 'abcde1234567890',\n // ...etc\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: '/webhooks',\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});\n\nexport const authenticate = shopify.authenticate;\n", + "language": "tsx" + } + ] + } + }, + { + "type": "Generic", + "anchorLink": "endpoints", + "title": "Set up your endpoints", + "sectionContent": "Create a route in your app to handle incoming webhook requests for each `callbackUrl` you set in your configuration.Legitimate webhook requests are always `POST` requests signed by Shopify, so you must authenticate them before taking any action. To do this you must set up an `action` that uses the `authenticate.webhook` function to authenticate the request.\n\nPlease keep in mind that webhook endpoints should respond as quickly as possible. If you need to run a long-running job, then consider using background tasks.\n\n> Caution: Webhook endpoints **must** respond with an `HTTP 200` code, or Shopify will retry.", + "codeblock": { + "title": "Receive webhook requests", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import {ActionFunctionArgs} from '@remix-run/node';\n\nimport db from '../db.server';\n\nimport {authenticate} from '~/shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {topic, shop, session} = await authenticate.webhook(request);\n\n switch (topic) {\n case 'APP_UNINSTALLED':\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 await db.session.deleteMany({where: {shop}});\n }\n break;\n case 'CUSTOMERS_DATA_REQUEST':\n case 'CUSTOMERS_REDACT':\n case 'SHOP_REDACT':\n default:\n throw new Response('Unhandled webhook topic', {status: 404});\n }\n\n throw new Response();\n};\n", + "language": "tsx" + } + ] + } + }, + { + "type": "Resource", + "title": "Resources", + "anchorLink": "resources", + "resources": [ + { + "name": "authenticate.webhook", + "url": "/docs/api/shopify-app-remix/authenticate/webhook" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/types.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/types.ts index c42b09ba51..4da4471f0b 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/types.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/types.ts @@ -78,7 +78,7 @@ export interface CreateUsageRecordOptions { /** * The amount to charge for this usage record. */ - amount: number; + amount: string; /** * The currency code for this usage record. */ From a30f6daa6d8e73381f22f962c891ce0338ae67ca Mon Sep 17 00:00:00 2001 From: Si Le Date: Tue, 10 Dec 2024 11:49:25 -0500 Subject: [PATCH 2/5] Updates changeset to patch --- .changeset/green-zoos-lie.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/green-zoos-lie.md b/.changeset/green-zoos-lie.md index b33f6f86de..9eb57c8659 100644 --- a/.changeset/green-zoos-lie.md +++ b/.changeset/green-zoos-lie.md @@ -1,6 +1,6 @@ --- -'@shopify/shopify-app-remix': minor -'@shopify/shopify-api': minor +'@shopify/shopify-app-remix': patch +'@shopify/shopify-api': patch --- Fixes Money.amount type from number to string to follow GraphQL schema Decimal type From 7ecc2bf653c1100264e6aaecfd75a5d76cf0e9c3 Mon Sep 17 00:00:00 2001 From: Si Le Date: Tue, 10 Dec 2024 13:27:56 -0500 Subject: [PATCH 3/5] fixes test case --- packages/apps/shopify-api/lib/billing/__tests__/responses.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apps/shopify-api/lib/billing/__tests__/responses.ts b/packages/apps/shopify-api/lib/billing/__tests__/responses.ts index 272a12b36d..fce23a10cd 100644 --- a/packages/apps/shopify-api/lib/billing/__tests__/responses.ts +++ b/packages/apps/shopify-api/lib/billing/__tests__/responses.ts @@ -301,7 +301,7 @@ export const SUBSCRIPTIONS_WITH_USAGE_PLANS_RESPONSE = JSON.stringify({ plan: { pricingDetails: { balanceUsed: { - amount: 0, + amount: '0', currencyCode: 'USD', }, cappedAmount: { @@ -339,7 +339,7 @@ export const SUBSCRIPTIONS_WITH_USAGE_PLANS_NOT_ACTIVE_RESPONSE = export const USAGE_RECORD_DESCRIPTION = 'Usage record description'; export const USAGE_RECORD_PRICE = { - amount: 1.0, + amount: '1.0', currencyCode: 'USD', } as Money; export const USAGE_RECORD_SUBSCRIPTION_ID = 'gid://456'; From 8b387d7b5774985edb3a107142b758266e008492 Mon Sep 17 00:00:00 2001 From: Si Le Date: Tue, 10 Dec 2024 14:01:36 -0500 Subject: [PATCH 4/5] Fixes shopify-remix-app test case --- .../authenticate/admin/billing/__tests__/mock-responses.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/mock-responses.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/mock-responses.ts index c4557f5e84..95f355c904 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/mock-responses.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/mock-responses.ts @@ -124,7 +124,7 @@ export const SUBSCRIPTIONS_WITH_USAGE_PLANS_RESPONSE = JSON.stringify({ export const USAGE_RECORD_DESCRIPTION = 'Usage record description'; export const USAGE_RECORD_PRICE = { - amount: 1.0, + amount: '1.0', currencyCode: 'USD', } as Money; export const USAGE_RECORD_SUBSCRIPTION_ID = 'gid://456'; From ed289763eb932a876b7a327191bde94b2e501157 Mon Sep 17 00:00:00 2001 From: Si Le Date: Tue, 10 Dec 2024 14:25:12 -0500 Subject: [PATCH 5/5] Fixes test --- .../admin/billing/__tests__/create-usage-record.test.ts | 8 ++++---- .../admin/billing/__tests__/mock-responses.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/create-usage-record.test.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/create-usage-record.test.ts index cd92439802..ee7d453b48 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/create-usage-record.test.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/create-usage-record.test.ts @@ -142,7 +142,7 @@ describe('Create usage record', () => { billing.createUsageRecord({ subscriptionLineItemId: '123', price: { - amount: 5, + amount: '5', currencyCode: 'USD', }, description: 'A usage record', @@ -185,7 +185,7 @@ describe('Create usage record', () => { billing.createUsageRecord({ subscriptionLineItemId: '123', price: { - amount: 5, + amount: '5', currencyCode: 'USD', }, description: 'A usage record', @@ -233,7 +233,7 @@ describe('Create usage record', () => { billing.createUsageRecord({ subscriptionLineItemId: '123', price: { - amount: 5, + amount: '5', currencyCode: 'USD', }, description: 'A usage record', @@ -285,7 +285,7 @@ describe('Create usage record', () => { subscriptionLineItemId: '123', isTest: true, price: { - amount: 5, + amount: '5', currencyCode: 'USD', }, description: 'A usage record', diff --git a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/mock-responses.ts b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/mock-responses.ts index 95f355c904..4446a764f6 100644 --- a/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/mock-responses.ts +++ b/packages/apps/shopify-app-remix/src/server/authenticate/admin/billing/__tests__/mock-responses.ts @@ -158,12 +158,12 @@ export const USAGE_RECORD_CREATE_RESPONSE = JSON.stringify({ export const USAGE_RECORD = { description: 'Usage record description', id: 'gid://123', - price: {amount: 1, currencyCode: 'USD'}, + price: {amount: '1.0', currencyCode: 'USD'}, subscriptionLineItem: { id: 'gid://456', plan: { pricingDetails: { - balanceUsed: {amount: 1, currencyCode: 'USD'}, + balanceUsed: {amount: '1.0', currencyCode: 'USD'}, cappedAmount: {amount: '5.00', currencyCode: 'USD'}, terms: '1 dollar per usage', },