---
title: Build a custom configuration page
description: >-
  Learn how to embed a page from your app in the Shopify marketing automations
  editor.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/marketing/automations/build-custom-configuration-page
  md: >-
    https://shopify.dev/docs/apps/build/marketing/automations/build-custom-configuration-page.md
---

# Build a custom configuration page

To give merchants a more seamless action configuration experience, and to allow them to manage resources that are external to Shopify marketing automations, you can embed a page from your app in the Shopify marketing automations editor.

In your Shopify marketing automations action configuration, merchants see a preview with an image and text that's fetched from your [custom configuration page preview URL](https://shopify.dev/docs/apps/build/marketing/automations/action-endpoints). Merchants can click the button to access the custom configuration page.

![A custom configuration page preview with an](https://shopify.dev/assets/assets/images/apps/flow/ccp-preview-Cw4865rW.png)

Your custom configuration page is then displayed in a frame in the Shopify admin.

![The custom configuration page is rendered with an App Bridge title bar.](https://shopify.dev/assets/assets/images/apps/flow/ccp-app-bridge-CwN-nGm3.png)

In this tutorial, you'll learn how to render a custom configuration page in Shopify marketing automations, customize the page frame, and access data relevant to your action in the custom configuration page context.

***

## Requirements

* You're a [user with app development permissions](https://shopify.dev/docs/apps/build/dev-dashboard/user-permissions).
* You've [created an app](https://shopify.dev/docs/apps/build/scaffold-app).

***

## Resources

To implement this feature, you'll use the following:

* [Shopify App Bridge](https://shopify.dev/docs/api/app-home)
* App Bridge components
* App Bridge actions specific to the custom configuration page

***

## Implementing a custom configuration page

To build a custom configuration page, you'll [use Shopify App Bridge to render a page from your app page in Shopify marketing automations](#use-shopify-app-bridge-to-render-your-app-page).

From the context of the custom configuration page, you can then [access step and property information](#access-action-information) that you can use to display the appropriate information.

You can also [add buttons to the App Bridge title bar](#add-buttons-to-the-app-bridge-title-bar) or [trigger a redirect to the previous page](#register-to-the-custom-configuration-pages-intent).

***

## Use Shopify App Bridge to render your app page

**Note:**

The specifics of the Custom Configuration Page integration varies between Shopify App Bridge versions. Make sure you implement the integration specific to your Shopify App Bridge version.

To render your custom configuration page, you need to integrate Shopify App Bridge on the route that you want to render. To learn about setting up Shopify App Bridge, refer to one of the following pages:

* [Getting started with Shopify App Bridge](https://shopify.dev/docs/api/app-bridge/previous-versions/app-bridge-from-npm/app-setup)

### Access action information

In the context of the custom configuration page, Shopify marketing automations makes the following action information available:

* **A `step_reference` search parameter**: `step_reference` is a unique ID for the step within a workflow, and can be used to identify the resource that the merchant is requesting.
* **Property data**: Properties contains the extension fields data that make up your [action payload schema](https://shopify.dev/docs/apps/build/marketing/automations/create-marketing-automation-actions#configure-the-payload-schema). The properties are passed as an object containing the properties as key-value pairs:

```json
{
    <property-name>: <property-value>
  }
```

### Shopify App Bridge integration for versions 4.​X.​X and up

#### Register to the Custom Configuration Page's intent

To access the step configuration data with Shopify App Bridge version 4.X.X and up, use the `shopify.intents` API. Registering to the intent gives you the step's `action`, its `step_reference` (encoded in `intent.type` as `gid://flow/stepReference/<step-reference>`), and the configured step `properties` under `intent.data.properties`.

The following example registers to the intent and renders the step's data in a configuration UI built with [Polaris web components](https://shopify.dev/docs/api/app-home). It also adds a **Done** button to the [App Bridge title bar](https://shopify.dev/docs/api/app-home/app-bridge-web-components/title-bar) that calls `intent.finish()` to return the merchant to the action configuration pane:

## Example

```jsx
import { useAppBridge, TitleBar } from '@shopify/app-bridge-react'
import { useState, useEffect } from 'react'


function CustomConfigurationPage() {
  const shopify = useAppBridge()
  const [intent, setIntent] = useState({})


  // Register to the Custom Configuration Page's intent to receive the
  // step_reference and the property values configured for this step.
  useEffect(() => {
    const cleanup = shopify.intents.register((incoming) => {
      setIntent(incoming)
    })


    return () => cleanup()
  }, [shopify])


  const hasIntent = Boolean(intent?.type)
  // intent.type is a GID of the form gid://flow/stepReference/<step-reference>.
  const stepReference = intent?.type ? intent.type.split('/').pop() : ''
  const propertyEntries = Object.entries(intent?.data?.properties ?? {})


  return (
    <>
      {/* "Done" returns the merchant to the action configuration pane. */}
      <TitleBar title="Custom configuration">
        <button variant="primary" onClick={() => intent?.finish?.()}>
          Done
        </button>
      </TitleBar>


      <s-page heading="Action configuration">
        {!hasIntent ? (
          <s-section>
            <s-stack direction="block" gap="base" alignItems="center">
              <s-spinner size="large" accessibilityLabel="Loading configuration" />
              <s-text color="subdued">Waiting for step data…</s-text>
            </s-stack>
          </s-section>
        ) : (
          <>
            <s-section heading="Step">
              <s-stack direction="block" gap="large">
                <s-stack direction="inline" gap="base" alignItems="center">
                  <s-text type="strong">Action</s-text>
                  <s-badge tone="info">{intent.action}</s-badge>
                </s-stack>
                <s-stack direction="block" gap="small">
                  <s-text type="strong">Step reference</s-text>
                  <s-text color="subdued">{stepReference}</s-text>
                </s-stack>
              </s-stack>
            </s-section>


            <s-section heading="Configured properties">
              {propertyEntries.length === 0 ? (
                <s-paragraph color="subdued">
                  No properties were configured for this step.
                </s-paragraph>
              ) : (
                <s-table variant="auto">
                  <s-table-header-row>
                    <s-table-header>Property</s-table-header>
                    <s-table-header>Value</s-table-header>
                  </s-table-header-row>
                  <s-table-body>
                    {propertyEntries.map(([key, value]) => (
                      <s-table-row key={key}>
                        <s-table-cell>
                          <s-text type="strong">{key}</s-text>
                        </s-table-cell>
                        <s-table-cell>
                          <PropertyValue value={value} />
                        </s-table-cell>
                      </s-table-row>
                    ))}
                  </s-table-body>
                </s-table>
              )}
            </s-section>
          </>
        )}
      </s-page>
    </>
  )
}


// Render each property value according to its type.
function PropertyValue({ value }) {
  if (typeof value === 'boolean') {
    return (
      <s-badge tone={value ? 'success' : 'neutral'}>
        {value ? 'Yes' : 'No'}
      </s-badge>
    )
  }
  if (value === null || value === undefined || value === '') {
    return <s-text color="subdued">—</s-text>
  }
  if (typeof value === 'object') {
    return <s-text>{JSON.stringify(value)}</s-text>
  }
  return <s-text>{String(value)}</s-text>
}
```

The `register` callback receives an `intent` object with the following fields:

| Field | Type | Description |
| - | - | - |
| `action` | `string` | The operation the merchant is performing. Always `configure` for the Custom Configuration Page. |
| `type` | `string` | A GID of the form `gid://flow/stepReference/<step-reference>`. |
| `data` | `object` | Contains the configured `properties` as key-value pairs. |
| `finish` | `method` | Returns the merchant to the previous page (the action configuration pane). |

The `register` method returns a cleanup function. Call it when your component unmounts to unregister from the intent.

#### Add buttons to the App Bridge title bar

You can add more actions to the title bar by passing `<button>` elements to the [`TitleBar`](https://shopify.dev/docs/api/app-home/app-bridge-web-components/title-bar) component. Only primary and secondary actions are supported.

## Example

```jsx
import { TitleBar } from '@shopify/app-bridge-react'


function Page() {
  return (
    <TitleBar title="Custom configuration">
      <button variant="primary" onClick={() => console.log('Primary action')}>
        Primary action
      </button>
      <button onClick={() => console.log('Secondary action')}>
        Secondary action
      </button>
    </TitleBar>
  )
}
```

### Shopify App Bridge integration for versions 3.​X.​X and down

#### Request property data

To access property data, you need to subscribe to `APP::APP_FRAME::PROPERTIES_EVENT`, and then request the properties by triggering the `APP::APP_FRAME::REQUEST_PROPERTIES` event. The following example code subscribes to the properties event and requests the action properties in React:

## Example

```jsx
import { useAppBridge } from '@shopify/app-bridge-react'


const Application = () => {
  const app = useAppBridge()
  const [propertiesData, setPropertiesData] = useState({})


  useEffect(() => {
    const unsubscribeToPropertiesEvent = app.subscribe(
      'APP::APP_FRAME::PROPERTIES_EVENT',
      payload => {
        setPropertiesData(payload['properties'])
      },
    )


    return unsubscribeToPropertiesEvent
  }, [app])


  useEffect(() => {
    app.dispatch({
      type: 'APP::APP_FRAME::REQUEST_PROPERTIES',
      group: 'AppFrame',
    })
  }, [])


  return (...)
}
```

#### Return to the previous page

By default, the title bar of the custom configuration page includes an **Exit** button that the user can use to return to the previous page. This might be the Shopify marketing automations editor. However, you can choose to trigger a redirect to the previous page using `APP::APP_FRAME::NAVIGATE_BACK`:

## Example

```jsx
app.dispatch({
  type: 'APP::APP_FRAME::NAVIGATE_BACK',
  group: 'AppFrame',
})
```

#### Add buttons to the App Bridge title bar

You can add more actions to the App Bridge title bar in one of two ways:

* Using `@shopify/app-bridge`: Use the [`Button.create`](https://shopify.dev/docs/api/app-bridge/previous-versions/actions/button#create-a-button) initializer to create the buttons, then pass them to the [`Titlebar.create`](https://shopify.dev/docs/api/app-bridge/previous-versions/actions/titlebar#plain-javascript) initializer to set the buttons. You need to keep a reference to the Titlebar instance if you wish to do additional updates after the initialization.

* Using `@shopify/app-bridge-react`: Pass the primary and secondary actions to the [`TitleBar`](https://shopify.dev/docs/api/app-bridge/previous-versions/actions/titlebar#react) React component.

  Only primary and secondary actions on the TitleBar are supported. Other App Bridge actions are ignored.

## Example

##### JavaScript

```js
import { TitleBar, Button } from '@shopify/app-bridge/actions'

// create the buttons
const primaryBtn = Button.create(app, {
  label: 'Button 1',
})
const secondaryBtn = Button.create(app, {
  label: 'Button 2',
})

// add click handlers
primaryBtn.subscribe(Button.Action.CLICK, () => {
  console.log('button 1 clicked')
})
secondaryBtn.subscribe(Button.Action.CLICK, () => {
  console.log('button 2 clicked')
})

const titleBar = TitleBar.create(app, {
  title: '',
  buttons: {
    primary: primaryBtn,
    secondary: [secondaryBtn],
  },
})

// update buttons after initialization
const newPrimary = Button.create(app, {
  label: 'New button',
})
newPrimary.subscribe(Button.Action.CLICK, () => {
  console.log('new primary button clicked')
})

titleBar.set({
  buttons: {
    primary: newPrimary,
    secondary: [secondaryBtn],
  },
})
```

##### React

```jsx
import { TitleBar } from '@shopify/app-bridge-react'

function Page() {
  const buttons = {
    primaryAction: {
      content: 'Button 1',
      onAction: () => {
        console.log('button 1 clicked')
      },
    },
    secondaryActions: [
      {
        content: 'Button 2',
        onAction: () => {
          console.log('button 2 clicked')
        },
      },
    ],
  }

  return <TitleBar title="" {...buttons} />
}
```

***

## Next steps

* Add [custom configuration page preview URL](https://shopify.dev/docs/apps/build/marketing/automations/action-endpoints#custom-configuration-page-preview) and [custom validation](https://shopify.dev/docs/apps/build/marketing/automations/action-endpoints#custom-validation) endpoints to your app.

- Add your custom configuration page preview URL, custom configuration page URL, and custom validation URL to [your Shopify marketing automations action configuration](https://shopify.dev/docs/apps/build/marketing/automations/create-marketing-automation-actions).

**Note:**

To add a custom configuration page to your action, you also need to add a custom validation endpoint.

***
