Build a Shopify App with Node.js and React

Build your user interface with Polaris

Now that you can run your app in Shopify, you can view and test your frontend components as you build them.

Install Polaris

Configure Next.js to import Polaris CSS styles from webpack so they can be used across your app. You’ll also need to import your Shopify API key using process.env so it can be accessed by the Polaris components.

  1. Install Polaris:
    Terminal
    Copy
                npm install --save @zeit/next-css @shopify/polaris
    
  2. Create a next.config.js file in your root project folder.
  3. Configure the next.config.js file:

    /next.config.js

    code contained in /next.config.js
    require("dotenv").config(); const withCSS = require('@zeit/next-css'); const webpack = require('webpack'); const apiKey = JSON.stringify(process.env.SHOPIFY_API_KEY); module.exports = withCSS({ webpack: (config) => { const env = { API_KEY: apiKey }; config.plugins.push(new webpack.DefinePlugin(env)); return config; }, });

Extend the App component

Next.js uses an App component to pass down classes to the other files in your app. This saves you from having to add imports to each file. You’ll set up an _app.js file that passes down the Polaris components, styles, and everything else typically found in an index file.

  1. Create an _app.js file in your pages directory.
  2. Create a class component that extends the App component and contains everything the component will pass down:

    /pages/_app.js

    code contained in /pages/_app.js
    import App from 'next/app'; import Head from 'next/head'; class MyApp extends App { render() { const { Component, pageProps } = this.props; return ( <React.Fragment> <Head> <title>Sample App</title> <meta charSet="utf-8" /> </Head> <Component {...pageProps} /> </React.Fragment> ); } } export default MyApp;

Add the Polaris app provider component

The Polaris app provider component passes down the props and context needed to use the Polaris library. Your app needs to be wrapped in this component to use Polaris.

  1. Wrap the Component in your /pages/_app.js file with the <AppProvider> component:

    /pages/_app.js

    code contained in /pages/_app.js
    import App from 'next/app'; import Head from 'next/head'; Add:import { AppProvider } from '@shopify/polaris'; class MyApp extends App { render() { const { Component, pageProps } = this.props; return ( <React.Fragment> <Head> <title>Sample App</title> <meta charSet="utf-8" /> </Head> Add: <AppProvider> <Component {...pageProps} /> Add: </AppProvider> </React.Fragment> ); } } export default MyApp;

Import the Polaris CSS styles

Since you’ll need Polaris styles to extend across your entire app, they can also be passed down by the Next.js App component.

  1. Add the Polaris styles import to your /pages/_app.js file:

    /pages/_app.js

    code contained in /pages/_app.js
    import App from 'next/app'; import Head from 'next/head'; import { AppProvider } from '@shopify/polaris'; Add:import '@shopify/polaris/styles.css'; class MyApp extends App { render() { const { Component, pageProps } = this.props; return ( <React.Fragment> <Head> <title>Sample App</title> <meta charSet="utf-8" /> </Head> <AppProvider> <Component {...pageProps} /> </AppProvider> </React.Fragment> ); } } export default MyApp;

Add the Translations prop

The translation prop is now required on the AppProvider. Translations are provided in the locales folder. When using Polaris, you are able to import translations from all languages supported by the core Shopify product and consume them through the i18n prop.

  1. Add the translations import and prop to your /pages/_app.js file:

    /pages/_app.js

    code contained in /pages/_app.js
    import App from 'next/app'; import Head from 'next/head'; import { AppProvider } from '@shopify/polaris'; import '@shopify/polaris/styles.css'; Add:import translations from '@shopify/polaris/locales/en.json'; class MyApp extends App { render() { const { Component, pageProps } = this.props; return ( <React.Fragment> <Head> <title>Sample App</title> <meta charSet="utf-8" /> </Head> Remove: <AppProvider> Add: <AppProvider i18n={translations}> <Component {...pageProps} /> </AppProvider> </React.Fragment> ); } } export default MyApp;
  2. Restart your server:
    Terminal
    Copy
                npm run dev
    

Test your Polaris import

Using the sample text in your pages/index.js file, add the Polaris Text style component to quickly test the Polaris import.

