---
title: Hide admin UI extensions
description: >-
  Learn how to conditionally hide admin action and block UI extensions in the
  Shopify admin.
source_url:
  html: 'https://shopify.dev/docs/apps/build/admin/actions-blocks/hide-extensions'
  md: 'https://shopify.dev/docs/apps/build/admin/actions-blocks/hide-extensions.md'
api_name: admin-extensions
---

# Hide admin UI extensions

This guide is the final part of a five-part tutorial series that describes how to build UI extensions that display as actions and blocks in Shopify admin. Before starting this guide, you'll need to build or copy the code for the issue tracker admin action and admin block from the [tutorial for connecting UI extensions to your app's backend](https://shopify.dev/docs/apps/build/admin/actions-blocks/connect-app-backend).

Alternatively, you can complete this section immediately after completing the [build an admin action](https://shopify.dev/docs/apps/build/admin/actions-blocks/build-admin-action) and [build an admin block](https://shopify.dev/docs/apps/build/admin/actions-blocks/build-admin-block) tutorials.

So far you've created UI extensions that add an action and block that allows merchants to create and track issues in their Shopify admin. In this tutorial, you'll learn how to hide a UI extension when it's not relevant to the target.

To demonstrate conditional logic, we'll check the variant count of a product to determine if the UI extension should be visible. If the product has more than one variant, then the UI extension will be visible. If the product has only one variant, then the UI extension will be hidden.

## What you'll learn

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

* Minimize the block when it's not relevant to the target.
* Hide the menu item that launches the action when it's not relevant to the target.

## Requirements

[Tutorials](https://shopify.dev/docs/apps/admin/admin-actions-and-blocks)

You've completed or copied the code from the [admin action tutorial](https://shopify.dev/docs/apps/build/admin/actions-blocks/build-admin-action) and the [admin block tutorial](https://shopify.dev/docs/apps/build/admin/actions-blocks/build-admin-block).

## Project

[View on GitHub](https://github.com/Shopify/example-admin-action-and-block-preact)

## Collapse an admin block

If an admin block isn't relevant on a page, then you can collapse it to minimize disruption for merchants, while still enabling them to see that they have pinned it to the page. To minimize an admin block, you can return `null` inside the [admin block](https://shopify.dev/docs/api/admin-extensions/latest/web-components/settings-and-templates/admin-block) component of your UI extension.

### Use the `getIssues` function to determine if the admin block should be visible.

Initialize a state variable called `shouldRender` and set it to `false`. You're already using the `getIssues` function to get metafield values. Using the same function, check if the product has more than one variant. If it does, then set the `shouldRender` state to `true`.

## /extensions/issue-tracker-conditional-block/src/BlockExtension.jsx

```jsx
import { render } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";


import { getIssues, updateIssues } from "../../issue-tracker-block/src/utils";


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


  const PAGE_SIZE = 3;


  function Extension() {
    const { i18n, data, navigation } = shopify;
    const [issues, setIssues] = useState([]);


    const productId = data.selected[0].id;
    const issuesCount = issues.length;
    const totalPages = issuesCount / PAGE_SIZE;


    const [loading, setLoading] = useState(true);
    const [_, setInitialValues] = useState([]);
    const [currentPage, setCurrentPage] = useState(1);


    const [shouldRender, setShouldRender] = useState(false);


    useEffect(() => {
      (async function getProductInfo() {
        const productData = await getIssues(productId);


        setLoading(false);


        if (productData?.data?.product?.variants?.edges.length > 1) {
          setShouldRender(true);
        }


        if (productData?.data?.product?.metafield?.value) {
          const parsedIssues = JSON.parse(
            productData.data.product.metafield.value,
          );
          setInitialValues(
            parsedIssues.map(({ completed }) => Boolean(completed)),
          );
          setIssues(parsedIssues);
        }
      })();
    }, [productId]);


    const paginatedIssues = useMemo(() => {
      if (issuesCount <= PAGE_SIZE) {
        // It's not necessary to paginate if there are fewer issues than the page size
        return issues;
      }


      // Slice the array after the last item of the previous page
      return [...issues].slice(
        (currentPage - 1) * PAGE_SIZE,
        currentPage * PAGE_SIZE,
      );
    }, [issuesCount, issues, currentPage]);
    console.log({ paginatedIssues });


    const handleChange = async (id, value) => {
      // Update the local state of the extension to reflect changes
      setIssues((currentIssues) => {
        // Create a copy of the array so that you don't mistakenly mutate the state
        const newIssues = [...currentIssues];
        // Find the index of the issue that you're interested in
        const editingIssueIndex = newIssues.findIndex(
          (listIssue) => listIssue.id == id,
        );
        // Overwrite that item with the new value
        newIssues[editingIssueIndex] = {
          // Spread the previous item to retain the values that you're not changing
          ...newIssues[editingIssueIndex],
          // Update the completed value
          completed: value === "completed" ? true : false,
        };
        return newIssues;
      });
    };


    const handleDelete = async (id) => {
      // Create a new array of issues, leaving out the one that you're deleting
      const newIssues = issues.filter((issue) => issue.id !== id);
      // Save to the local state
      setIssues(newIssues);
      // Commit changes to the database
      await updateIssues(productId, newIssues);
    };


    const onSubmit = async () => {
      // Commit changes to the database
      await updateIssues(productId, issues);
    };


    const onReset = () => {};


    const blockMarkup = loading ? (
      <s-spinner></s-spinner>
    ) : (
      <>
        <s-form id={`issues-form`} onSubmit={onSubmit} onReset={onReset}>
          {paginatedIssues.length > 0 ? (
            <s-table
              paginate
              onNextPage={() => setCurrentPage(currentPage + 1)}
              onPreviousPage={() => setCurrentPage(currentPage - 1)}
              hasNextPage={currentPage < totalPages}
              hasPreviousPage={currentPage > 1}
            >
              <s-table-header-row>
                <s-table-header listSlot="primary">
                  {i18n.translate("issue-column-heading")}
                </s-table-header>
                <s-table-header>
                  {i18n.translate("status-column-heading")}
                </s-table-header>
                <s-table-header></s-table-header>
              </s-table-header-row>
              <s-table-body>
                {paginatedIssues.map(
                  ({ id, title, description, completed }, index) => {
                    return (
                      <s-table-row key={id}>
                        <s-table-cell>
                          <s-stack direction="block">
                            <s-text>{title}</s-text>
                            <s-text>{description}</s-text>
                          </s-stack>
                        </s-table-cell>
                        <s-table-cell>
                          <s-select
                            labelAccessibilityVisibility="exclusive"
                            label={i18n.translate("select-label")}
                            value={completed ? "completed" : "todo"}
                            onChange={(e) => handleChange(id, (/** @type {HTMLInputElement} */ (e.target)).value)}
                          >
                            <s-option value="todo">
                              {i18n.translate("option-todo")}
                            </s-option>
                            <s-option value="completed">
                              {i18n.translate("option-completed")}
                            </s-option>
                          </s-select>
                        </s-table-cell>
                        <s-table-cell>
                          <s-button
                            variant="tertiary"
                            icon="delete"
                            accessibilityLabel={i18n.translate(
                              "delete-issue-button",
                            )}
                            onClick={() => handleDelete(id)}
                          />
                        </s-table-cell>
                      </s-table-row>
                    );
                  },
                )}
              </s-table-body>
              <s-button
                onClick={() => {
                  const url = `extension:issue-tracker-action`;
                  navigation?.navigate(url);
                }}
              >
                {i18n.translate("add-issue-button")}
              </s-button>
            </s-table>
          ) : (
            <s-text>{i18n.translate("no-issues-text")}</s-text>
          )}
          <s-box>
            <s-button
              onClick={() => {
                const url = `extension:issue-tracker-action`;
                navigation?.navigate(url);
              }}
            >
              {i18n.translate("add-issue-button")}
            </s-button>
          </s-box>
        </s-form>
      </>
    );


    // Only render the block body if there is more than one variant, otherwise, return null to collapse the block
    return (
      <s-admin-block
        heading={i18n.translate("name")}
        collapsedSummary={
          !shouldRender ? i18n.translate("collapsed-summary") : null
        }
      >
        {shouldRender ? blockMarkup : null}
      </s-admin-block>
    );
  }
};
```

### Conditionally return JSX content based on the result of the get​Product​Variants function.

If `shouldRender` is `true`, then render the block's content. If it's `false`, then return `null` to collapse the block.

Use the `collapsedSummary` to provide meaningful information to the merchant about why the block is collapsed.

## /extensions/issue-tracker-conditional-block/src/BlockExtension.jsx

```jsx
import { render } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";


import { getIssues, updateIssues } from "../../issue-tracker-block/src/utils";


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


  const PAGE_SIZE = 3;


  function Extension() {
    const { i18n, data, navigation } = shopify;
    const [issues, setIssues] = useState([]);


    const productId = data.selected[0].id;
    const issuesCount = issues.length;
    const totalPages = issuesCount / PAGE_SIZE;


    const [loading, setLoading] = useState(true);
    const [_, setInitialValues] = useState([]);
    const [currentPage, setCurrentPage] = useState(1);


    const [shouldRender, setShouldRender] = useState(false);


    useEffect(() => {
      (async function getProductInfo() {
        const productData = await getIssues(productId);


        setLoading(false);


        if (productData?.data?.product?.variants?.edges.length > 1) {
          setShouldRender(true);
        }


        if (productData?.data?.product?.metafield?.value) {
          const parsedIssues = JSON.parse(
            productData.data.product.metafield.value,
          );
          setInitialValues(
            parsedIssues.map(({ completed }) => Boolean(completed)),
          );
          setIssues(parsedIssues);
        }
      })();
    }, [productId]);


    const paginatedIssues = useMemo(() => {
      if (issuesCount <= PAGE_SIZE) {
        // It's not necessary to paginate if there are fewer issues than the page size
        return issues;
      }


      // Slice the array after the last item of the previous page
      return [...issues].slice(
        (currentPage - 1) * PAGE_SIZE,
        currentPage * PAGE_SIZE,
      );
    }, [issuesCount, issues, currentPage]);
    console.log({ paginatedIssues });


    const handleChange = async (id, value) => {
      // Update the local state of the extension to reflect changes
      setIssues((currentIssues) => {
        // Create a copy of the array so that you don't mistakenly mutate the state
        const newIssues = [...currentIssues];
        // Find the index of the issue that you're interested in
        const editingIssueIndex = newIssues.findIndex(
          (listIssue) => listIssue.id == id,
        );
        // Overwrite that item with the new value
        newIssues[editingIssueIndex] = {
          // Spread the previous item to retain the values that you're not changing
          ...newIssues[editingIssueIndex],
          // Update the completed value
          completed: value === "completed" ? true : false,
        };
        return newIssues;
      });
    };


    const handleDelete = async (id) => {
      // Create a new array of issues, leaving out the one that you're deleting
      const newIssues = issues.filter((issue) => issue.id !== id);
      // Save to the local state
      setIssues(newIssues);
      // Commit changes to the database
      await updateIssues(productId, newIssues);
    };


    const onSubmit = async () => {
      // Commit changes to the database
      await updateIssues(productId, issues);
    };


    const onReset = () => {};


    const blockMarkup = loading ? (
      <s-spinner></s-spinner>
    ) : (
      <>
        <s-form id={`issues-form`} onSubmit={onSubmit} onReset={onReset}>
          {paginatedIssues.length > 0 ? (
            <s-table
              paginate
              onNextPage={() => setCurrentPage(currentPage + 1)}
              onPreviousPage={() => setCurrentPage(currentPage - 1)}
              hasNextPage={currentPage < totalPages}
              hasPreviousPage={currentPage > 1}
            >
              <s-table-header-row>
                <s-table-header listSlot="primary">
                  {i18n.translate("issue-column-heading")}
                </s-table-header>
                <s-table-header>
                  {i18n.translate("status-column-heading")}
                </s-table-header>
                <s-table-header></s-table-header>
              </s-table-header-row>
              <s-table-body>
                {paginatedIssues.map(
                  ({ id, title, description, completed }, index) => {
                    return (
                      <s-table-row key={id}>
                        <s-table-cell>
                          <s-stack direction="block">
                            <s-text>{title}</s-text>
                            <s-text>{description}</s-text>
                          </s-stack>
                        </s-table-cell>
                        <s-table-cell>
                          <s-select
                            labelAccessibilityVisibility="exclusive"
                            label={i18n.translate("select-label")}
                            value={completed ? "completed" : "todo"}
                            onChange={(e) => handleChange(id, (/** @type {HTMLInputElement} */ (e.target)).value)}
                          >
                            <s-option value="todo">
                              {i18n.translate("option-todo")}
                            </s-option>
                            <s-option value="completed">
                              {i18n.translate("option-completed")}
                            </s-option>
                          </s-select>
                        </s-table-cell>
                        <s-table-cell>
                          <s-button
                            variant="tertiary"
                            icon="delete"
                            accessibilityLabel={i18n.translate(
                              "delete-issue-button",
                            )}
                            onClick={() => handleDelete(id)}
                          />
                        </s-table-cell>
                      </s-table-row>
                    );
                  },
                )}
              </s-table-body>
              <s-button
                onClick={() => {
                  const url = `extension:issue-tracker-action`;
                  navigation?.navigate(url);
                }}
              >
                {i18n.translate("add-issue-button")}
              </s-button>
            </s-table>
          ) : (
            <s-text>{i18n.translate("no-issues-text")}</s-text>
          )}
          <s-box>
            <s-button
              onClick={() => {
                const url = `extension:issue-tracker-action`;
                navigation?.navigate(url);
              }}
            >
              {i18n.translate("add-issue-button")}
            </s-button>
          </s-box>
        </s-form>
      </>
    );


    // Only render the block body if there is more than one variant, otherwise, return null to collapse the block
    return (
      <s-admin-block
        heading={i18n.translate("name")}
        collapsedSummary={
          !shouldRender ? i18n.translate("collapsed-summary") : null
        }
      >
        {shouldRender ? blockMarkup : null}
      </s-admin-block>
    );
  }
};
```

## Test hiding the admin block

After you've updated the UI extension, test that the admin block collapses with the following steps:

1. Navigate to your app directory:

   ## Terminal

   ```bash
   cd <directory>
   ```

2. To build and preview your app, either start or restart your server with the following command:

   ## Terminal

   ```bash
   shopify app dev
   ```

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

4. In the extension list for your app, click on the preview link for the issue tracker UI extension.

5. If there are any product variants, delete them and confirm that the admin block is collapsed.

6. Add a product variant with two options and confirm that the admin block expands.

### Hide an admin action

Hiding a UI extension's admin action uses a second script to control the visibility of the action in the **More actions** menu. This script only runs after the page is loaded and doesn't maintain state.

Add a field to your TOML file to specify the path to the `shouldRender` script.

## /extensions/issue-tracker-conditional-action/shopify.extension.toml

```toml
api_version = "2026-04"
[[extensions]]
name = "t:name"
handle = "conditional-admin-action-extension"
uid = "3ca7f446-20fb-da23-6b62-b3c1e2852d2bfcf7472e"
type = "ui_extension"


# Only 1 target can be specified for each Admin action condition extension
[[extensions.targeting]]
module = "./src/ActionExtension.jsx"
target = "admin.product-details.action.render"


# This is the relative path to the module that contains the logic to determine if the action trigger should be visible.
[extensions.targeting.should_render]
module = "./src/condition/shouldRender.js"
```

Create a `condition/shouldRender.js` file in the same `src` directory as the extension that you want to control.

## /extensions/issue-tracker-conditional-action/src/condition/shouldRender.js

```javascript
import { getVariantsCount } from "../utils";


export default async function extension() {


  const { data } = shopify;
  const variantCount = await getVariantsCount(data.selected[0].id);
  return { display: variantCount > 1 };
}
```

Register your module by defining an `extension` function that is the `default` `export` of the file.

## /extensions/issue-tracker-conditional-action/src/condition/shouldRender.js

```javascript
import { getVariantsCount } from "../utils";


export default async function extension() {


  const { data } = shopify;
  const variantCount = await getVariantsCount(data.selected[0].id);
  return { display: variantCount > 1 };
}
```

Create a function called `getVariantsCount` to use Direct API to fetch variants of the product in your `utils.js` file.

## /extensions/issue-tracker-conditional-action/src/utils.js

```javascript
export async function updateIssues(id, newIssues) {
  // This example uses metafields to store the data. For more information, refer to https://shopify.dev/docs/apps/custom-data/metafields.
  return await makeGraphQLQuery(
    `mutation SetMetafield($namespace: String!, $ownerId: ID!, $key: String!, $type: String!, $value: String!) {
    metafieldDefinitionCreate(
      definition: {namespace: $namespace, key: $key, name: "Tracked Issues", ownerType: PRODUCT, type: $type, access: {admin: MERCHANT_READ_WRITE}}
    ) {
      createdDefinition {
        id
      }
    }
    metafieldsSet(metafields: [{ownerId:$ownerId, namespace:$namespace, key:$key, type:$type, value:$value}]) {
      userErrors {
        field
        message
        code
      }
    }
  }
  `,
    {
      ownerId: id,
      namespace: "$app:issues",
      key: "issues",
      type: "json",
      value: JSON.stringify(newIssues),
    },
  );
}


export async function getIssues(productId) {
  // This example uses metafields to store the data. For more information, refer to https://shopify.dev/docs/apps/custom-data/metafields.
  const res = await makeGraphQLQuery(
    `query Product($id: ID!) {
      product(id: $id) {
        metafield(namespace: "$app:issues", key:"issues") {
          value
        }
      }
    }
  `,
    { id: productId },
  );


  if (res?.data?.product?.metafield?.value) {
    return JSON.parse(res.data.product.metafield.value);
  }
}


async function makeGraphQLQuery(query, variables) {
  const graphQLQuery = {
    query,
    variables,
  };


  const res = await fetch("shopify:admin/api/graphql.json", {
    method: "POST",
    body: JSON.stringify(graphQLQuery),
  });


  if (!res.ok) {
    console.error("Network error");
  }


  return await res.json();
}


// Use direct API calls to fetch data from Shopify.
// See https://shopify.dev/docs/api/admin-graphql for more information about Shopify's GraphQL API
export async function getVariantsCount(id) {
  const getProductQuery = {
    query: `query Product($id: ID!) {
      product(id: $id) {
        variantsCount {
          count
        }
      }
    }`,
    variables: { id },
  };


  const res = await fetch("shopify:admin/api/graphql.json", {
    method: "POST",
    body: JSON.stringify(getProductQuery),
  });


  if (!res.ok) {
    console.error("Network error");
  }


  const productData = await res.json();
  return productData.data.product.variantsCount.count;
}
```

Use the `getVariantsCount` function to determine the number of variants on the product. Return an object with a key of `display` and a Boolean value to control whether the action extension menu item is visible.

## /extensions/issue-tracker-conditional-action/src/condition/shouldRender.js

```javascript
import { getVariantsCount } from "../utils";


export default async function extension() {


  const { data } = shopify;
  const variantCount = await getVariantsCount(data.selected[0].id);
  return { display: variantCount > 1 };
}
```

## Test hiding the admin action

After you've updated the UI extensions that provide your admin action and block, test them with the following steps:

1. Navigate to your app directory:

   ## Terminal

   ```bash
   cd <directory>
   ```

2. To build and preview your app, either start or restart your server with the following command:

   ## Terminal

   ```bash
   shopify app dev
   ```

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

4. Delete any variants that were added in the previous section.

5. If there are any product variants, delete them and confirm that the admin action is not visible in the More actions menu.

6. Add a product variant with two options and confirm that the admin action is visible in the **More actions** menu.

## /extensions/issue-tracker-conditional-block/src/BlockExtension.jsx

```jsx
import { render } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";


import { getIssues, updateIssues } from "../../issue-tracker-block/src/utils";


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


  const PAGE_SIZE = 3;


  function Extension() {
    const { i18n, data, navigation } = shopify;
    const [issues, setIssues] = useState([]);


    const productId = data.selected[0].id;
    const issuesCount = issues.length;
    const totalPages = issuesCount / PAGE_SIZE;


    const [loading, setLoading] = useState(true);
    const [_, setInitialValues] = useState([]);
    const [currentPage, setCurrentPage] = useState(1);


    const [shouldRender, setShouldRender] = useState(false);


    useEffect(() => {
      (async function getProductInfo() {
        const productData = await getIssues(productId);


        setLoading(false);


        if (productData?.data?.product?.variants?.edges.length > 1) {
          setShouldRender(true);
        }


        if (productData?.data?.product?.metafield?.value) {
          const parsedIssues = JSON.parse(
            productData.data.product.metafield.value,
          );
          setInitialValues(
            parsedIssues.map(({ completed }) => Boolean(completed)),
          );
          setIssues(parsedIssues);
        }
      })();
    }, [productId]);


    const paginatedIssues = useMemo(() => {
      if (issuesCount <= PAGE_SIZE) {
        // It's not necessary to paginate if there are fewer issues than the page size
        return issues;
      }


      // Slice the array after the last item of the previous page
      return [...issues].slice(
        (currentPage - 1) * PAGE_SIZE,
        currentPage * PAGE_SIZE,
      );
    }, [issuesCount, issues, currentPage]);
    console.log({ paginatedIssues });


    const handleChange = async (id, value) => {
      // Update the local state of the extension to reflect changes
      setIssues((currentIssues) => {
        // Create a copy of the array so that you don't mistakenly mutate the state
        const newIssues = [...currentIssues];
        // Find the index of the issue that you're interested in
        const editingIssueIndex = newIssues.findIndex(
          (listIssue) => listIssue.id == id,
        );
        // Overwrite that item with the new value
        newIssues[editingIssueIndex] = {
          // Spread the previous item to retain the values that you're not changing
          ...newIssues[editingIssueIndex],
          // Update the completed value
          completed: value === "completed" ? true : false,
        };
        return newIssues;
      });
    };


    const handleDelete = async (id) => {
      // Create a new array of issues, leaving out the one that you're deleting
      const newIssues = issues.filter((issue) => issue.id !== id);
      // Save to the local state
      setIssues(newIssues);
      // Commit changes to the database
      await updateIssues(productId, newIssues);
    };


    const onSubmit = async () => {
      // Commit changes to the database
      await updateIssues(productId, issues);
    };


    const onReset = () => {};


    const blockMarkup = loading ? (
      <s-spinner></s-spinner>
    ) : (
      <>
        <s-form id={`issues-form`} onSubmit={onSubmit} onReset={onReset}>
          {paginatedIssues.length > 0 ? (
            <s-table
              paginate
              onNextPage={() => setCurrentPage(currentPage + 1)}
              onPreviousPage={() => setCurrentPage(currentPage - 1)}
              hasNextPage={currentPage < totalPages}
              hasPreviousPage={currentPage > 1}
            >
              <s-table-header-row>
                <s-table-header listSlot="primary">
                  {i18n.translate("issue-column-heading")}
                </s-table-header>
                <s-table-header>
                  {i18n.translate("status-column-heading")}
                </s-table-header>
                <s-table-header></s-table-header>
              </s-table-header-row>
              <s-table-body>
                {paginatedIssues.map(
                  ({ id, title, description, completed }, index) => {
                    return (
                      <s-table-row key={id}>
                        <s-table-cell>
                          <s-stack direction="block">
                            <s-text>{title}</s-text>
                            <s-text>{description}</s-text>
                          </s-stack>
                        </s-table-cell>
                        <s-table-cell>
                          <s-select
                            labelAccessibilityVisibility="exclusive"
                            label={i18n.translate("select-label")}
                            value={completed ? "completed" : "todo"}
                            onChange={(e) => handleChange(id, (/** @type {HTMLInputElement} */ (e.target)).value)}
                          >
                            <s-option value="todo">
                              {i18n.translate("option-todo")}
                            </s-option>
                            <s-option value="completed">
                              {i18n.translate("option-completed")}
                            </s-option>
                          </s-select>
                        </s-table-cell>
                        <s-table-cell>
                          <s-button
                            variant="tertiary"
                            icon="delete"
                            accessibilityLabel={i18n.translate(
                              "delete-issue-button",
                            )}
                            onClick={() => handleDelete(id)}
                          />
                        </s-table-cell>
                      </s-table-row>
                    );
                  },
                )}
              </s-table-body>
              <s-button
                onClick={() => {
                  const url = `extension:issue-tracker-action`;
                  navigation?.navigate(url);
                }}
              >
                {i18n.translate("add-issue-button")}
              </s-button>
            </s-table>
          ) : (
            <s-text>{i18n.translate("no-issues-text")}</s-text>
          )}
          <s-box>
            <s-button
              onClick={() => {
                const url = `extension:issue-tracker-action`;
                navigation?.navigate(url);
              }}
            >
              {i18n.translate("add-issue-button")}
            </s-button>
          </s-box>
        </s-form>
      </>
    );


    // Only render the block body if there is more than one variant, otherwise, return null to collapse the block
    return (
      <s-admin-block
        heading={i18n.translate("name")}
        collapsedSummary={
          !shouldRender ? i18n.translate("collapsed-summary") : null
        }
      >
        {shouldRender ? blockMarkup : null}
      </s-admin-block>
    );
  }
};
```

## /extensions/issue-tracker-conditional-action/shopify.extension.toml

```toml
api_version = "2026-04"
[[extensions]]
name = "t:name"
handle = "conditional-admin-action-extension"
uid = "3ca7f446-20fb-da23-6b62-b3c1e2852d2bfcf7472e"
type = "ui_extension"


# Only 1 target can be specified for each Admin action condition extension
[[extensions.targeting]]
module = "./src/ActionExtension.jsx"
target = "admin.product-details.action.render"


# This is the relative path to the module that contains the logic to determine if the action trigger should be visible.
[extensions.targeting.should_render]
module = "./src/condition/shouldRender.js"
```

## /extensions/issue-tracker-conditional-action/src/condition/shouldRender.js

```javascript
import { getVariantsCount } from "../utils";


export default async function extension() {


  const { data } = shopify;
  const variantCount = await getVariantsCount(data.selected[0].id);
  return { display: variantCount > 1 };
}
```

## /extensions/issue-tracker-conditional-action/src/utils.js

```javascript
export async function updateIssues(id, newIssues) {
  // This example uses metafields to store the data. For more information, refer to https://shopify.dev/docs/apps/custom-data/metafields.
  return await makeGraphQLQuery(
    `mutation SetMetafield($namespace: String!, $ownerId: ID!, $key: String!, $type: String!, $value: String!) {
    metafieldDefinitionCreate(
      definition: {namespace: $namespace, key: $key, name: "Tracked Issues", ownerType: PRODUCT, type: $type, access: {admin: MERCHANT_READ_WRITE}}
    ) {
      createdDefinition {
        id
      }
    }
    metafieldsSet(metafields: [{ownerId:$ownerId, namespace:$namespace, key:$key, type:$type, value:$value}]) {
      userErrors {
        field
        message
        code
      }
    }
  }
  `,
    {
      ownerId: id,
      namespace: "$app:issues",
      key: "issues",
      type: "json",
      value: JSON.stringify(newIssues),
    },
  );
}


export async function getIssues(productId) {
  // This example uses metafields to store the data. For more information, refer to https://shopify.dev/docs/apps/custom-data/metafields.
  const res = await makeGraphQLQuery(
    `query Product($id: ID!) {
      product(id: $id) {
        metafield(namespace: "$app:issues", key:"issues") {
          value
        }
      }
    }
  `,
    { id: productId },
  );


  if (res?.data?.product?.metafield?.value) {
    return JSON.parse(res.data.product.metafield.value);
  }
}


async function makeGraphQLQuery(query, variables) {
  const graphQLQuery = {
    query,
    variables,
  };


  const res = await fetch("shopify:admin/api/graphql.json", {
    method: "POST",
    body: JSON.stringify(graphQLQuery),
  });


  if (!res.ok) {
    console.error("Network error");
  }


  return await res.json();
}


// Use direct API calls to fetch data from Shopify.
// See https://shopify.dev/docs/api/admin-graphql for more information about Shopify's GraphQL API
export async function getVariantsCount(id) {
  const getProductQuery = {
    query: `query Product($id: ID!) {
      product(id: $id) {
        variantsCount {
          count
        }
      }
    }`,
    variables: { id },
  };


  const res = await fetch("shopify:admin/api/graphql.json", {
    method: "POST",
    body: JSON.stringify(getProductQuery),
  });


  if (!res.ok) {
    console.error("Network error");
  }


  const productData = await res.json();
  return productData.data.product.variantsCount.count;
}
```

## Tutorial complete!

Congratulations! You learned how to hide your UI extension's admin blocks and actions when they are not relevant to a given target. Keep the momentum going with these related resources.

[Admin UI extension APIs\
\
](https://shopify.dev/docs/api/admin-extensions)

[Learn about the admin UI extension APIs.](https://shopify.dev/docs/api/admin-extensions)

[Participate\
\
](https://github.com/Shopify/ui-extensions)

[File any issues or feature requests on the UI Extensions GitHub repository.](https://github.com/Shopify/ui-extensions)

[Deploy\
\
](https://shopify.dev/docs/apps/deployment/app-versions)

[Learn how to deploy your UI extensions to merchants.](https://shopify.dev/docs/apps/deployment/app-versions)
