---
title: Use extensions to surface app data
description: Use extensions to surface app data
source_url:
  html: 'https://shopify.dev/docs/apps/build/sidekick/build-app-data'
  md: 'https://shopify.dev/docs/apps/build/sidekick/build-app-data.md'
---

# Use extensions to surface app data

Use an app extension to expose data in your app to Sidekick. By providing your app's data in an app extension, Sidekick can search your app's data to get tailored results. Merchants can get value from your app just by working with Sidekick, while also staying in their flow.

### Use case example in this guide

This guide shows you how to allow Sidekick to search your app's data. You can use this example to build your own app extension for search, and other tasks.

In this example, a merchant can ask Sidekick to find the best performing subject lines in their email campaigns. Sidekick searches using the app extension in the email app, then returns the best options. Clicking on an option invokes the email app, navigated right to the correct result.

![Sidekick email search interface](https://shopify.dev/assets/assets/admin/sidekick/email-performance-flow-C00FiYFF.png)

***

## Requirements

* [Create an app](https://shopify.dev/docs/apps/build/scaffold-app).
* For App Home, use the latest version of [App Bridge](https://shopify.dev/docs/api/app-home).
* Authenticate with [Shopify managed install](https://shopify.dev/docs/apps/build/authentication-authorization/cli-app-authentication) and [token exchange](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#token-exchange-grant), not the legacy OAuth install flow. See [Authentication](#authentication).
* Add an [`extensions_summary`](https://shopify.dev/docs/apps/build/sidekick#add-an-extensions-summary-to-your-app) to your `shopify.app.toml`. This is required for all apps with Sidekick-eligible extensions.
* Shopify CLI 3.90.0 or later. Run `shopify version` to check your installed version.
* Add `https://extensions.shopifycdn.com` to your app's CORS allowlist so your backend accepts requests from the Sidekick sandbox.

***

## Allow Sidekick to use your app's data

**Existing MCP servers:**

Existing MCP servers can't be reused directly with Sidekick. Tools must run in Shopify's sandbox and use Shopify authentication. The tools schema follows MCP-like patterns but is Shopify-specific — statically indexing tools is what keeps Sidekick fast.

In this example, a merchant can ask Sidekick to find the best performing subject lines in their email campaigns. Sidekick searches using the app extension in the email app, then returns the best options. Clicking on an option invokes the email app, navigated right to the correct result.

This app extension is headless, can be run at any point from anywhere, and can be called programmatically for Shopify search in the admin.

### Create an app extension

Use `shopify-cli` to create an app extension.

## Terminal

```terminal
shopify app generate extension --template app_data --name tools
```

This will create a UI extension in the `extensions` folder.

## App tools folder structure

```toml
extensions/tools
  ├── README.md
  ├── package.json
  ├── shopify.extension.toml // The config file for the extension
  ├── tsconfig.json
  ├── shopify.d.ts // Provides types for components and APIs available to the extension
  ├── src
  │   └── index.js // The code that powers your app extension
  └── tools.json // The schema for your app extension (created in the next step)
```

### Configure your app extension

Customize your app's extension in `shopify.extension.toml` to include a `description`. Optionally, you can link to instructions for how Sidekick should use your app extension.

## extensions/tools/shopify.extension.toml

```toml
[[extensions]]
name = "Email Tools"
description = "Tools for working with email, searching email campaigns, and getting email campaign stats"
handle = "tools"
type = "ui_extension"


[[extensions.targeting]]
module = "./src/index.js"
target = "admin.app.tools.data"
tools = "./tools.json"
instructions = "./instructions.md"
```

**Note:**

`admin.app.tools.data` is a special target that is not tied to a Shopify resource and executes headlessly.

The `description` field in `shopify.extension.toml` is required — you'll get a validation error if you leave it blank. Sidekick uses this field to determine when your extension is relevant to a merchant's question. A vague description like "Search extension for your app" won't give Sidekick enough context to invoke your extension reliably. Instead, be specific about the domain and tasks your extension covers. See [Writing effective extension descriptions](https://shopify.dev/docs/apps/build/sidekick#writing-effective-extension-descriptions) for detailed guidance and examples.

**Limits:**

The `description` field has a 256-token limit. The `instructions.md` file has a 2,048-token limit.

### Write your Java​Script module

When your app's extension is invoked, your JavaScript will be called automatically in Shopify's sandboxed browser environment. Tools run client-side. The sandbox provides access to [Direct API](https://shopify.dev/docs/api/admin-extensions/latest#direct-api-access) for making GraphQL queries, and [App Authentication](https://shopify.dev/docs/api/admin-extensions/latest#app-authentication) for fetching data from your app's backend — see [Authentication](#authentication) for token handling details.

Tools must return JSON. Sidekick doesn't currently render custom UI from extension responses, so use [resource links](#data-output-with-resource-links) to connect results to actions. Aim for tool responses under 1 second; extensions that consistently exceed this threshold may be skipped by Sidekick.

Your app extension must only be used for retrieving data from your app. If you need to perform actions in your app, use an [app extension](https://shopify.dev/docs/apps/build/sidekick/build-app-actions) and see [resource links](#data-output-with-resource-links) for more information. If you need to link to pages inside your embedded app, use an [app protocol URL](#data-output-with-embedded-app-links).

## extensions/tools/src/index.js

```js
export default () => {
  shopify.tools.register('get_campaigns', async ({name, date}) => {
    const response = await fetch('/api/campaigns', {
      method: 'POST',
      body: JSON.stringify({name, date})
    });
    return response.json();
  });


  shopify.tools.register('campaign_stats', async ({campaign_id}) => {
    const response = await fetch(`/api/campaigns/${campaign_id}/stats`);
    return response.json();
  });
}
```

**Limits:**

Your app extension must return a response of 4,000 tokens or less. Responses that exceed either limit will be rejected.

Responses should be returned within 1 second. Extensions that consistently exceed this threshold may be skipped by Sidekick.

### Write your tools schema

Declare your schema in the `JSON` file referenced in `shopify.extension.toml`.

## ./tools.json

```json
[
  {
    "$schema": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/tool.json",
    "name": "get_campaigns",
    "description": "Search email marketing campaigns",
    "inputSchema": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Perform a full text search"
        },
        "after": {
          "type": "string",
          "description": "Cursor for pagination"
        },
        "before": {
          "type": "string",
          "description": "Cursor for pagination"
        },
        "first": {
          "type": "integer",
          "description": "Number of results to return"
        },
        "last": {
          "type": "integer",
          "description": "Number of results from the end"
        },
        "creationPeriod": {
          "type": "object",
          "properties": {
            "from": {
              "type": "string",
              "format": "date-time"
            },
            "to": {
              "type": "string",
              "format": "date-time"
            }
          }
        },
        "editedAtPeriod": {
          "type": "object",
          "properties": {
            "from": {
              "type": "string",
              "format": "date-time"
            },
            "to": {
              "type": "string",
              "format": "date-time"
            }
          }
        },
        "effectiveStatusDatePeriod": {
          "type": "object",
          "properties": {
            "from": {
              "type": "string",
              "format": "date-time"
            },
            "to": {
              "type": "string",
              "format": "date-time"
            }
          }
        },
        "marketingActivityStatuses": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "ACTIVE",
              "CANCELLED",
              "DELETED",
              "DRAFT",
              "FAILED",
              "INACTIVE",
              "PAUSED",
              "PENDING",
              "SCHEDULED"
            ]
          }
        },
        "sortKey": {
          "type": "string",
          "enum": [
            "CREATED_AT",
            "EDITED_AT",
            "EFFECTIVE_STATUS_DATE",
            "STATUS_UPDATED_AT",
            "SUBJECT",
            "UPDATED_AT"
          ]
        },
        "sortOrder": {
          "type": "string",
          "enum": [
            "ASC",
            "DESC"
          ]
        },
        "contentType": {
          "type": "string",
          "enum": [
            "CUSTOM_CODE_BODY",
            "THEME_INSTANCE"
          ]
        }
      }
    }
  },
  {
    "$schema": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/tool.json",
    "name": "campaign_stats",
    "description": "Get statistics for a specific email marketing campaign",
    "inputSchema": {
      "type": "object",
      "properties": {
        "campaign_id": {
          "type": "string",
          "description": "The unique identifier of the campaign"
        }
      },
      "required": ["campaign_id"]
    }
  }
]
```

**Limits:**

Each tool `name` can be up to 64 characters. Each tool `description` can be up to 512 characters. You can register a maximum of 20 tools for each app, shared across all extensions (data and action).

### Write instructions for using the app extension

Define instructions for how Sidekick should use your app extension in a `instructions.md` file.

**Note:**

`instructions.md` is optional, but is highly recommended for providing context and guidance to Sidekick about your app extension.

## ./instructions.md

```md
## When to Use Email Tools


Use these tools when the merchant asks about:
- Email campaigns and newsletters
- Subscriber lists and segments
- Email automations and flows
- Email marketing metrics


## Important Guidelines


- Always confirm campaign details before sending
- When creating segments, verify the conditions are correct
- For campaign metrics, specify the date range if not provided
- Email tools uses "flows" for automated email sequences (not "automations")


## Common Workflows


### Sending a Campaign
1. First verify the campaign exists using get_campaign
2. Confirm the target segment with the merchant
3. Use get_campaign_stats with the campaign ID


### Creating a Segment
1. Ask the merchant for the segmentation criteria
2. Translate their request into conditions
3. Create the segment and confirm the name
```

***

## Putting it all together

After following this tutorial, your `extensions` folder structure should look like this:

## Folder structure

```toml
extensions/tools
  ├── instructions.md // The instructions for using the app extension
  ├── README.md //
  ├── package.json
  ├── tools.json // The schema for your tools
  ├── tsconfig.json
  ├── shopify.d.ts // Provides types for APIs available to the extension
  ├── shopify.extension.toml // The config file for the extension
  └── src
      └── index.js // The code that powers your app extension
```

### Data output with resource links

Sidekick is optimized for results that are returned in Model Context Protocol's [Resource Links](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#resource-links) format. Using the Resource Links format ensures Sidekick will know how to invoke your app's [actions](https://shopify.dev/docs/apps/build/sidekick/build-app-actions) on the data returned by your extension. For a worked example connecting a search tool's resource link output to an action-link extension's intent, see [End-to-end example: search, open, and edit an email campaign](https://shopify.dev/docs/apps/build/sidekick/build-app-actions#end-to-end-example-search-open-and-edit-an-email-campaign).

Your tool should return a `results` array containing one or more resource link objects. Each resource link must include the following fields:

| Field | Required | Description |
| - | - | - |
| `type` | Yes | The resource link type identifier. Must be `"resource_link"`. |
| `uri` | Yes | A URI that uniquely identifies the resource in your app. For example, `gid://application/email/123`. The `gid://` prefix is required if you want Sidekick to strip the GID and substitute the bare tail identifier into a `mapTo: "param"` URL placeholder. See [How placeholder substitution works](https://shopify.dev/docs/apps/build/sidekick/build-app-actions#how-placeholder-substitution-works). |
| `name` | Yes | A human-readable label for the resource. |
| `mimeType` | Yes | The content type of the resource. For example, `application/email`. Must match the `type` declared in your app's [intent configuration](https://shopify.dev/docs/apps/build/sidekick/build-app-actions#register-your-extension-as-an-intent). This is how Sidekick connects a search result to the correct action. |
| `_meta` | No | An object containing summary data about the resource. Use this to include key details that Sidekick can reason about without needing a separate fetch. |

**Note:**

The `mimeType` value in your resource link must match the `type` field in your intent's `shopify.extension.toml` configuration. This is what allows Sidekick to connect a data result to the right app action. For example, if your intent declares `type = "application/email"`, your resource links should use `mimeType: "application/email"`.

**Use `_meta` for summary data:**

`_meta` is the canonical place to attach the summary data the model needs to reason about a result. It matches the [MCP Resource Links](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#resource-links) shape and is what Sidekick's tooling and other tutorials look for. Use it instead of inventing parallel field names like `metadata` or `details`.

#### Basic example

Use the `uri` field to link directly to a specific resource in your app.

## Example search campaign tool results

```js
{
  results: [
    {
      type: 'resource_link',
      uri: 'gid://application/email/123',
      name: 'Sale starts soon!',
      mimeType: 'application/email'
    }
  ]
}
```

#### Include summary data with `_meta`

You can include summary data in the `_meta` field to give Sidekick context it can reason about immediately, without making additional requests. We recommend this hybrid approach when the model needs to answer questions about the data (for example, filtering by status or reporting on dates) while still providing clickable resource links.

## Example results with summary data

```js
{
  results: [
    {
      type: 'resource_link',
      uri: 'gid://application/email/123',
      name: 'Sale starts soon!',
      mimeType: 'application/email',
      _meta: {
        status: 'ACTIVE',
        editedAt: '2025-01-27T10:00:00Z',
        openRate: 0.42
      }
    },
    {
      type: 'resource_link',
      uri: 'gid://application/email/456',
      name: 'Holiday promo',
      mimeType: 'application/email',
      _meta: {
        status: 'DRAFT',
        editedAt: '2025-01-25T08:30:00Z'
      }
    }
  ]
}
```

#### Best practices for resource links

Follow these guidelines to get the best results from Sidekick when returning resource links.

*Keep `_meta` concise.* Only include the fields the model is most likely to need, such as status, dates, or key metrics. Large nested structures consume tokens quickly — if a response exceeds the 4,000-token limit (approximately 16,000 characters), it will be rejected and the merchant won't see any results.

*Let the resource link handle full detail.* The `uri` points to the complete resource in your app. Use `_meta` for a summary that helps the model answer the merchant's question, and let the merchant click through for everything else.

*Match `mimeType` to your intent type.* If `mimeType` doesn't match the `type` in your [intent configuration](https://shopify.dev/docs/apps/build/sidekick/build-app-actions#register-your-extension-as-an-intent), Sidekick won't be able to connect the search result to the correct action. For example, if your intent declares `type = "application/email"`, every resource link for that intent should use `mimeType: "application/email"`.

*Use stable URIs.* The `uri` value should be a stable identifier that won't change over time (for example, a GID). Sidekick may reference these URIs in follow-up actions, so they need to resolve consistently.

### Data output with embedded app links

Sidekick can link to pages in your embedded app when your tool returns results with URLs that use the `app:` protocol. Shopify constructs the base URL for your app and navigates directly to your embedded app.

Embedded app links are a different result shape from a [resource link](#data-output-with-resource-links): use a `url` field (not `uri`), and the resource-link `type`, `name`, and `mimeType` fields don't apply. Sidekick resolves any URL value that uses the `app:` protocol against your embedded app's base URL.

## Example embedded app link

```js
{
  results: [
    {
      url: 'app://email/123'
    }
  ]
}
```

***

## Authentication

Requests from a Sidekick tool to your app backend carry an ID token, which your backend exchanges for an access token using [token exchange](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#token-exchange-grant). Shopify recommends scaffolding your app with Shopify CLI, which sets it up with [Shopify managed installation](https://shopify.dev/docs/apps/build/authentication-authorization/cli-app-authentication) for installs and scope grants, and an app template that performs the token exchange for you. The legacy OAuth install flow doesn't work for Sidekick tools.

Tools run in Shopify's sandboxed browser environment and use Shopify's existing sandbox authentication for outbound `fetch` calls. For requests to your app's configured auth domain (or its subdomains), Shopify automatically attaches an `Authorization` header. For requests to any other domain, call `auth.idToken()` and attach the bearer token yourself.

If you scaffolded with the React Router app template, your server calls `authenticate.admin(request)`, which validates the ID token, performs the token exchange, and returns an authenticated `admin` client. Those requests are cross-origin, so pair `authenticate.admin()` with the returned `cors` helper to include the required headers:

```ts
const {cors} = await authenticate.admin(request);
return cors(json({...}));
```

If you aren't using Shopify's app templates or packages, see [Authenticate an embedded app without a template](https://shopify.dev/docs/apps/build/authentication-authorization/implement-token-exchange) to implement the exchange yourself. For more detail on how tools authenticate, see [App authentication](https://shopify.dev/docs/api/admin-extensions/latest#app-authentication).

***

## Develop and test your extension

Sidekick extensions run differently than some other extension types. Follow these steps to run and test your extension locally. For a step-by-step round-trip checklist that combines a data extension with an action-link extension, see [Verify the round trip locally](https://shopify.dev/docs/apps/build/sidekick/build-app-actions#verify-the-round-trip-locally) in the app actions guide.

### Run your extension locally

Start your local dev server to make your extension available in Sidekick:

## Terminal

```terminal
shopify app dev
```

Install your app on your dev store, then open the Shopify admin and navigate to your store.

**Don't use the `p` command:**

The `p` shortcut from the Shopify CLI isn't yet supported for Sidekick extensions. Open your dev store directly in your browser instead.

You should see your extension in the dev panel. Click the preview link for the `admin.app.tools.data` target to open Sidekick, then ask a question that would invoke your tool.

### Debug your extension

Tools execute client-side in the browser sandbox, so logs and errors appear in your browser's developer console. Changes you make reload automatically, so you can ask Sidekick to run the tools again.

***