UI built with Polaris Text style
  1. Add an import of the TextStyle component to the top of your pages/index.js file:

    /pages/index.js

    code contained in /pages/index.js
    Add:import { TextStyle } from '@shopify/polaris'; const Index = () => ( <div> <p>Sample app using React and Next.js</p> </div> ); export default Index;
  2. Wrap your sample text in the TextStyle component with the positive variation:
    TextStyle has a variation prop that can give your text more visual meaning and add hierarchy to a page. To learn more about how to use this component, see the Polaris documentation for Text style.

    /pages/index.js

    code contained in /pages/index.js
    import { TextStyle } from '@shopify/polaris'; const Index = () => ( <div> Remove: <p>Sample app using React and Next.js</p> Add: <TextStyle variation="positive"> Add: Sample app using React and Next.js Add: </TextStyle> </div> ); export default Index;

Create Polaris UI

The sample app uses fundamental Polaris components that are relevant for most apps. This section of the tutorial prepares you to continue building your own app with other Polaris components as well. You can use the desktop and mobile views from the Invision design mockups to better visualize the components you’ll be adding.

Add a page

The Polaris Page component wraps each page of an embedded app. It includes the page’s title and primary actions. In the example below, you set the primary action (but there are many other options available as well).

UI built with Polaris page
  1. In your pages/index.js, import of the Page component:

    /pages/index.js

    code contained in /pages/index.js
    Remove:import { TextStyle } from '@shopify/polaris'; Add:import { Page, TextStyle } from '@shopify/polaris'; const Index = () => ( <div> <TextStyle variation="positive"> Sample app using React and Next.js </TextStyle> </div> ); export default Index;
  2. Replace the existing <div> with the Page component

    /pages/index.js

    code contained in /pages/index.js
    import { Page, TextStyle } from '@shopify/polaris'; const Index = () => ( Remove: <div> Add: <Page> <TextStyle variation="positive"> Sample app using React and Next.js </TextStyle> Add: </Page> Remove: </div> ); export default Index;

Add a layout

Now that you have a page, add the Polaris Layout component to give structure to the other components that you’ll add.

UI built with Polaris layout
  1. Add an import of the Layout component to your pages/index.js file:

    /pages/index.js

    code contained in /pages/index.js
    Remove:import { Page, TextStyle } from '@shopify/polaris'; Add:import { Layout, Page, TextStyle } from '@shopify/polaris'; const Index = () => ( <Page> <TextStyle variation="positive"> Sample app using React and Next.js </TextStyle> </Page> ); export default Index;
  2. Add the Layout component inside of the Polaris Page component:

    /pages/index.js

    code contained in /pages/index.js
    import { Layout, Page, TextStyle } from '@shopify/polaris'; const Index = () => ( <Page> Add: <Layout> <TextStyle variation="positive"> Sample app using React and Next.js </TextStyle> Add: </Layout> </Page> ); export default Index;

Add an empty state

The Polaris Empty state component helps to communicate the value of your app and its primary action when merchants first add it to their Shopify admin. The empty state includes a title, description, illustration, and a primary action. The component contains a CDN link for the default empty state illustration that you can use in your app.

UI built with Polaris empty state
  1. In your pages/index.js, replace the TextStyle component and text with the EmptyState component and example text:
    The console log has been added to indicate the clicked action. Polaris components take the onAction prop to pass a callback function.

    /pages/index.js

    code contained in /pages/index.js
    Remove:import { Layout, Page, TextStyle } from '@shopify/polaris'; Add:import { EmptyState, Layout, Page } from '@shopify/polaris'; Add:const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg'; const Index = () => ( <Page> <Layout> Remove: <TextStyle variation="positive"> Remove: Sample app using React and Next.js Remove: </TextStyle> Add: <EmptyState Add: heading="Discount your products temporarily" Add: action={{ Add: content: 'Select products', Add: onAction: () => console.log('clicked'), Add: }} Add: image={img} Add: > Add: <p>Select products to change their price temporarily.</p> Add: </EmptyState> </Layout> </Page> ); export default Index;

