Skip to main content

Render untrusted content as text

Render every value that you didn't write yourself as text, using an API that can't produce markup.


Cross-site scripting (XSS) happens when content that an attacker controls is rendered as markup or code. The content can arrive through any field your app renders: a product title or description, a variant value, a metafield, a cart line item property, a discount code, a search query, an uploaded file name, or any setting that you don't control. A script that runs this way can read sessions, tokens, and the personal and payment information entered on the page, act as a logged-in merchant in the Shopify admin, or change a storefront.

Shopify serves a Content Security Policy (CSP) that limits what a browser loads. It's permissive enough for apps and themes to work, and it doesn't restrict markup that your own code renders into the page, so it's a backstop rather than a control.


Search your code for the sinks that execute untrusted input, and replace them:

// Avoid: these treat the value as markup.
element.innerHTML = untrustedValue;
element.insertAdjacentHTML('beforeend', untrustedValue);
document.write(untrustedValue);
$(element).html(untrustedValue);

// Use: this can produce only text.
element.textContent = untrustedValue;

textContent covers text. Handle code, HTML input, and URLs separately:

  • Never execute as code a string supplied by a store owner, a user, or any other source outside your control. Configuration and customization have better answers: structured settings, a fixed set of options, or a sandboxed extension point.
  • If your product needs to accept HTML, then sanitize it on your server with a well-maintained sanitizer and a strict allowlist of tags and attributes.
  • Validate URLs. Allow only the schemes you need, which is normally just https:, and reject javascript:, unexpected data: URLs, and protocol-relative URLs.

For the Liquid side of this practice, see Escape every value for its context.


Was this page helpful?