All Tutorials

Create and manage a product subscription app extension

All Tutorials

Create and manage a product subscription app extension

Create and manage a product subscription app extension

This tutorial explains the structure and the different modes of a product subscription app extension, as well as some best practices for their implementation.

Requirements

Concepts and key terms

Containers

Containers are interface elements that are used to render your extension with Argo components. There are two different container types that you will use:

  • App overlay container: A full-screen container designed for creating or editing subscription plans. App overlay container screenshot

  • App modal container: A smaller overlay used for adding or removing subscription plans. In the app modal container, primary and secondary action buttons are responsible for triggering a custom behaviour, such as performing a resource update. App modal container screenshot

App-provided and Shopify-provided interface elements

When the product subscription app extension renders to the merchant in Shopify admin, it includes both app-provided and Shopify-provided interface elements:

  • The App Chrome is an area of a container that can't be directly controlled by the extension. Inside this area, the appearance and behavior of select UI elements, such as the primary and secondary actions can be configured using the container API.

  • The App Canvas is an area of a container where the app extension's content is rendered.

The following images show which interface elements are provided by the app and which are provided by Shopify in the app overlay and app modal containers:

Interface elements in the app overlay container

Interface elements that are app-provided and Shopify-provided in the app overlay container screenshot

Interface elements in the app modal container

Interface elements that are app-provided and Shopify-provided in the app modal container

Container API

The Container API allows limited control over App Chrome behaviors, such as setting the primary and secondary actions. Refer to the container API docs for a complete list of the available behaviors.

Modes

Extension modes represent the state of the extension, and helps Shopify determine which UI to display. The product subscription app extension uses the following modes:

  • Add: Add an existing selling plan group to a product or variant
  • Create: Create a new selling plan group
  • Edit: Edit an existing selling plan group
  • Remove: Remove an existing selling plan group from a product or variant

Make a request to your app’s server

With the extension scaffold created, you can setup requests to your app's server for each of the extension modes. The Fetch API is included in your extension when you create it using the Shopify App CLI. You can use the Fetch API to make authenticated calls to your app’s backend service.

Authentication

Extensions built with Argo use session tokens to authenticate requests between your extension and your app's backend server. A session token provides the information needed to verify that a request is coming from Shopify and from the correct shop. You must provide the session token in every request to your backend server.

For more information on authenticating requests between Argo and your backend server, refer to Argo extension authentication.

Create a new selling plan group

The Create mode allows a user to create a new selling plan group for the current product or one of its variants. The Create plan UI should display the plan title, the delivery frequency, and the discount percentage. The Create mode triggers the overlay container.

The call to your backend server should trigger a call to the Shopify GraphQL Admin API's sellingPlanGroupsCreate mutation.

Example input data passed to Create mode:

interface CreatePayload {
  productId: string;
  variantId?: string;
}

Example onPrimaryAction in Create mode:

const onPrimaryAction = useCallback(async () => {
  const token = await getSessionToken();

  // The product and variant ID's collected from the modal form
  let payload = {
    productId: data.productId,
    variantId: data.variantId,
  };

  // Here, send the form data to your app server to create the new plan.
  const response = await fetch('https://server-url-here', {
    headers: {
      'any-header-key': token || 'unknown token',
    },
    body: JSON.stringify(payload)
  });

  // If the server responds with an OK status, then refresh the UI and close the modal
  if (response.ok) {
    done();
  } else {
    console.log('Handle error.');
  }

  close();
}, [getSessionToken, done, close]);

In the payload, include the productId and variantId collected from the extension form. In the header, include the session token. If the response returns an OK status, then you can call done to re-render the modal and close to close it.

Add an existing selling plan group to a product or variant

The Add mode allows a user to add the current product to an existing selling plan group. The Add plan UI should display a searchable list of selling plan groups on the shop. Selling plan groups that are already associated with the product either directly or through one of its variants should not be listed. The Add mode triggers the modal container.

The call to your backend server should trigger a call to the Shopify GraphQL Admin API's productJoinSellingPlanGroups mutation, applying the selected plans to the current product.

Example input data passed to Add mode:

interface AddPayload {
  productId: string;
  variantId?: null;
}

Example primaryAction in Add mode:

setPrimaryAction({
  content: 'Add to plan',
  onAction: async () => {
    // Get a fresh session token before every call to your app server.
    const token = await getSessionToken();

    // The product and variant ID's collected from the modal form
    let payload = {
      productId: data.productId,
      variantId: data.variantId,
    };

    // Here, send the form data to your app server to add the product to an existing plan.
    const response = await fetch('https://server-url-here', {
      headers: {
        'any-header-key': token || 'unknown token',
      },
      body: JSON.stringify(payload)
    });

    // If the server responds with an OK status, then refresh the UI and close the modal
    if (response.ok) {
      done();
    } else {
      console.log('Handle error.');
    }

    close();
  },
});

In the payload, include the productId and variantId collected from the extension form. In the header, include the session token. If the response returns an OK status, then you can call done to re-render the modal and close to close it.

Edit an existing selling plan group

The Edit mode allows a user to edit a product's selling plan group. The Edit plan UI should display the plan title, the delivery frequency, and the discount percentage. The edit mode triggers the overlay container.

The call to your backend service should trigger a call to the Shopify GraphQL Admin API's sellingPlanGroupUpdate mutation, applying the selected plans to the current product.

Input data passed to edit mode:

interface EditPayload {
  sellingPlanGroupId: string;
  productId: string;
  variantId?: string;
}

Example onPrimaryAction function in the edit mode:

const onPrimaryAction = useCallback(async () => {
  // Get a fresh session token before every call to your app server.
  const token = await getSessionToken();
  // The product ID and variant ID collected from the modal form and the selling plan group ID
  let payload = {
    sellingPlanGroupId: data.sellingPlanGroupId,
    productId: data.productId,
    variantId: data.variantId,
  };

  // Here, send the form data to your app server to add the product to an existing plan.
  const response = await fetch('https://server-url-here', {
    headers: {
      'any-header-key': token || 'unknown token',
    },
    body: JSON.stringify(payload)
  });
  // If the server responds with an OK status, then refresh the UI and close the modal
  if (response.ok) {
    done();
  } else {
    console.log('Handle error.');
  }
  close();
}, [getSessionToken, done, close]);

In the payload, include the productId and variantId collected from the extension form and the sellingPlanGroupId. In the header, include the session token. If the response returns an OK status, then you can call done to re-render the modal and close to close it.

Remove an existing selling plan group from a product or variant

The Remove mode allows a user to remove a product's selling plan group. The Remove plan UI should display the plan and product titles. The Remove mode triggers the modal container.

The call to your backend server should trigger a call to one of the following mutations:

  • If the selling plan group is associated at the product level, sellingPlanGroup.appliesToProduct is true, then you should call the sellingPlanGroupRemoveProducts mutation.
  • If the selling plan group is associated at the variant level, sellingPlanGroup.appliesToProductVariants is true, then you should call the sellingPlanGroupRemoveProductVariants mutation, passing the list of variantIds.

Example input data passed to Remove mode:

interface RemovePayload {
  sellingPlanGroupId: string;
  productId: string;
  variantId?: string;
  variantIds: string[];
}

Example primaryAction function in Remove mode:

setPrimaryAction({
  content: 'Add to plan',
  onAction: async () => {
    // Get a fresh session token before every call to your app server.
    const token = await getSessionToken();

    // The product ID, variant ID, variantIds, and the selling plan group ID
    let payload = {
      sellingPlanGroupId: data.sellingPlanGroupId,
      productId: data.productId,
      variantId: data.variantId,
      variantIds: data.variantIds,
    };

    // Here, send the form data to your app server to add the product to an existing plan.
    const response = await fetch('https://server-url-here', {
      headers: {
        'any-header-key': token || 'unknown token',
      },
      body: JSON.stringify(payload)
    });

    // If the server responds with an OK status, then refresh the UI and close the modal
    if (response.ok) {
      done();
    } else {
      console.log('Handle error.');
    }

    close();
  },
});

In the payload, include the productId, variantId, variantIds and the sellingPlanGroupId. In the header, include the session token. If the response returns an OK status, then you can call done to re-render the modal and close to close it.

Unit testing

To thoroughly test your extension, we recommend that you set up unit testing. You can use a testing framework that best suits your needs.

The underlying technology for Argo is remote-ui. @remote-ui/testing is a testing library that you can use to test subscription app extension components and their usage.

The APIs in @remote-ui/testing are inspired by the @shopify/react-testing library.

Next steps