Add an annotated layout

The annotated layout is a variation of the Polaris Layout component. The annotated layout is generally used on a settings page to let merchants easily set up, enable, or disable functions of an app. It’s set up in sections that include titles and descriptions to help merchants understand the groupings of content.

Now that you have the basic structure of your main page, you’ll add a second page with an annotated layout. Within this new page you’ll create a React class component. A React class component is required, instead of a functional component, because you’ll be implementing Polaris components that require state.

UI built with Polaris annotated layout and card
  1. In your pages folder, create an annotated-layout.js file.
  2. In the pages/annotated-layout.js file, add a React class component to access component state:
    The React class component has a render method that returns the React elements.

    /pages/annotated-layout.js

    code contained in /pages/annotated-layout.js
    class AnnotatedLayout extends React.Component { state = {}; render() { return <div>Annotated layout page</div>; } } export default AnnotatedLayout;
  3. Replace the existing <div> with the Polaris Annotated Layout component, and include the first annotated section with a card component:

    /pages/annotated-layout.js

    code contained in /pages/annotated-layout.js
    Add:import { Card, Layout, Page } from '@shopify/polaris'; class AnnotatedLayout extends React.Component { state = {}; render() { Remove: return <div>Annotated layout page</div>; Add: return ( Add: <Page> Add: <Layout> Add: <Layout.AnnotatedSection Add: title="Default discount" Add: description="Add a product to Sample App, it will automatically be discounted." Add: > Add: <Card sectioned> Add: <div>Card</div> Add: </Card> Add: </Layout.AnnotatedSection> Add: </Layout> Add: </Page> Add: ); } } export default AnnotatedLayout;

Add a form

Now that you have the skeleton of your annotated layout, add the Polaris Form component to the card. The Polaris text field component includes a handleChange function to capture any user updates.

UI built with Polaris annotated layout and form
  1. Replace all of the contents of your pages/annotated-layout.js file with the following:
    You may notice you’ve destructured the state for ease of use. If you don’t want to, then you can omit the line and use this.state.discount instead.

    /pages/annotated-layout.js

    code contained in /pages/annotated-layout.js
    import { Button, Card, Form, FormLayout, Layout, Page, Stack, TextField, } from '@shopify/polaris'; class AnnotatedLayout extends React.Component { state = { discount: '10%', }; render() { const { discount } = this.state; return ( <Page> <Layout> <Layout.AnnotatedSection title="Default discount" description="Add a product to Sample App, it will automatically be discounted." > <Card sectioned> <Form onSubmit={this.handleSubmit}> <FormLayout> <TextField value={discount} onChange={this.handleChange('discount')} label="Discount percentage" type="discount" /> <Stack distribution="trailing"> <Button primary submit> Save </Button> </Stack> </FormLayout> </Form> </Card> </Layout.AnnotatedSection> </Layout> </Page> ); } handleSubmit = () => { this.setState({ discount: this.state.discount, }); console.log('submission', this.state); }; handleChange = (field) => { return (value) => this.setState({ [field]: value }); }; } export default AnnotatedLayout;
  2. View the pages/annotated-layout.js page in your browser by appending annotated-layout to the end of your store’s myshopify URL. For example, https://dev-tools-education.myshopify.com/admin/apps/sample-embedded-app/annotated-layout.
  3. Submit the form.

Your browser's console log will show you what the submission looks like submitting to an endpoint. In this case, the discount is hardcoded for demo purposes.

<span class="translation_missing" title="translation missing: en.build_a_shopify_app_with_node_and_react.build_your_user_interface_with_polaris.create_polaris_ui.add_a_settings_toggle.form_submission_console_log_screenshot_alt">Form Submission Console Log Screenshot Alt</span>

Add a settings toggle

The second section of the annotated layout page uses the Polaris Setting toggle component to let merchants enable or disable a feature of the sample embedded app. This component toggles the enabled state and updates the text based on the merchant’s interaction.

