---
title: Shared-namespace metafield rendered as active content
description: >-
  Investigates metafield and metaobject values from shared namespaces, which
  other installed apps can write, that reach HTML, JavaScript, or URL output,
  and shows how to fix them.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/security/app-security-checks/shared-namespace-metafield-render
  md: >-
    https://shopify.dev/docs/apps/build/security/app-security-checks/shared-namespace-metafield-render.md
---

# Shared-namespace metafield rendered as active content

Investigates metafield and metaobject values from shared namespaces, which other installed apps can write, that reach HTML, JavaScript, or URL output, and shows how to fix them.

| Detail | Value |
| - | - |
| Check ID | `SHARED_NAMESPACE_METAFIELD_RENDER` |
| Severity | Medium |
| How it runs | Agent review |

***

## Why it matters

[Merchant-owned metafields](https://shopify.dev/docs/apps/build/metafields#metafield-ownership) and [metaobjects](https://shopify.dev/docs/apps/build/metaobjects#metaobject-ownership) can be written by the merchant and by every other app installed on the shop that has write access to that resource. If one of those apps is compromised, then an attacker can overwrite a value that your app renders. A value in a shared namespace is lower-trust input, even when your app is the only app that's meant to write it.

This check reports a shared-namespace value that reaches HTML, JavaScript, or URL output without escaping or serialization for that context. It also reports a value that's deliberately rendered as raw HTML, script, or a URL, such as widget markup that your app stores in `reviews.widget_html`. It covers Liquid in theme app extensions, Storefront API responses written to the DOM, Admin API responses rendered in the embedded app, and app proxy responses.

This check is reported alongside the cross-site scripting checks, such as [`THEME_EXTENSION_XSS`](https://shopify.dev/docs/apps/build/security/app-security-checks/theme-extension-xss) and [`UNSAFE_INNERHTML`](https://shopify.dev/docs/apps/build/security/app-security-checks/unsafe-innerhtml), not instead of them. It's about who else can write the value. Those checks still require you to escape it.

***

## How ownership is decided

Your coding agent decides ownership from the namespace or type prefix in your code. It doesn't read the definitions in `shopify.app.toml`.

* **App-owned:** `$app` and `$app:<sub-namespace>`, which you read in Liquid with square brackets, such as `product.metafields["$app"].<key>`. Also a literal `app--` namespace, and the metafields of the Liquid [`app`](https://shopify.dev/docs/api/liquid/objects/app) object, `app.metafields`. For metaobjects, a `$app:` type.
* **Shared:** Every other namespace or type, such as `custom`, `reviews`, or `specs`. This includes a bare `app` namespace in Liquid, such as `product.metafields.app.<key>`, which isn't translated to your app's namespace. It also includes `shopify--` namespaces and standard definitions: Shopify controls their structure, but merchants and apps can write their values.
* **Unresolved:** A namespace that's built at runtime, such as a `namespace` variable passed to a query. Your agent names the value instead of guessing its ownership.

Any `app--` prefix is treated as app-owned, even though it could belong to a different app.

An app-owned value isn't automatically safe. The merchant can still edit it when the definition allows it, and the cross-site scripting checks still require escaping.

***

## Vulnerable example

## extensions/reviews/blocks/reviews-widget.liquid

```liquid
{% comment %}
  Widget markup that this app writes to the shared reviews
  namespace, rendered raw. Any other app with write access to
  products can replace it with script that runs on every
  product page.
{% endcomment %}
<div class="reviews-widget">
  {{ product.metafields.reviews.widget_html }}
</div>
```

***

## How to fix

Move the field to an app-owned namespace with a declared definition, prefer typed fields such as rich text or URL, or escape the value for its context.

## extensions/reviews/blocks/reviews-widget.liquid

```liquid
{% comment %}
  Store the content in an app-owned rich text metafield, so other
  apps can't write it, and let metafield_tag render the markup.
  Read app-owned metafields through the $app prefix. A bare app
  namespace isn't app-owned.
{% endcomment %}
<div class="reviews-widget">
  {{ product.metafields["$app"].review_summary | metafield_tag }}
</div>
```

Declare the app-owned definition in `shopify.app.toml`. Use `access.admin = "merchant_read"` if merchants shouldn't edit the value, and `access.storefront = "public_read"` if the Storefront API needs it.

## shopify.app.toml

```toml
[product.metafields.app.review_summary]
name = "Review summary"
type = "rich_text_field"
access.admin = "merchant_read"
```

**Don’t**

* Store HTML, script, or tracking code in a shared namespace, such as `custom` or `reviews`, and render it raw because your app wrote it.
* Treat `shopify--` namespaces or standard definitions as trusted. Merchants and other apps can write their values.

**Do**

* Store values that your app renders in an app-owned namespace, declared as `[<resource>.metafields.app.<key>]` in `shopify.app.toml`, and read them in Liquid through the `$app` prefix, such as `product.metafields["$app"].<key>`.
* Prefer typed fields over HTML strings, such as a `rich_text_field` rendered with `metafield_tag`, or a `url` field with a validated scheme.
* Otherwise, escape or serialize the value with `json` for the context where it's output.

For more detail, see [Metafield ownership](https://shopify.dev/docs/apps/build/metafields#metafield-ownership).

***

## Related

* [`shopify app security check`](https://shopify.dev/docs/api/shopify-cli/app/app-security-check), the command that runs this check
* [All app security checks](https://shopify.dev/docs/apps/build/security/app-security-checks)

***
