Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 51

Building an Embedded App Page

Recommended resources

Hello! It's great to see you again.

In our last lesson, we mastered Shopify Polaris, learning how to build a user interface that looks and feels like a native part of the Shopify Admin. We now have the skills to create the "face" of our app. But a pretty face isn't enough; an app needs to be functional.

Today, we'll bring that UI to life by connecting it to the heart of the Shopify platform: the merchant's store data. This lesson synthesizes everything we've covered on App Bridge and Polaris, focusing on the complete, end-to-end flow of data from Shopify's servers to your app's front end.

Given your background in full-stack development, you'll find the pattern familiar: a backend process fetches data, and a frontend component renders it. We'll simply be using Shopify's specialized tools for each step.

By the end of this lesson, you will be able to create an embedded app page that can display and interact with store data.

The Anatomy of an Embedded App Page

Let's quickly revisit the architecture of an embedded app. As a developer, you are responsible for rendering the "App surface," but this surface must communicate with the Shopify Admin (via App Bridge) and present a consistent UI (via Polaris).

Shopify Embedded App Interface Breakdown
This diagram shows how different parts of the screen are rendered. Shopify handles the main admin chrome, App Bridge can render UI elements like the save bar, and your app renders the main content area using Polaris components.

Today's goal is to make that "App surface" dynamic by populating it with data from the merchant's store, like a list of their products.


The Data Flow: From Shopify to Your Component

In a modern Shopify app built with the Remix template, fetching and displaying data follows a clear, secure pattern that separates server-side and client-side concerns.

  1. The Route Component (e.g., app/routes/app.products.jsx): This file defines a new page in your app. It exports two key things:

    • A loader function that runs only on the server.
    • A React component that runs on the client (the browser).
  2. The loader Function (Backend): This is where you perform secure operations. The Shopify CLI provides an authenticate.admin(request) helper. This gives you an admin object, which contains a pre-configured GraphQL client. You use this client to query the Shopify Admin API for the data you need (e.g., products, orders). The loader then returns this data as a JSON response.

  3. The useLoaderData Hook (Connecting Backend to Frontend): In your React component, you call the useLoaderData hook. This hook provides the data that was returned from your loader function.

  4. The Polaris Component (Frontend): You take the data from useLoaderData and pass it as props to Polaris components like ResourceList or Card to render the UI for the merchant.

This architecture ensures that your API keys and secret tokens are never exposed to the browser, as all API calls happen securely on the server within the loader.


Part 1: Fetching and Displaying Products

Let's walk through building a new page in our app that fetches a list of products from the store and displays them. The following video provides an excellent step-by-step guide using the latest Shopify app template. We'll break it down into logical steps.

Getting Started with Shopify App Bridge - Create a Shopify App

The video 'Getting Started with Shopify App Bridge' from the WeeklyHow channel demonstrates the entire process of creating a new page, fetching data, and displaying it. We will focus on the sections that build out a 'Products' page.

This is a practical walkthrough. Follow these segments in order: Creating the Page & UI Shell (08:53 - 10:51): First, watch how to create a new route file (app.products.tsx) and use Polaris Page, Layout, and Card components to build the basic UI structure. Fetching Data in the Loader (17:20 - 23:00): This is the core backend part. Pay close attention to how the loader function is defined, how admin.graphql is used to make an API call, and how the data is returned using the json helper from Remix. Updating API Scopes (23:00 - 27:47): This is a critical, real-world step. The video shows the error you get when your app doesn't have permission to read products and how to fix it by updating the shopify.app.toml file and redeploying the app's configuration. You will encounter this constantly as a freelancer. Rendering the Data (27:47 - 33:42): Finally, see how the fetched data is retrieved in the component using useLoaderData and rendered using the Polaris ResourceList component. Note the use of Thumbnail to display product images.