UI built with Polaris annotated layout and setting toggle
  1. In your pages/annotated-layout.js file, import the SettingToggle component and add a handler to toggle the state:
    You can view all of the the components in the Polaris library by visiting the Polaris website.

    /pages/annotated-layout.js

    code contained in /pages/annotated-layout.js
    import { Button, Card, Form, FormLayout, Layout, Page, Add: SettingToggle, Stack, TextField, Add: TextStyle, } from '@shopify/polaris'; class AnnotatedLayout extends React.Component { state = { discount: '10%', Add: enabled: false, }; render() { Remove: const { discount } = this.state; Add: const { discount, enabled } = this.state; Add: const contentStatus = enabled ? 'Disable' : 'Enable'; Add: const textStatus = enabled ? 'enabled' : 'disabled'; return ( <Page> <Layout> <Layout.AnnotatedSection title="Default discount" description="Add a product to Sample App, it will automatically be discounted." > <Card sectioned> <Form onSubmit={this.handleSubmit}> <FormLayout> <TextField value={discount} onChange={this.handleChange('discount')} label="Discount percentage" type="discount" /> <Stack distribution="trailing"> <Button primary submit> Save </Button> </Stack> </FormLayout> </Form> </Card> </Layout.AnnotatedSection> Add: <Layout.AnnotatedSection Add: title="Price updates" Add: description="Temporarily disable all Sample App price updates" Add: > Add: <SettingToggle Add: action={{ Add: content: contentStatus, Add: onAction: this.handleToggle, Add: }} Add: enabled={enabled} Add: > Add: This setting is{' '} Add: <TextStyle variation="strong">{textStatus}</TextStyle>. Add: </SettingToggle> Add: </Layout.AnnotatedSection> </Layout> </Page> ); } handleSubmit = () => { this.setState({ discount: this.state.discount, }); console.log('submission', this.state); }; handleChange = (field) => { return (value) => this.setState({[field]: value}); }; Add: handleToggle = () => { Add: this.setState(({ enabled }) => { Add: return { enabled: !enabled }; Add: }); Add: }; } export default AnnotatedLayout;

Set up your app navigation

Apps that are embedded in the Shopify admin can enable a navigation component in the title bar. You’ll use the routes that were automatically created by Next.js to set up your navigation links in the Shopify Partner Dashboard.

Shopify embedded app, navigation
  1. Log in to the Shopify Partner Dashboard, and click Apps > sample-embedded-app to navigate to your app.
  2. Click Extensions: Shopify Partner Dashboard, main app page
  3. Click Manage Embedded app: Shopify Partner Dashboard, app extensions page
  4. Click configure: Shopify Partner Dashboard, embedded app extension page
  5. On the Navigation bar page, click Add Navigation link, and then add a name and a link to correspond to the pages that you created. In the sample app, we used “Simple resource list” as the name for our “index” link, and "Annotated layout" for our "annotated-layout" link.

    Shopify embedded app, navigation

    The navigation menu should now be available in your app. If it doesn’t appear right away, then wait 30 seconds and refresh the page.

Add Shopify App Bridge

