---
title: Product disclosures
description: >-
  Store, manage, and display regulatory product disclosures (such as Proposition
  65 and choking-hazard warnings) as structured data across every Shopify
  surface — Admin, themes, the Storefront API, checkout, and agentic channels.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/product-merchandising/product-disclosures
  md: >-
    https://shopify.dev/docs/apps/build/product-merchandising/product-disclosures.md
---

# Product disclosures

Product disclosures let merchants attach regulatory safety and compliance content — such as California Proposition 65 warnings or CPSC choking-hazard warnings — to products as **structured data** instead of free text buried in descriptions, tags, or theme code.

Because disclosures are structured, the same content renders consistently across the Online Store, the Shop app, custom storefronts, checkout, and agentic channels, and it can be syndicated to marketplaces without re-entry. Disclosure copy is preserved verbatim, which is what regulators and agents require.

This guide covers every surface where you can manage disclosures:

* [The data model](#data-model) — how disclosures are stored.
* [Manage disclosures with the Admin API](#manage-disclosures-with-the-admin-api).
* [Read disclosures with the Storefront API](#read-disclosures-with-the-storefront-api).
* [Display disclosures in a theme](#display-disclosures-in-a-theme) (Liquid).
* [Surface disclosures in checkout](#surface-disclosures-in-checkout) (Checkout UI extensions).
* [Access disclosures from agentic channels](#access-disclosures-from-agentic-channels) (Catalog / UCP).

**Note:**

Disclosures carry **legal weight** and are distinct from informational labels ("final sale", "limited edition"). The cost of a missing disclosure (legal exposure, channel delisting) is far higher than an unnecessary one, so over-disclosure is the safe default. Merchants own compliance liability; Shopify provides the tooling.

***

## Data model

A product disclosure is composed of three layers:

| Layer | What it is |
| - | - |
| Disclosure taxonomy | An open, Shopify-governed list of disclosure types published in the public [product-taxonomy](https://github.com/Shopify/product-taxonomy) repo. Each leaf (for example, Prop 65 cancer warning) has a stable ID exposed as `gid://shopify/TaxonomyDisclosure/<id>`, with default title, content, jurisdictions, legal citation, and symbol. Disclosure is a **separate axis** of the taxonomy, parallel to product categories, not a child of a category. |
| Disclosure metaobjects | Shop-scoped metaobject entries that hold the disclosure content shown to buyers. Shopify seeds canonical entries from the taxonomy, and merchants can edit them or author custom entries. Multiple entries of the same type are allowed (content can vary per product). |
| Product metafield | A standard product metafield, `shopify.disclosure` of type `list.disclosure_reference`, that holds the list of disclosure metaobjects applied to a product. |

### The product metafield

| Property | Value |
| - | - |
| Namespace | `shopify` |
| Key | `disclosure` |
| Type | `list.disclosure_reference` |
| Owner type | `Product` |
| Storefront API visible | Yes |

`disclosure_reference` is a dedicated metafield type for disclosure metaobjects. It exists as its own type because disclosures have behavior generic references don't: jurisdiction-aware contextualization, native first-party channel rendering, and tailored Admin UX.

### Disclosure metaobject definitions and entry types

Shopify provides **standard metaobject definitions**, one per disclosure leaf. The metaobject `type` is the stable, machine-readable identifier and uses a reserved `shopify--` prefix so merchant-authored entries can never collide with Shopify-managed ones.

Shopify provides these entry types:

| Group | Entry types |
| - | - |
| Chemical exposure (Prop 65) | `shopify--disclosure-us-ca-prop65-cancer`, `shopify--disclosure-us-ca-prop65-reproductive`, `shopify--disclosure-us-ca-prop65-cancer_reproductive`, `shopify--disclosure-us-ca-prop65-alcohol` |
| Choking hazards (CPSC) | `shopify--disclosure-us-cpsc-choking_small_parts`, `shopify--disclosure-us-cpsc-choking_balloons`, `shopify--disclosure-us-cpsc-choking_marbles`, `shopify--disclosure-us-cpsc-choking_small_balls` |
| Custom | `shopify--disclosure-custom` |

Each entry exposes these fields:

| Field | Type | Required | Description |
| - | - | - | - |
| `taxonomy_reference` | `product_taxonomy_disclosure_reference` | Yes (not on `custom`) | Reference to the taxonomy leaf this entry instantiates, set as its GID (for example `gid://shopify/TaxonomyDisclosure/<id>`). It's a dedicated reference type, not free text; it disambiguates sub-leaves within a disclosure group. |
| `title` | single line text (max 100) | Yes | Buyer-facing heading (for example, "Choking Hazard"). |
| `internal_label` | single line text (max 255) | Yes | Internal-only label that mirrors the disclosure name. Not exposed to the Storefront API. |
| `content` | rich text | No | The disclosure copy, rendered verbatim. Stored as a rich-text JSON document. |
| `symbol` | file reference (`Image`) | No | The visual symbol (for example, the Prop 65 triangle or ISO 7010 W001), stored as a reference to an image media file. For taxonomized disclosures it's populated automatically from the taxonomy when the entry is created. |
| `jurisdictions` | list of single line text (min 1) | Yes | Jurisdiction codes the disclosure applies to, for example `["US-CA"]`. |
| `display_preferences` | JSON | Yes | Which surfaces to render on. The `surfaces` array accepts `product_page`, `cart`, and `checkout`, for example `{"surfaces":["product_page"]}`. |

**Note:**

**Custom disclosures** (`shopify--disclosure-custom`) are the sanctioned escape hatch for content Shopify doesn't yet standardize. They have no `taxonomy_reference` because there's no leaf to point at. They still require `title`, `internal_label`, `jurisdictions`, and `display_preferences`; capture `jurisdictions` directly on the entry.

### Grain

Disclosures are stored at the **product grain**. They are not stored per variant; variant-specific cases are handled by over-disclosing at the product level or surfacing variant messages in checkout. When surfaced through the Catalog Lookup API, product-grain disclosures are transformed to the variant grain (see [agentic channels](#access-disclosures-from-agentic-channels)).

***

## Requirements

To work with product disclosures, you need:

* An app that can make [authenticated requests](https://shopify.dev/docs/api/admin-graphql#authentication) to the GraphQL Admin API.
* The `write_products` and `write_metaobjects` [access scopes](https://shopify.dev/docs/api/usage/access-scopes).
* To read disclosures from a custom storefront, a Storefront API access token with the `unauthenticated_read_product_listings` and `unauthenticated_read_metaobjects` [access scopes](https://shopify.dev/docs/api/usage/access-scopes).

***

## Manage disclosures with the Admin API

For product disclosures, use a three-step API flow: create the disclosure metaobject instances, enable the product disclosure metafield definition, then set the metafield value on the product. You can do everything below in the Admin GraphQL API, or perform the equivalent steps in the product details page in the Shopify admin.

Creating a standard disclosure metaobject auto-enables its metaobject definition, so there's no separate step to enable definitions. Enabling the `shopify.disclosure` metafield definition is separate: it creates the product-level slot but doesn't create any metaobjects or attach them to a product.

### Step 1: Create the disclosure metaobject

Create a disclosure entry with `metaobjectCreate`. If the shop hasn't enabled the standard metaobject definition for that `type`, this call enables it before creating the entry. `title`, `internal_label`, `jurisdictions`, and `display_preferences` are required (plus `taxonomy_reference` on non-`custom` types); `content` is optional. For taxonomized disclosures, the `symbol` is populated automatically from the taxonomy when the entry is created, so you don't set it. The `content` field is a rich-text JSON document so the copy renders verbatim. Repeat the call for each disclosure you need to create.

```graphql
mutation CreateDisclosureMetaobject($metaobject: MetaobjectCreateInput!) {
  metaobjectCreate(metaobject: $metaobject) {
    metaobject {
      id
      handle
      type
      fields { key value }
    }
    userErrors { field message code }
  }
}
```

```json
{
  "metaobject": {
    "type": "shopify--disclosure-us-ca-prop65-cancer",
    "handle": "us-ca-prop65-cancer-default",
    "fields": [
      { "key": "taxonomy_reference", "value": "gid://shopify/TaxonomyDisclosure/<id>" },
      { "key": "title", "value": "Prop 65 Cancer Warning" },
      { "key": "internal_label", "value": "Prop 65 Cancer Warning" },
      { "key": "content", "value": "{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"value\":\"WARNING:\",\"bold\":true},{\"type\":\"text\",\"value\":\" Cancer - http://www.P65Warnings.ca.gov\"}]}]}" },
      { "key": "jurisdictions", "value": "[\"US-CA\"]" },
      { "key": "display_preferences", "value": "{\"surfaces\":[\"product_page\"]}" }
    ]
  }
}
```

Save the returned `metaobject.id`; you reference it in Step 3.

**Note:**

Valid `TaxonomyDisclosure` IDs are 2–5 and 13–16. The `handle` is merchant-visible and useful as a stable mapping key for CSV import/export and PIM integrations.

### Step 2: Enable the product disclosure metafield definition

Enable the standard `shopify.disclosure` metafield definition on products with `standardMetafieldDefinitionEnable`. This creates the product-level metafield definition; it doesn't attach any values to a product.

```graphql
mutation EnableProductDisclosureMetafieldDefinition {
  standardMetafieldDefinitionEnable(
    ownerType: PRODUCT
    namespace: "shopify"
    key: "disclosure"
  ) {
    createdDefinition {
      id
      namespace
      key
      ownerType
      type { name }
    }
    userErrors { field message }
  }
}
```

### Step 3: Set the product metafield value

Set the `shopify.disclosure` metafield to a JSON list of the disclosure metaobject GIDs returned in Step 1.

```graphql
mutation SetProductDisclosure($productId: ID!, $value: String!) {
  metafieldsSet(metafields: [{
    ownerId: $productId
    namespace: "shopify"
    key: "disclosure"
    type: "list.disclosure_reference"
    value: $value
  }]) {
    metafields { id namespace key type value }
    userErrors { field message code }
  }
}
```

```json
{
  "productId": "gid://shopify/Product/<id>",
  "value": "[\"gid://shopify/Metaobject/<id>\"]"
}
```

### Reading disclosures in the Admin API

Query the product metafield and dereference the metaobject fields:

```graphql
{
  product(id: "gid://shopify/Product/<id>") {
    metafield(namespace: "shopify", key: "disclosure") {
      references(first: 10) {
        nodes {
          ... on Metaobject {
            id
            type
            field(key: "title") { value }
            field(key: "content") { value }
            field(key: "symbol") { value }
            field(key: "jurisdictions") { value }
          }
        }
      }
    }
  }
}
```

### Cleanup

To remove disclosure support, delete the metafield definition (optionally removing associated metafields), then delete the metaobject definitions.

```graphql
mutation DeleteDisclosureMetafieldDefinition {
  metafieldDefinitionDelete(
    id: "gid://shopify/MetafieldDefinition/<id>"
    deleteAllAssociatedMetafields: true
  ) {
    deletedDefinitionId
    userErrors { field message code }
  }
}
```

```graphql
mutation DeleteDisclosureMetaobjectDefinitions {
  prop65Cancer: metaobjectDefinitionDelete(id: "gid://shopify/MetaobjectDefinition/<id>") {
    deletedId
    userErrors { field message code }
  }
}
```

### Bulk and CSV

* **Bulk editor**: you can apply taxonomy disclosures to products in bulk through the standard mixed-reference bulk-edit flow (single column).
* **CSV import/export**: disclosures are imported and exported by metaobject `handle`, the same as other mixed-reference metafields. Use a stable handle so external systems (for example, a PIM) can map to the disclosure consistently.

***

## Read disclosures with the Storefront API

Custom storefronts read disclosures by dereferencing the product metafield's metaobject references.

```graphql
query ProductDisclosures($handle: String!) {
  product(handle: $handle) {
    metafield(namespace: "shopify", key: "disclosure") {
      references(first: 10) {
        nodes {
          ... on Metaobject {
            type
            title: field(key: "title") { value }
            content: field(key: "content") { value }
            symbol: field(key: "symbol") {
              reference {
                ... on MediaImage { image { url altText } }
              }
            }
            jurisdiction: field(key: "jurisdictions") { value }
          }
        }
      }
    }
  }
}
```

Two of these fields aren't plain strings, so render them accordingly:

* **`content`** comes back as a **rich-text JSON document**, not HTML or plain text. Render it through a rich-text renderer (parse the JSON and map its nodes); don't print the raw `value`, or buyers will see JSON.
* **`symbol`** is a **file reference**. Dereference it through `reference { ... on MediaImage { image { url altText } } }` to get a usable image URL. The field's raw `value` is only the `MediaImage` GID, which won't render as an image.

Filter by `jurisdictions` against the buyer's market if you want jurisdiction-aware display; the API returns all disclosures applied to the product.

***

## Display disclosures in a theme

On the Online Store, disclosures render through a self-contained Liquid section that reads the `shopify.disclosure` metafield. It renders only when a product has disclosures attached, so it's safe to include in every product template. The section shows disclosures in a collapsible accordion: collapsed shows all titles on one line separated by bullets; expanded shows each disclosure's full content.

### Add the section

1. In your Shopify admin, go to **Online Store > Themes > Edit code**.
2. Under **Sections**, click **Add a new section** and name it `disclosures`.
3. Replace the contents with the section below.

```liquid
{%- assign disclosures = product.metafields.shopify.disclosure.value -%}


{%- if disclosures != blank -%}
  <div style="padding: 2rem 1.5rem;">
    {%- if section.settings.heading != blank -%}
      <h3 style="margin-bottom: 2rem;">{{ section.settings.heading | escape }}</h3>
    {%- endif -%}


    <details class="disclosures__details"{% if section.settings.open_by_default %} open{% endif %}>
      <summary class="disclosures__summary">
        {%- for disclosure in disclosures -%}
          <span class="disclosures-summary-item">
            {%- if disclosure.symbol != blank -%}
              <img src="{{ disclosure.symbol.value | image_url: width: 20 }}" alt="" width="20" height="20" loading="lazy">
            {%- endif -%}
            <span>{{ disclosure.title.value }}</span>
          </span>
          {%- unless forloop.last -%}<span class="disclosures-separator">&bull;</span>{%- endunless -%}
        {%- endfor -%}
      </summary>
      <div class="disclosures__content">
        {%- for disclosure in disclosures -%}
          <div class="disclosures-item">
            <div class="disclosures-item__header">
              {%- if disclosure.symbol != blank -%}
                <img src="{{ disclosure.symbol.value | image_url: width: 20 }}" alt="" width="20" height="20" loading="lazy">
              {%- endif -%}
              {%- if disclosure.title.value != blank -%}<span>{{ disclosure.title.value }}</span>{%- endif -%}
            </div>
            <div class="disclosures-item__content">{{ disclosure.content | metafield_tag }}</div>
          </div>
        {%- endfor -%}
      </div>
    </details>
  </div>
{%- endif -%}


{% schema %}
{
  "name": "Disclosures",
  "limit": 1,
  "settings": [
    { "type": "text", "id": "heading", "label": "Heading" },
    { "type": "checkbox", "id": "open_by_default", "label": "Open by default", "default": false }
  ],
  "presets": [{ "name": "Disclosures" }]
}
{% endschema %}
```

**Note:**

This sample omits the accordion CSS/JS for brevity. Style the accordion to span the full page width — disclosure content needs room to be readable.

### Render the section

How you render depends on your template format.

* **JSON templates (Online Store 2.0)**: open the theme editor on your product template, click **Add section**, and select **Disclosures**. The section supports a **Heading** setting (optional heading above the accordion) and an **Open by default** setting (whether the accordion starts expanded).
* **Liquid templates**: add `{% section 'disclosures' %}` to `templates/product.liquid` where you want disclosures to appear.

### Placement in first-party themes

The disclosures accordion is designed to be **full width**, which affects where it can live:

* **Dawn**: places disclosures in a standalone, full-width section as a sibling **below** Product information. Dawn's Product information is locked to one column of a two-column grid, so a block there can't be full width. Making disclosures its own section keeps the editor's block order matching the rendered order.
* **Horizon**: places disclosures in a static block **inside** Product information (at the bottom). Horizon allows blocks within Product information to render outside the constrained column, so the block stays full width while editor ordering stays accurate.

Merchants can edit the heading, toggle the default open state, move the section/block, and remove it entirely if they prefer to surface disclosures elsewhere.

***

## Surface disclosures in checkout

**Shopify Plus:**

Checkout UI extensions are available only to [Shopify Plus](https://www.shopify.com/plus) merchants.

Disclosures render natively on the product details page and cart. To also surface them in checkout, use a [Checkout UI extension](https://shopify.dev/docs/api/checkout-ui-extensions) that reads each product's disclosure metaobjects from the Storefront API (see [Read disclosures with the Storefront API](#read-disclosures-with-the-storefront-api)) and renders them.

The extension needs Storefront API access — the `api_access` capability in your `shopify.extension.toml`:

```toml
[extensions.capabilities]
api_access = true
```

***

## Access disclosures from agentic channels

Disclosures are exposed to agentic commerce through the **Universal Commerce Protocol (UCP)** `lookup_catalog` response, reusing the `message_warning` schema rather than a new top-level field.

A disclosure is a `message` with `type: "warning"` and `presentation: "disclosure"`. The top-level `messages[]` array is the canonical surface, and each entry is anchored to a product via a JSONPath `path`. This keeps the bulk case unambiguous: a response with twenty products carrying different disclosures yields twenty `messages[]` entries, each pointing at its product node.

```json
{
  "messages": [
    {
      "type": "warning",
      "code": "prop65",
      "path": "$.products[0]",
      "content": "**California Proposition 65 Warning.** This product can expose you to chemicals including acrylamide, which is known to the State of California to cause cancer.",
      "content_type": "markdown",
      "presentation": "disclosure",
      "image_url": "https://{cdn}/prop65-warning.svg",
      "url": "https://www.p65warnings.ca.gov"
    },
    {
      "type": "warning",
      "code": "choking_hazard",
      "path": "$.products[2]",
      "content": "**Choking hazard.** Small parts. Not suitable for children under 3 years.",
      "content_type": "markdown",
      "presentation": "disclosure",
      "image_url": "https://{cdn}/choking-hazard.svg"
    }
  ]
}
```

Disclosures are sourced at the product grain in Core (only active-status entries are returned). Per the UCP contract they're then surfaced to the variant grain in the Catalog response, with one `messages[]` entry per disclosure anchored by `path` (no aggregation or dedupe). The shape extends to future variant-grain disclosures via `$.products[N].variants[M]` without breaking.

***

## Jurisdictions and display preferences

* **Jurisdictions** use country and subdivision codes (for example, `US`, `US-CA`). Regional groupings (EU, NAFTA) are not modeled. For taxonomized disclosures jurisdiction comes from the taxonomy leaf; for custom disclosures, set `jurisdictions` on the entry.
* **Display preferences** (`{"surfaces": ["product_page"]}`) declare which surfaces a disclosure should render on. The `surfaces` array accepts `product_page`, `cart`, and `checkout`. Disclosures render natively on the product details page and cart; to render in checkout, use a Checkout UI extension (see [Surface disclosures in checkout](#surface-disclosures-in-checkout)).

***

## Developer tools and resources

[GraphQL Admin API reference\
\
](https://shopify.dev/docs/api/admin-graphql)

[Explore the metaobject, metafield, and file objects and mutations used to create and manage disclosures.](https://shopify.dev/docs/api/admin-graphql)

[Storefront API reference\
\
](https://shopify.dev/docs/api/storefront)

[Read disclosure metaobjects and their fields from custom storefronts.](https://shopify.dev/docs/api/storefront)

[Metaobjects\
\
](https://shopify.dev/docs/apps/build/metaobjects)

[Learn how metaobject definitions and entries model structured data.](https://shopify.dev/docs/apps/build/metaobjects)

[Metafields\
\
](https://shopify.dev/docs/apps/build/metafields)

[Learn how metafields extend Shopify resources with custom data.](https://shopify.dev/docs/apps/build/metafields)

***
