---
title: Connect admin UI extensions
description: Learn how to use admin actions and blocks together to create cohesive features in the Shopify admin.
source_url:
html: https://shopify.dev/docs/apps/build/admin/actions-blocks/connect-admin-extensions
md: https://shopify.dev/docs/apps/build/admin/actions-blocks/connect-admin-extensions.md
---
# Connect admin UI extensions
This guide is the third 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 code from the [admin action UI extension](https://shopify.dev/docs/apps/build/admin/actions-blocks/build-admin-action) and [admin block UI extension](https://shopify.dev/docs/apps/build/admin/actions-blocks/build-admin-block) guides.

At this point in the tutorial series, you can create new issues using an [admin action](https://shopify.dev/docs/apps/build/admin/actions-blocks/build-admin-action) and you can view and delete the created issues using an [admin block](https://shopify.dev/docs/apps/build/admin/actions-blocks/build-admin-block). While this is functional, the merchant experience would be better if all of the features could be used in the same location.
Now, you'll modify the block and action so that merchants can create and edit issues directly from the block.
## What you'll learn
In this tutorial, you'll learn how to do the following tasks:
* Update your admin block UI extension to launch admin action UI extensions with an intent, to indicate whether it should edit or create a resource
* Update your admin action UI extension, so that it can listen for the intent and edit the appropriate resource
* Run the UI extensions locally and test them on a development store
## 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)
## Add a button to open your action form
Add a button that lets merchants create new issues directly from the block so that they don't have to navigate to **More actions** at the top of the page.
This button launches the same UI extension for an admin action that you've already written, so you don't have to duplicate your code.
Access the [navigation](https://shopify.dev/docs/api/admin-extensions/api/block-extension-api) method from the API.
## src/BlockExtension.jsx
```jsx
import { render } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";
import { updateIssues, getIssues } from "./utils";
export default async () => { {
render(, document.body);
}
const PAGE_SIZE = 3;
function Extension() {
const { data, navigation, i18n } = shopify;
const [loading, setLoading] = useState(true);
const [_, setInitialValues] = useState([]);
const [issues, setIssues] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const productId = data.selected[0].id;
const issuesCount = issues.length;
const totalPages = issuesCount / PAGE_SIZE;
useEffect(() => {
(async function getProductInfo() {
// Load the product's metafield of type issues
const productData = await getIssues(productId);
setLoading(false);
if (productData?.data?.product?.metafield?.value) {
const parsedIssues = JSON.parse(
productData.data.product.metafield.value,
);
setInitialValues(
parsedIssues.map(({ completed }) => Boolean(completed)),
);
setIssues(parsedIssues);
```
Add a button to call `navigation.navigate`, passing your extension handle to the method as `extension:{extension-handle}`. The extension handle is defined in your extension's `shopify.extension.toml` file.
Note
Currently, it's only possible to navigate between UI extensions on the same resource page in admin. For example, you can navigate from a block on the product details page (`admin.product-details.block.render`) to an action on the product details page (`admin.product-details.action.render`).
## src/BlockExtension.jsx
```jsx
import { render } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";
import { updateIssues, getIssues } from "./utils";
export default async () => { {
render(, document.body);
}
const PAGE_SIZE = 3;
function Extension() {
const { data, navigation, i18n } = shopify;
const [loading, setLoading] = useState(true);
const [_, setInitialValues] = useState([]);
const [issues, setIssues] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const productId = data.selected[0].id;
const issuesCount = issues.length;
const totalPages = issuesCount / PAGE_SIZE;
useEffect(() => {
(async function getProductInfo() {
// Load the product's metafield of type issues
const productData = await getIssues(productId);
setLoading(false);
if (productData?.data?.product?.metafield?.value) {
const parsedIssues = JSON.parse(
productData.data.product.metafield.value,
);
setInitialValues(
parsedIssues.map(({ completed }) => Boolean(completed)),
);
setIssues(parsedIssues);
```
Add a button to display when there are no issues to show.
## src/BlockExtension.jsx
```jsx
import { render } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";
import { updateIssues, getIssues } from "./utils";
export default async () => { {
render(, document.body);
}
const PAGE_SIZE = 3;
function Extension() {
const { data, navigation, i18n } = shopify;
const [loading, setLoading] = useState(true);
const [_, setInitialValues] = useState([]);
const [issues, setIssues] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const productId = data.selected[0].id;
const issuesCount = issues.length;
const totalPages = issuesCount / PAGE_SIZE;
useEffect(() => {
(async function getProductInfo() {
// Load the product's metafield of type issues
const productData = await getIssues(productId);
setLoading(false);
if (productData?.data?.product?.metafield?.value) {
const parsedIssues = JSON.parse(
productData.data.product.metafield.value,
);
setInitialValues(
parsedIssues.map(({ completed }) => Boolean(completed)),
);
setIssues(parsedIssues);
```
## Modify the block to launch actions with intents for editing
Add an **Edit** button to each issue displayed by the UI extension's block. When clicked, this button launches the action using the same [navigation API](https://shopify.dev/docs/api/admin-extensions/api/block-extension-api) that you used in the previous part of this tutorial.
Add a button for each issue. This time, when you call navigate for the **Edit** button, you'll also add a URL parameter that specifies the ID of issue as `extension:admin-issue-tracker-action?issueId={id}`.
## src/BlockExtension.jsx
```jsx
import { render } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";
import { updateIssues, getIssues } from "./utils";
export default async () => { {
render(, document.body);
}
const PAGE_SIZE = 3;
function Extension() {
const { data, navigation, i18n } = shopify;
const [loading, setLoading] = useState(true);
const [_, setInitialValues] = useState([]);
const [issues, setIssues] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const productId = data.selected[0].id;
const issuesCount = issues.length;
const totalPages = issuesCount / PAGE_SIZE;
useEffect(() => {
(async function getProductInfo() {
// Load the product's metafield of type issues
const productData = await getIssues(productId);
setLoading(false);
if (productData?.data?.product?.metafield?.value) {
const parsedIssues = JSON.parse(
productData.data.product.metafield.value,
);
setInitialValues(
parsedIssues.map(({ completed }) => Boolean(completed)),
);
setIssues(parsedIssues);
```
## Modify the action to edit an issue when launched with an intent
Edit your UI extension, so the action checks for the presence of the parameter that you passed.
You'll use the `intents` API, which gives your extension access to information about how it's been launched. When the action extension detects an `issueId` parameter in the launch URL from the API, it edits the issue with the ID that that's been passed. To enable editing, you'll first fetch the issue's existing data, and then use it to populate the form.
## src/ActionExtension.jsx
```jsx
import { render } from "preact";
import { useCallback, useEffect, useState } from "preact/hooks";
import { getIssues, updateIssues } from "./utils";
export default async () => { {
render(, document.body);
}
function generateId(allIssues) {
return !allIssues?.length ? 0 : allIssues[allIssues.length - 1].id + 1;
}
function validateForm({ title, description }) {
return {
isValid: Boolean(title) && Boolean(description),
errors: {
title: !title,
description: !description,
},
};
}
function Extension() {
const { close, data, intents, i18n } = shopify;
const issueId = intents?.launchUrl
? new URL(intents?.launchUrl)?.searchParams?.get("issueId")
: null;
const [loading, setLoading] = useState(issueId ? true : false);
const [issue, setIssue] = useState({
title: "",
description: "",
id: issueId,
});
const [allIssues, setAllIssues] = useState([]);
```
When the user saves the issue, you'll edit the existing issue instead of creating a new one.
## src/ActionExtension.jsx
```jsx
import { render } from "preact";
import { useCallback, useEffect, useState } from "preact/hooks";
import { getIssues, updateIssues } from "./utils";
export default async () => { {
render(, document.body);
}
function generateId(allIssues) {
return !allIssues?.length ? 0 : allIssues[allIssues.length - 1].id + 1;
}
function validateForm({ title, description }) {
return {
isValid: Boolean(title) && Boolean(description),
errors: {
title: !title,
description: !description,
},
};
}
function Extension() {
const { close, data, intents, i18n } = shopify;
const issueId = intents?.launchUrl
? new URL(intents?.launchUrl)?.searchParams?.get("issueId")
: null;
const [loading, setLoading] = useState(issueId ? true : false);
const [issue, setIssue] = useState({
title: "",
description: "",
id: issueId,
});
const [allIssues, setAllIssues] = useState([]);
```
## Test the UI extensions
1. Navigate to your app 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 developer console.
4. On the Dev Console page, click on the preview link for the issue tracker UI extension.
5. The product details page opens. If you don't have a product in your store, then you need to create one.
6. To find your block, scroll to the bottom of the page. It should display the issues that you've created so far.
7. On one of the issues, click **Edit** button. This should open up an action that's pre-populated with the issue details.
8. Edit the issue and save it. The updated issue should be reflected in the block at the bottom of the page.

## src/BlockExtension.jsx
```jsx
import { render } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";
import { updateIssues, getIssues } from "./utils";
export default async () => { {
render(, document.body);
}
const PAGE_SIZE = 3;
function Extension() {
const { data, navigation, i18n } = shopify;
const [loading, setLoading] = useState(true);
const [_, setInitialValues] = useState([]);
const [issues, setIssues] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const productId = data.selected[0].id;
const issuesCount = issues.length;
const totalPages = issuesCount / PAGE_SIZE;
useEffect(() => {
(async function getProductInfo() {
// Load the product's metafield of type issues
const productData = await getIssues(productId);
setLoading(false);
if (productData?.data?.product?.metafield?.value) {
const parsedIssues = JSON.parse(
productData.data.product.metafield.value,
);
setInitialValues(
parsedIssues.map(({ completed }) => Boolean(completed)),
);
setIssues(parsedIssues);
```
### Next steps
[](https://shopify.dev/docs/apps/admin/admin-actions-and-blocks/connect-extension-and-backend)
[Connect your app's backend](https://shopify.dev/docs/apps/admin/admin-actions-and-blocks/connect-extension-and-backend)
[Complete the next guide in this tutorial series by connecting to your app's backend.](https://shopify.dev/docs/apps/admin/admin-actions-and-blocks/connect-extension-and-backend)
[](https://shopify.dev/docs/api/admin-extensions)
[Admin UI extension APIs](https://shopify.dev/docs/api/admin-extensions)
[Learn about admin UI extensions, components, and APIs.](https://shopify.dev/docs/api/admin-extensions)
[](https://shopify.dev/docs/apps/deployment/app-versions)
[Deployment](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)
[](https://github.com/Shopify/ui-extensions)
[Issues](https://github.com/Shopify/ui-extensions)
[File any issues or feature requests on the UI Extensions GitHub repository.](https://github.com/Shopify/ui-extensions)