---
title: Build for order action menus
description: >-
  Learn how to build order action menu extensions that allows customers to
  report issues with their orders.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/customer-accounts/order-action-extensions/build-for-order-action-menus
  md: >-
    https://shopify.dev/docs/apps/build/customer-accounts/order-action-extensions/build-for-order-action-menus.md
api_name: customer-account-ui-extensions
---

# Build for order action menus

In this tutorial, you'll create an order action menu extension that allows customers to report problems with their orders.

## What you'll learn

In this tutorial, you'll learn how to do the following tasks:

* Create an order action menu extension that opens a modal
* Make an API call to the [Customer Account API](https://shopify.dev/docs/api/customer), to retrieve the order's fulfillments
* Conditionally render the order action menu extension based on the order's fulfillment status
* Conditionally alter the extension's behavior if the customer belongs to a company, for B2B
* Run the extension locally and test it on a dev store

## Requirements

[Create a Partner account](https://www.shopify.com/partners)

[Create a development store](https://shopify.dev/docs/apps/build/stores/development-stores)

The development store should be pre-populated with [test data](https://shopify.dev/docs/apps/build/stores/generated-test-data), including an order associated with the email address you'll use to log in to the customer account experience.

[Shopify CLI](https://shopify.dev/docs/apps/tools/cli/installation)

You'll need to use the [latest version of Shopify CLI](https://shopify.dev/docs/api/shopify-cli#upgrade).

[Scaffold an app](https://shopify.dev/docs/apps/build/scaffold-app)

Scaffold an app that uses Shopify CLI.

## Project

[View on GitHub](https://github.com/Shopify/customer-account-tutorials/tree/main/preact/example-customer-account--order-action-menu--preact)

### Create a customer account UI extension

To create a customer account UI extension, you can use Shopify CLI, which generates starter code for building your extension and automates common development tasks.

1. Navigate to your app directory:

   ## Terminal

   ```terminal
   cd <directory>
   ```

2. Run the following command to create a new customer account UI extension:

   ## Terminal

   ```terminal
   shopify app generate extension --template customer_account_ui --name customer-account-ui-extension
   ```

   You should now have a new extension directory in your app's directory. The extension directory includes the extension script at `src/{FileName}.jsx`. The following is an example directory structure:

   ## Customer account UI extension file structure

   ```text
   └── my-app
   └── extensions
         └── my-customer-account-ui-extension
             ├── src
             │   └── CustomerAccount.jsx // The index page of the customer account UI extension
             ├── locales
             │   ├── en.default.json // The default locale for the customer account UI extension
             │   └── fr.json // The locale file for non-regional French translations
             ├── shopify.extension.toml // The config file for the customer account UI extension
             └── package.json
   ```

1) Start your development server to build and preview your app:

   ## Terminal

   ```terminal
   shopify app dev
   ```

   To learn about the processes that are executed when you run `dev`, refer to the [Shopify CLI command reference](https://shopify.dev/docs/api/shopify-cli/app/app-dev).

2) Press `p` to open the Dev Console.

3) In the extension list for your app, click on the preview link for your extension.

### Configure access scopes for your app

In your app’s `shopify.app.toml` file, include the required [access scopes](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration#access_scopes). The example code needs the `customer_read_orders` access scope to read the `ORDER` resource with the GraphQL Admin API.

To read on the `CUSTOMER` and `ORDER` resources using the Customer Account API, the `customer_read_customers` and `customer_read_orders` scopes will be required.

## /preact/example-customer-account--order-action-menu--preact/shopify.app.toml

```toml
name = "ca-order-action-menu"
application_url = "https://shopify.dev/apps/default-app-home"
embedded = true


[build]
automatically_update_urls_on_dev = true
include_config_on_deploy = true


[webhooks]
api_version = "2025-10"


[access_scopes]
scopes = "customer_read_customers,customer_read_orders"


[auth]
redirect_urls = [ "https://shopify.dev/apps/default-app-home/api/auth" ]
```

### Set up the targets for your extension

Set up the targets for your customer account UI extension. [Targets](https://shopify.dev/docs/api/customer-account-ui-extensions/latest/targets) control where your extension renders in the customer account flow.

You'll use static targets to render a button on the **Order index** and **Order status** pages and to render a modal when the button is clicked.

#### Reference the targets in your configuration file

This example code uses the following targets:

[customer-account.order.action.menu-item.render](https://shopify.dev/docs/api/customer-account-ui-extensions/latest/targets/order-actions#order-action-menu-item-) [customer-account.order.action.render](https://shopify.dev/docs/api/customer-account-ui-extensions/latest/targets/order-actions#order-action-)

In your extension's [`shopify.extension.toml`](https://shopify.dev/docs/apps/build/app-extensions/configure-app-extensions) configuration file, for each of the targets, create an `[[extensions.targeting]]` section with the following information:

* **`target`**: An identifier that specifies where you're injecting code into Shopify.
* **`module`**: The path to the file that contains the extension code.

***

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/shopify.extension.toml

```toml
api_version = "2025-10"


[[extensions]]
uid = "80e9f109-e0c9-ab13-2c5a-e065beb9d2eb669d1292"
type = "ui_extension"
name = "action-menu-extension-preact"
handle = "action-menu-extension-preact"


[[extensions.targeting]]
module = "./src/MenuActionExtension.jsx"
target = "customer-account.order.action.menu-item.render"


[[extensions.targeting]]
module = "./src/MenuActionModalExtension.jsx"
target = "customer-account.order.action.render"
```

#### Create files for your targets

Create files in your extension's `src` directory for each of your targets.

In this example, you'll create a file for the order action menu extension and a file for the order action modal extension. The filenames must match the `module` paths you specified.

### Build the order action menu item

#### Fetch the order's fulfillments

Merchants might not want to allow customers to report a problem before at least some parts of an order are fulfilled. To determine the fulfillment status, retrieve the order's fulfillments from the [Customer Account API](https://shopify.dev/docs/api/customer). Make sure to fetch data before rendering the button the first time.

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/src/MenuActionExtension.jsx

```jsx
import {render} from 'preact';


export default async () => {
  let hasFulfillments = false;


  try {
    const orderQuery = {
      query: `query Order($orderId: ID!) {
        order(id: $orderId) {
          fulfillments(first: 1) {
            nodes {
              latestShipmentStatus
            }
          }
        }
      }`,
      variables: {orderId: shopify.orderId},
    };


    const result = await fetch(
      'shopify://customer-account/api/2025-10/graphql.json',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(orderQuery),
      }
    );


    const {data} = await result.json();


    hasFulfillments = data.order.fulfillments.nodes.length !== 0;
  } catch (error) {
    console.log(error);
    hasFulfillments = false;
  }
  render(<MenuActionExtension showAction={hasFulfillments} />, document.body);
};


function MenuActionExtension({showAction}) {
  if (!showAction) {
    return null;
  }


  return <s-button>Report a problem</s-button>;
}
```

#### Conditionally render the order action menu item, based on the order's fulfillment status

Add a condition to render `null` if the order doesn't have any fulfillments.

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/src/MenuActionExtension.jsx

```jsx
import {render} from 'preact';


export default async () => {
  let hasFulfillments = false;


  try {
    const orderQuery = {
      query: `query Order($orderId: ID!) {
        order(id: $orderId) {
          fulfillments(first: 1) {
            nodes {
              latestShipmentStatus
            }
          }
        }
      }`,
      variables: {orderId: shopify.orderId},
    };


    const result = await fetch(
      'shopify://customer-account/api/2025-10/graphql.json',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(orderQuery),
      }
    );


    const {data} = await result.json();


    hasFulfillments = data.order.fulfillments.nodes.length !== 0;
  } catch (error) {
    console.log(error);
    hasFulfillments = false;
  }
  render(<MenuActionExtension showAction={hasFulfillments} />, document.body);
};


function MenuActionExtension({showAction}) {
  if (!showAction) {
    return null;
  }


  return <s-button>Report a problem</s-button>;
}
```

#### Render the button

Now you'll render the order action button. The button isn't being passed a `to` or `onPress` prop. This is how we know to connect this button to the order action modal extension.

***

![The rendered order action menu item button.](https://shopify.dev/assets/assets/images/apps/customer-accounts/order-action-menu-extensions/build-render-button-DQWzP30G.png)

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/src/MenuActionExtension.jsx

```jsx
import {render} from 'preact';


export default async () => {
  let hasFulfillments = false;


  try {
    const orderQuery = {
      query: `query Order($orderId: ID!) {
        order(id: $orderId) {
          fulfillments(first: 1) {
            nodes {
              latestShipmentStatus
            }
          }
        }
      }`,
      variables: {orderId: shopify.orderId},
    };


    const result = await fetch(
      'shopify://customer-account/api/2025-10/graphql.json',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(orderQuery),
      }
    );


    const {data} = await result.json();


    hasFulfillments = data.order.fulfillments.nodes.length !== 0;
  } catch (error) {
    console.log(error);
    hasFulfillments = false;
  }
  render(<MenuActionExtension showAction={hasFulfillments} />, document.body);
};


function MenuActionExtension({showAction}) {
  if (!showAction) {
    return null;
  }


  return <s-button>Report a problem</s-button>;
}
```

### Build the order action modal

#### Check if this is a B2B customer

Add an extra option in the select field component if the customer belongs to a company.

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/src/MenuActionModalExtension.jsx

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {useState} from 'preact/hooks';


export default async () => {
  render(<MenuActionModalExtension />, document.body);
};


const dtcOptions = [
  {value: '1', label: 'Package item is damaged'},
  {value: '2', label: 'Missing items'},
  {value: '3', label: 'Wrong item was sent'},
  {value: '4', label: 'Item arrived too late'},
  {value: '5', label: 'Never received item'},
];


const b2bOptions = dtcOptions.concat([
  {value: '6', label: 'Package sent to the wrong company location'},
]);


function MenuActionModalExtension() {
  const [currentProblem, setCurrentProblem] = useState('1');
  const [isLoading, setIsLoading] = useState(false);


  const isB2BCustomer =
    shopify.authenticatedAccount.purchasingCompany.value != null;


  function onSubmit() {
    // Simulating a request to your server to store the reported problem
    setIsLoading(true);


    console.log('Problem reported: ', currentProblem);


    setTimeout(() => {
      setIsLoading(false);
      shopify.close();
    }, 750);
  }


  const options = isB2BCustomer ? b2bOptions : dtcOptions;


  return (
    <s-customer-account-action heading="Report a problem">
      <s-select
        label="Select a problem"
        value={currentProblem}
        onChange={(e) => setCurrentProblem(e.target.value)}
      >
        {options.map((option) => (
          <s-option key={option.value} value={option.value}>
            {option.label}
          </s-option>
        ))}
      </s-select>


      <s-button
        slot="primary-action"
        loading={isLoading}
        onClick={() => onSubmit()}
      >
        Report
      </s-button>
      <s-button slot="secondary-actions" onClick={() => shopify.close()}>
        Cancel
      </s-button>
    </s-customer-account-action>
  );
}
```

#### Build the modal's UI

Use the customer account action component to extend the target. Here, you'll use the select component to display a list of reasons for reporting the order.

***

![The order action modal UI with the reasons select field.](https://shopify.dev/assets/assets/images/apps/customer-accounts/order-action-menu-extensions/build-modal-ui-BVD4EPDY.png)

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/src/MenuActionModalExtension.jsx

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {useState} from 'preact/hooks';


export default async () => {
  render(<MenuActionModalExtension />, document.body);
};


const dtcOptions = [
  {value: '1', label: 'Package item is damaged'},
  {value: '2', label: 'Missing items'},
  {value: '3', label: 'Wrong item was sent'},
  {value: '4', label: 'Item arrived too late'},
  {value: '5', label: 'Never received item'},
];


const b2bOptions = dtcOptions.concat([
  {value: '6', label: 'Package sent to the wrong company location'},
]);


function MenuActionModalExtension() {
  const [currentProblem, setCurrentProblem] = useState('1');
  const [isLoading, setIsLoading] = useState(false);


  const isB2BCustomer =
    shopify.authenticatedAccount.purchasingCompany.value != null;


  function onSubmit() {
    // Simulating a request to your server to store the reported problem
    setIsLoading(true);


    console.log('Problem reported: ', currentProblem);


    setTimeout(() => {
      setIsLoading(false);
      shopify.close();
    }, 750);
  }


  const options = isB2BCustomer ? b2bOptions : dtcOptions;


  return (
    <s-customer-account-action heading="Report a problem">
      <s-select
        label="Select a problem"
        value={currentProblem}
        onChange={(e) => setCurrentProblem(e.target.value)}
      >
        {options.map((option) => (
          <s-option key={option.value} value={option.value}>
            {option.label}
          </s-option>
        ))}
      </s-select>


      <s-button
        slot="primary-action"
        loading={isLoading}
        onClick={() => onSubmit()}
      >
        Report
      </s-button>
      <s-button slot="secondary-actions" onClick={() => shopify.close()}>
        Cancel
      </s-button>
    </s-customer-account-action>
  );
}
```

#### Make an API call to store the reported problem

The example code adds a timeout to fake an API call. In a production-ready application, make an API call to your server to store the reported problem.

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/src/MenuActionModalExtension.jsx

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {useState} from 'preact/hooks';


export default async () => {
  render(<MenuActionModalExtension />, document.body);
};


const dtcOptions = [
  {value: '1', label: 'Package item is damaged'},
  {value: '2', label: 'Missing items'},
  {value: '3', label: 'Wrong item was sent'},
  {value: '4', label: 'Item arrived too late'},
  {value: '5', label: 'Never received item'},
];


const b2bOptions = dtcOptions.concat([
  {value: '6', label: 'Package sent to the wrong company location'},
]);


function MenuActionModalExtension() {
  const [currentProblem, setCurrentProblem] = useState('1');
  const [isLoading, setIsLoading] = useState(false);


  const isB2BCustomer =
    shopify.authenticatedAccount.purchasingCompany.value != null;


  function onSubmit() {
    // Simulating a request to your server to store the reported problem
    setIsLoading(true);


    console.log('Problem reported: ', currentProblem);


    setTimeout(() => {
      setIsLoading(false);
      shopify.close();
    }, 750);
  }


  const options = isB2BCustomer ? b2bOptions : dtcOptions;


  return (
    <s-customer-account-action heading="Report a problem">
      <s-select
        label="Select a problem"
        value={currentProblem}
        onChange={(e) => setCurrentProblem(e.target.value)}
      >
        {options.map((option) => (
          <s-option key={option.value} value={option.value}>
            {option.label}
          </s-option>
        ))}
      </s-select>


      <s-button
        slot="primary-action"
        loading={isLoading}
        onClick={() => onSubmit()}
      >
        Report
      </s-button>
      <s-button slot="secondary-actions" onClick={() => shopify.close()}>
        Cancel
      </s-button>
    </s-customer-account-action>
  );
}
```

### Preview the extension

Preview your extension to make sure that it works as expected.

#### Start your server

Run the Shopify CLI `dev` command to build your app and preview it on your dev store.

1. In a terminal, navigate to your app directory.

2. Either start or restart your server to build and preview your app:

   ## Terminal

   ```bash
   shopify app dev
   ```

3. If prompted, select a dev store.

4. Press `p` to open the Dev Console.

5. In the extension list for your app, click the preview link for one of your targets.

The customer accounts experience opens.

Test your conditional logic by submitting and fulfilling some orders. For example, the order action menu item should only display if the order has at least one fulfillment.

***

## /preact/example-customer-account--order-action-menu--preact/shopify.app.toml

```toml
name = "ca-order-action-menu"
application_url = "https://shopify.dev/apps/default-app-home"
embedded = true


[build]
automatically_update_urls_on_dev = true
include_config_on_deploy = true


[webhooks]
api_version = "2025-10"


[access_scopes]
scopes = "customer_read_customers,customer_read_orders"


[auth]
redirect_urls = [ "https://shopify.dev/apps/default-app-home/api/auth" ]
```

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/shopify.extension.toml

```toml
api_version = "2025-10"


[[extensions]]
uid = "80e9f109-e0c9-ab13-2c5a-e065beb9d2eb669d1292"
type = "ui_extension"
name = "action-menu-extension-preact"
handle = "action-menu-extension-preact"


[[extensions.targeting]]
module = "./src/MenuActionExtension.jsx"
target = "customer-account.order.action.menu-item.render"


[[extensions.targeting]]
module = "./src/MenuActionModalExtension.jsx"
target = "customer-account.order.action.render"
```

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/src/MenuActionExtension.jsx

```jsx
import {render} from 'preact';


export default async () => {
  let hasFulfillments = false;


  try {
    const orderQuery = {
      query: `query Order($orderId: ID!) {
        order(id: $orderId) {
          fulfillments(first: 1) {
            nodes {
              latestShipmentStatus
            }
          }
        }
      }`,
      variables: {orderId: shopify.orderId},
    };


    const result = await fetch(
      'shopify://customer-account/api/2025-10/graphql.json',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(orderQuery),
      }
    );


    const {data} = await result.json();


    hasFulfillments = data.order.fulfillments.nodes.length !== 0;
  } catch (error) {
    console.log(error);
    hasFulfillments = false;
  }
  render(<MenuActionExtension showAction={hasFulfillments} />, document.body);
};


function MenuActionExtension({showAction}) {
  if (!showAction) {
    return null;
  }


  return <s-button>Report a problem</s-button>;
}
```

## /preact/example-customer-account--order-action-menu--preact/extensions/action-menu-extension-preact/src/MenuActionModalExtension.jsx

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {useState} from 'preact/hooks';


export default async () => {
  render(<MenuActionModalExtension />, document.body);
};


const dtcOptions = [
  {value: '1', label: 'Package item is damaged'},
  {value: '2', label: 'Missing items'},
  {value: '3', label: 'Wrong item was sent'},
  {value: '4', label: 'Item arrived too late'},
  {value: '5', label: 'Never received item'},
];


const b2bOptions = dtcOptions.concat([
  {value: '6', label: 'Package sent to the wrong company location'},
]);


function MenuActionModalExtension() {
  const [currentProblem, setCurrentProblem] = useState('1');
  const [isLoading, setIsLoading] = useState(false);


  const isB2BCustomer =
    shopify.authenticatedAccount.purchasingCompany.value != null;


  function onSubmit() {
    // Simulating a request to your server to store the reported problem
    setIsLoading(true);


    console.log('Problem reported: ', currentProblem);


    setTimeout(() => {
      setIsLoading(false);
      shopify.close();
    }, 750);
  }


  const options = isB2BCustomer ? b2bOptions : dtcOptions;


  return (
    <s-customer-account-action heading="Report a problem">
      <s-select
        label="Select a problem"
        value={currentProblem}
        onChange={(e) => setCurrentProblem(e.target.value)}
      >
        {options.map((option) => (
          <s-option key={option.value} value={option.value}>
            {option.label}
          </s-option>
        ))}
      </s-select>


      <s-button
        slot="primary-action"
        loading={isLoading}
        onClick={() => onSubmit()}
      >
        Report
      </s-button>
      <s-button slot="secondary-actions" onClick={() => shopify.close()}>
        Cancel
      </s-button>
    </s-customer-account-action>
  );
}
```

## Tutorial complete!

Nice work - what you just built could be used by Shopify merchants around the world! Keep the momentum going with these related tutorials and resources.

### Next Steps

[Extension placement\
\
](https://shopify.dev/docs/apps/customer-accounts/best-practices/deciding-extension-placement)

[Explore extension placement options and make informed decisions on where to position them.](https://shopify.dev/docs/apps/customer-accounts/best-practices/deciding-extension-placement)

[Localize your extension\
\
](https://shopify.dev/docs/apps/customer-accounts/best-practices/localizing-ui-extensions)

[Learn about localizing your customer account UI extensions for international merchants and customers.](https://shopify.dev/docs/apps/customer-accounts/best-practices/localizing-ui-extensions)

[Targets\
\
](https://shopify.dev/docs/api/customer-account-ui-extensions/latest/targets)

[Learn about the targets offered in customer account.](https://shopify.dev/docs/api/customer-account-ui-extensions/latest/targets)

[UX guidelines\
\
](https://shopify.dev/docs/apps/customer-accounts/best-practices/ux-guidelines)

[Follow our UX guidelines for customer accounts to ensure a consistent and satisfying user experience.](https://shopify.dev/docs/apps/customer-accounts/best-practices/ux-guidelines)

[Web components\
\
](https://shopify.dev/docs/api/customer-account-ui-extensions/latest/web-components)

[Learn about the components you can use to build customer account UI extensions.](https://shopify.dev/docs/api/customer-account-ui-extensions/latest/web-components)