Shopify App Bridge is a JavaScript library that seamlessly integrates your app into Shopify user interfaces, including the web admin, mobile app, and POS. In this tutorial we’ve focused primarily on the web admin, but App Bridge will ensure your app name, logo, and navigation menu appears reliably across all of Shopify’s interfaces. Keeping your look and feel consistent with Shopify’s UI also makes it faster and easier for merchants to start using your app. When building with React, you can use the Shopify App Bridge React library to initialize the library by passing your app's Shopify API Key and the shop origin to the App Bridge Provider component.

  1. In the server.js file, set the shopOrigin in cookies from the user's session:

    /server.js

    code contained in /server.js
    require('isomorphic-fetch'); const dotenv = require('dotenv'); const Koa = require('koa'); const next = require('next'); const { default: createShopifyAuth } = require('@shopify/koa-shopify-auth'); const { verifyRequest } = require('@shopify/koa-shopify-auth'); const session = require('koa-session'); dotenv.config(); const port = parseInt(process.env.PORT, 10) || 3000; const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); const { SHOPIFY_API_SECRET_KEY, SHOPIFY_API_KEY } = process.env; app.prepare().then(() => { const server = new Koa(); server.use(session(server)); server.keys = [SHOPIFY_API_SECRET_KEY]; server.use( createShopifyAuth({ apiKey: SHOPIFY_API_KEY, secret: SHOPIFY_API_SECRET_KEY, scopes: ['read_products'], afterAuth(ctx) { const { shop, accessToken } = ctx.session; Add: ctx.cookies.set('shopOrigin', shop, { httpOnly: false }); ctx.redirect('/'); }, }), ); server.use(verifyRequest()); server.use(async (ctx) => { await handle(ctx.req, ctx.res); ctx.respond = false; ctx.res.statusCode = 200; return }); server.listen(port, () => { console.log(`> Ready on http://localhost:${port}`); }); });
  2. Install the App Bridge React package and js-cookie to read cookies in your pages/_app.js file:
    Terminal
    Copy
                npm install --save @shopify/app-bridge-react js-cookie
    
  3. In pages/_app.js add the provider component from App Bridge React

    /pages/_app.js

    code contained in /pages/_app.js
    import App from 'next/app'; import Head from 'next/head'; import { AppProvider } from '@shopify/polaris'; Add:import { Provider } from '@shopify/app-bridge-react'; import '@shopify/polaris/styles.css'; import translations from '@shopify/polaris/locales/en.json'; class MyApp extends App { render() { const { Component, pageProps } = this.props; return ( <React.Fragment> <Head> <title>Sample App</title> <meta charSet="utf-8" /> </Head> Add: <Provider> <AppProvider i18n={translations}> <Component {...pageProps} /> </AppProvider> Add: </Provider> </React.Fragment> ); } } export default MyApp;
  4. Add your Shopify API key, the shopOrigin using js-cookie, and the forceRedirect prop to the AppProvider component:
    Pulling in your Shopify API key by using process.env lets you share it across your app without exposing it publicly.

    /pages/_app.js

    code contained in /pages/_app.js
    import App from 'next/app'; import Head from 'next/head'; import { AppProvider } from '@shopify/polaris'; import { Provider } from '@shopify/app-bridge-react'; import '@shopify/polaris/styles.css'; import translations from '@shopify/polaris/locales/en.json'; Add:import Cookies from 'js-cookie'; class MyApp extends App { render() { const { Component, pageProps } = this.props; Add: const config = { apiKey: API_KEY, shopOrigin: Cookies.get("shopOrigin"), forceRedirect: true }; return ( <React.Fragment> <Head> <title>Sample App</title> <meta charSet="utf-8" /> </Head> Remove: <Provider> Add: <Provider config={config}> <AppProvider i18n={translations}> <Component {...pageProps} /> </AppProvider> </Provider> </React.Fragment> ); } } export default MyApp;
  5. Restart your server:
    Because the shopOrigin cookie is set during authentication, you’ll need to re-authenticate your app in your store’s admin. To do this, visit https://YOUR_NGROK_ADDRESS.io/auth?shop=YOUR_SHOPIFY_STORE.myshopify.com in your web browser.
    Terminal
    Copy
                  npm run dev
    

Add the Title Bar

Now that you have the Shopify App Bridge React library, you can add the Title Bar to your navigation bar.

  1. In your pages/index.js import TitleBar from App Bridge React and pass it the primary action props.

    /pages/index.js

    code contained in /pages/index.js
    import { EmptyState, Layout, Page } from '@shopify/polaris'; import { TitleBar } from '@shopify/app-bridge-react'; const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg'; const Index = () => ( <Page> Add: <TitleBar Add: primaryAction={{ Add: content: 'Select products', Add: }} Add: /> <Layout> <EmptyState heading="Discount your products temporarily" action={{ content: 'Select products', onAction: () => console.log('clicked'), }} image={img} > <p>Select products to change their price temporarily.</p> </EmptyState> </Layout> </Page> ); export default Index;

Add the Resource Picker

The App Bridge Resource Picker lets embedded apps easily give merchants a way to select resources from their store. The sample embedded app loads the resource picker from the title bar and empty state to let merchants select products.