After watching, let's crystallize the key steps:

  • Create a new file in /app/routes/ (e.g., app.products.tsx) to define the new page.
  • Define a loader function that is async and uses await authenticate.admin(request).
  • Construct a GraphQL query as a string and pass it to admin.graphql(). Remember to request the specific fields you need (e.g., id, title, featuredImage).
  • Update your app's scopes in shopify.app.toml to include necessary permissions like read_products.
  • Return the data from the loader using json({ products: productsData.products.edges }).
  • In your React component, get the data using const { products } = useLoaderData().
  • Map over the data and render it using an appropriate Polaris component like ResourceList.
Test your understanding!

In the data fetching pattern we just saw, why is it crucial to perform the GraphQL query inside the loader function instead of directly in the React component using a useEffect hook and fetch?

Show answer

There are two main reasons:

  1. Security: The loader function runs on your server. This allows it to safely use the app's API credentials to make authenticated requests to Shopify. If you were to make the API call from the frontend (useEffect), you would have to expose your secret API keys to the browser, which is a major security vulnerability.
  2. Performance & Simplicity: The Remix framework is designed for this server-side data loading pattern. It handles the data transfer from server to client automatically. This simplifies your component logic (no need for loading/error states managed by useEffect) and supports server-side rendering (SSR), which can lead to faster page loads.

Part 2: Interacting with Store Data via ResourcePicker

Displaying data is only half the story. A truly useful app allows merchants to interact with their data. One of the most common and powerful ways to do this is with App Bridge's ResourcePicker. It opens a modal that allows the merchant to browse, search, and select their Products, Variants, or Collections.

The official Shopify documentation provides a fantastic tutorial on building a QR code generator app. We will focus on the part where it implements the product selector.

Build a Shopify app using React Router

The Shopify developer documentation for 'Build a Shopify app using React Router' contains a complete walkthrough. We'll zero in on the sections that create and use the product selector.

Read through the section titled 'Create a QR code form'. You don't need to implement the entire form, but focus on understanding these key concepts: Set up the form route: Notice the creation of app.qrcodes.$id.jsx, which shows how to handle both creating new items and editing existing ones. Add a product selector: This is the most important part. See how the App Bridge ResourcePicker is invoked to open the product selection modal. Pay attention to how the selection is handled and used to update the component's state. Lay out the form: Skim this section to see how various Polaris web components (s-page, s-text-field, s-button) are used to build the form's UI. This reinforces what you learned in the previous lesson.

The core interaction logic for the ResourcePicker looks like this (adapted from the tutorial for clarity):

import { useAppBridge } from "@shopify/app-bridge-react";
import { useState } from "react";

function ProductSelector() {
  const shopify = useAppBridge();
  const [selectedProduct, setSelectedProduct] = useState(null);

  async function selectProduct() {
    const products = await shopify.resourcePicker({
      type: "product",
      action: "select",
      multiple: false, // Set to true to allow multiple selections
    });

    if (products) {
      const { id, title, images } = products[0];
      // You now have the product data. You can save it to your component's state.
      setSelectedProduct({ id, title, image: images[0]?.originalSrc });
      
      // In a real app, you would likely send this ID to your backend
      // to save it in your database.
    }
  }

  // ... JSX to display the selected product and a button to trigger selectProduct
}

This pattern is incredibly powerful. You get a native, feature-rich product selection UI with just a few lines of code, and you receive clean, structured data back to use in your application.


Conclusion

In this lesson, we bridged the gap between our frontend UI and Shopify's backend data. You've built a complete, full-stack slice of functionality within an embedded app, a core competency for any Shopify developer.

Key Takeaways:

  • Embedded app pages are created as new routes within your app's file structure.
  • The Remix loader function is the secure, server-side gateway for fetching data from the Shopify Admin API using the admin.graphql client.
  • Data from the loader is passed to your frontend component via the useLoaderData hook.
  • Polaris components like ResourceList and Card provide a consistent and professional way to display store data to the merchant.
  • App Bridge's ResourcePicker is an essential tool for enabling merchants to interact with and select resources like products and collections from within your app.

In our next lesson, we will build on the interaction patterns we saw today. We will explore how to use App Bridge actions to trigger native Shopify UI elements like modals and toast messages. This will allow us to provide clear feedback to the merchant after they perform an action, such as confirming that a product has been saved or alerting them to an error.