UI built with Polaris resource picker
  1. In your pages/index.js file, add a class that sets a state for the resource picker:

    /pages/index.js

    code contained in /pages/index.js
    import { EmptyState, Layout, Page } from '@shopify/polaris'; const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg'; Remove:const Index = () => ( Add:class Index extends React.Component { Add: state = { open: false }; Add: render() { Add: return ( <Page> <Layout> <EmptyState heading="Select products to start" action={{ content: 'Select products', onAction: () => console.log('clicked'), }} image={img} > <p>Select products and change their price temporarily</p> </EmptyState> </Layout> </Page> ); Add: } Add:} export default Index;
  2. In your pages/index.js file, add the ResourcePicker component to the primary action button on the EmptyState component:
    For now, console.log will store the selected products after user submission. Later on, you’ll use onSelection to pass product IDs to the API calls you’ll be making.

    /pages/index.js

    code contained in /pages/index.js
    import { EmptyState, Layout, Page } from '@shopify/polaris'; Remove:import { TitleBar} from '@shopify/app-bridge-react'; Add:import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react'; const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg'; class Index extends React.Component { state = { open: false }; render() { return ( <Page> <TitleBar primaryAction={{ content: 'Select products', }} /> Add: <ResourcePicker Add: resourceType="Product" Add: showVariants={false} Add: open={this.state.open} Add: onSelection={(resources) => this.handleSelection(resources)} Add: onCancel={() => this.setState({ open: false })} Add: /> <Layout> <EmptyState heading="Select products to start" action={{ content: 'Select products', Remove: onAction: () => console.log('clicked') Add: onAction: () => this.setState({ open: true }), }} image={img} > <p>Select products and change their price temporarily</p> </EmptyState> </Layout> </Page > ); } Add: handleSelection = (resources) => { Add: this.setState({ open: false }) Add: console.log(resources) Add: }; } export default Index;
    Empty Polaris resource picker

Import sample product data into your development store

When you open the resource picker in your app, you might see a message that says “Your store doesn’t have any products yet.” If so, then import some sample product data into your development store.

  1. Download products_export.csv.
  2. Navigate to the Products section of your Shopify development store, and click Import: Shopify development store, Products section
  3. Click Choose file, and upload the products_export.csv file: Select cvs file to import into Shopify
  4. Click Start import: Importing products data into Shopify You’ll receive an email from Shopify when your import is complete.

Add the resource picker to your title bar

The embedded app title bar persists through each screen of your app and gives merchants quick access to the primary action in your app. Now that the resource picker is set to open only on action, add it to the primary action in the title bar of the App Bridge React TitleBar component.

  1. In your pages/index.js file, set up the Page component to trigger the resource picker:

    /pages/index.js

    code contained in /pages/index.js
    import { EmptyState, Layout, Page } from '@shopify/polaris'; import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react'; const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg'; class Index extends React.Component { state = { open: false }; render() { return ( <Page> <TitleBar primaryAction={{ content: 'Select products', Add: onAction: () => this.setState({ open: true }), /> <ResourcePicker resourceType="Product" showVariants={false} open={this.state.open} onSelection={(resources) => this.handleSelection(resources)} onCancel={() => this.setState({ open: false })} /> <Layout> <EmptyState heading="Select products to start" action={{ content: 'Select products', onAction: () => this.setState({ open: true }), }} image={img} > <p>Select products and change their price temporarily</p> </EmptyState> </Layout> </Page> ); } handleSelection = (resources) => { this.setState({ open: false }); console.log(resources); }; } export default Index;

Passing data from the resource picker

At this point you should see the modal open and close with your store’s products displayed. If you select a product and click Select, then each product should be found in your console.log.

You’ll want to take those products and display them to merchants in a form. Eventually you’ll write a query to the Shopify GraphQL Admin API, but first you need to create an array of IDs to use in the query.

  1. In your pages/index.js file, use map to create an array of IDs from the selected resources:

    /pages/index.js

    code contained in /pages/index.js
    handleSelection = (resources) => { Add: const idsFromResources = resources.selection.map((product) => product.id); this.setState({ open: false }); console.log(resources); };
Continue to Learn the GraphQL Admin API