Content Security Policy is one of the most effective browser-side security mechanisms available to web developers. It tells the browser exactly which resources are permitted to load on your pages — which scripts can execute, which stylesheets can be applied, which domains can serve images, fonts, and API requests. Anything not explicitly allowed gets blocked.

For Shopify stores, CSP implementation is simultaneously critical and complicated. Critical because ecommerce sites handle payment data, customer personal information, and session tokens that attackers actively target. Complicated because Shopify’s hosted platform, third-party app ecosystem, and the sheer number of external services a typical store relies on make writing a tight policy genuinely challenging.

This guide covers the technical implementation of CSP on Shopify — from basic meta tag policies on standard plans to full HTTP header control on Shopify Plus. We will walk through every directive, show you how to audit your existing resource loading, handle the app compatibility problem, and deploy a policy that actually provides security without breaking your store. This is part of the broader security approach we implement through our Shopify development services.

What Content Security Policy actually does

CSP is a security standard (W3C recommendation) that allows website operators to declare which dynamic resources are allowed to load and execute on their pages. It works by sending a policy to the browser — either as an HTTP response header or an HTML meta tag — that specifies permitted sources for each type of resource.

When the browser encounters a resource that violates the policy, it blocks that resource and optionally reports the violation to a specified endpoint. This mechanism defends against several classes of attack:

  • Cross-site scripting (XSS) — by preventing execution of injected scripts that were not explicitly whitelisted.
  • Data exfiltration — by controlling which domains can receive data via connect-src and form-action.
  • Clickjacking — by controlling whether your pages can be framed via frame-ancestors.
  • Magecart-style attacks — by blocking unauthorised scripts from skimming payment data on checkout-adjacent pages.
  • Mixed content — by enforcing HTTPS for all resource loading via upgrade-insecure-requests.

CSP does not replace input validation, output encoding, or server-side security controls. It is a defence-in-depth layer that mitigates the impact of vulnerabilities that slip through other defences. Think of it as a seatbelt — you still need to drive carefully, but it significantly reduces the damage when something goes wrong.

Diagram showing how CSP blocks unauthorised scripts from executing in the browser
CSP acts as a whitelist: the browser only loads resources from sources explicitly permitted in the policy, blocking everything else.

Why CSP matters for Shopify stores

Shopify is a hosted platform, which means Shopify handles much of the server-side security — infrastructure hardening, PCI compliance for the checkout, SSL certificates, and platform-level vulnerability patching. This is one of the genuine advantages of Shopify over self-hosted platforms.

However, the storefront — the pages rendered by your Liquid theme — is where your CSP policy applies, and this is also where the most significant client-side attack surface exists. A typical Shopify store loads scripts from a dozen or more external domains:

  • Shopify’s own CDN (cdn.shopify.com, cdn.shopifycloud.com)
  • Analytics providers (Google Analytics, Meta Pixel, TikTok, Pinterest)
  • Third-party apps (reviews, upsells, pop-ups, loyalty programmes)
  • Payment providers (PayPal, Klarna, Clearpay)
  • Chat widgets (Gorgias, Tidio, Zendesk)
  • Marketing tools (Klaviyo, Attentive, Yotpo)
  • Performance monitoring (Hotjar, Microsoft Clarity, Lucky Orange)

Each of these external scripts represents a potential supply chain risk. If any one of these third-party services is compromised, the attacker’s code executes with full access to your customers’ browsing session, form inputs, and potentially payment details on pre-checkout pages.

The performance implications of loading all these scripts are well documented. The security implications receive far less attention but are equally significant. CSP gives you explicit control over which external resources your store loads, providing a measurable reduction in supply chain attack risk.

CSP directives explained

A CSP policy consists of directives, each controlling a specific type of resource. Here are the directives most relevant to Shopify stores:

default-src

The fallback directive. If a specific directive is not set, the browser falls back to default-src. Start restrictive:

default-src 'self';

This tells the browser: unless a more specific directive says otherwise, only load resources from the same origin as the page.

script-src

Controls which scripts can execute. This is the most critical directive for XSS prevention:

script-src 'self' https://cdn.shopify.com https://cdn.shopifycloud.com;

Avoid 'unsafe-inline' if at all possible. It defeats the primary purpose of CSP by allowing any inline script to execute. If you must allow inline scripts (which on Shopify, you almost always must), use nonces or hashes instead.

style-src

Controls stylesheet loading. Many Shopify apps inject inline styles, which means you typically need:

style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;

The 'unsafe-inline' for styles is less dangerous than for scripts because CSS cannot execute arbitrary code (with minor exceptions like CSS expressions in legacy IE, which is no longer relevant).

img-src

Controls image sources. Shopify stores load images from multiple CDN domains:

img-src 'self' data: https://cdn.shopify.com https://cdn.shopifycloud.com https://*.googleusercontent.com;

connect-src

Controls fetch, XMLHttpRequest, WebSocket, and EventSource connections. This is critical for controlling data exfiltration:

connect-src 'self' https://monorail-edge.shopifysvc.com https://a.]klaviyo.com;

font-src

Controls web font loading:

font-src 'self' https://fonts.gstatic.com https://cdn.shopify.com;

frame-src and frame-ancestors

frame-src controls what your page can embed in iframes. frame-ancestors controls who can embed your page (the CSP equivalent of X-Frame-Options). Note: frame-ancestors only works in HTTP headers, not meta tags.

frame-src https://www.youtube.com https://player.vimeo.com https://www.google.com;
frame-ancestors 'self';
Table of CSP directives and their functions in a Shopify context
Each CSP directive controls a specific resource type. The most security-critical for ecommerce are script-src, connect-src, and form-action.

Implementing CSP via meta tags on Shopify

On standard Shopify plans, you cannot set custom HTTP response headers. The only option is a <meta> tag in your theme’s theme.liquid layout file. This is what we use on most Shopify development projects:

<!-- In theme.liquid, inside <head> -->
<meta http-equiv="Content-Security-Policy" content="
  default-src 'self';
  script-src 'self' 'unsafe-inline' https://cdn.shopify.com https://cdn.shopifycloud.com https://monorail-edge.shopifysvc.com;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.shopify.com;
  font-src 'self' https://fonts.gstatic.com https://cdn.shopify.com;
  img-src 'self' data: https://cdn.shopify.com https://cdn.shopifycloud.com;
  connect-src 'self' https://monorail-edge.shopifysvc.com;
  frame-src 'none';
  object-src 'none';
  base-uri 'self';
  form-action 'self' https://checkout.shopify.com;
  upgrade-insecure-requests;
">

This is a reasonable starting point, but you will need to expand it significantly once you account for analytics, marketing pixels, and apps.

Meta tag limitations

The meta tag approach has several limitations you need to be aware of:

  • frame-ancestors is not supported — you cannot prevent clickjacking via meta tags.
  • report-uri and report-to are not supported — you cannot collect violation reports.
  • sandbox is not supported.
  • The meta tag must appear before any resource loads — if Shopify injects scripts before your meta tag, those scripts are not affected by the policy.

That last point is particularly relevant. Shopify injects several of its own scripts into the <head> before your theme content renders. These scripts will execute regardless of your CSP meta tag’s position.

Building your source whitelist

Before deploying any CSP, you need to know exactly which domains your store loads resources from. Open your browser’s DevTools, go to the Network tab, and browse your entire store — homepage, collection pages, product pages, cart, and any custom pages. Record every unique domain.

// Run this in the browser console to list all unique resource origins
const origins = new Set();
performance.getEntriesByType('resource').forEach(r => {
  try {
    const url = new URL(r.name);
    origins.add(url.origin);
  } catch(e) {}
});
console.table([...origins].sort());

You will likely find 15-30 unique origins on a typical Shopify store. Each one needs to be mapped to the correct CSP directive.

Full HTTP headers on Shopify Plus

Shopify Plus merchants have additional options. The most practical approach is deploying a reverse proxy or edge function (using Cloudflare Workers, AWS CloudFront, or Fastly) that intercepts Shopify’s response and injects custom HTTP headers before forwarding to the client.

// Cloudflare Worker example: adding CSP headers to Shopify responses
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const response = await fetch(request);
  const newHeaders = new Headers(response.headers);

  newHeaders.set('Content-Security-Policy',
    "default-src 'self'; " +
    "script-src 'self' 'nonce-{DYNAMIC}' https://cdn.shopify.com https://cdn.shopifycloud.com; " +
    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
    "img-src 'self' data: https://cdn.shopify.com https://cdn.shopifycloud.com; " +
    "connect-src 'self' https://monorail-edge.shopifysvc.com; " +
    "frame-ancestors 'self'; " +
    "report-uri https://your-report-endpoint.example.com/csp-reports;"
  );

  newHeaders.set('X-Content-Type-Options', 'nosniff');
  newHeaders.set('X-Frame-Options', 'SAMEORIGIN');
  newHeaders.set('Referrer-Policy', 'strict-origin-when-cross-origin');

  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: newHeaders
  });
}

This approach gives you full control over all CSP directives, including frame-ancestors and reporting. The trade-off is added infrastructure complexity and a small latency cost from the proxy layer.

Architecture diagram showing Cloudflare Workers injecting CSP headers into Shopify responses
On Shopify Plus, a reverse proxy layer (Cloudflare Workers, AWS CloudFront, etc.) can inject full CSP HTTP headers before the response reaches the browser.

Handling third-party Shopify apps

This is where CSP on Shopify gets genuinely difficult. The average Shopify store runs 6-12 apps, and most of them inject scripts, styles, and make network requests to their own infrastructure. A strict CSP will break most of them.

The approach we recommend through our Shopify development work is methodical:

Step 1: Audit every app

For each installed app, document:

  • Which script domains it loads (check the DOM and Network tab)
  • Whether it uses inline scripts (most do)
  • Which API endpoints it calls (connect-src domains)
  • Whether it loads images, fonts, or styles from external domains
  • Whether it creates iframes (some review widgets, payment options)

We maintain an internal spreadsheet of common Shopify apps and their CSP requirements. Here is a simplified example for a few common services:

// Example: CSP additions for common Shopify integrations

// Klaviyo
// script-src: https://static.klaviyo.com https://a.klaviyo.com
// connect-src: https://a.klaviyo.com https://fast.a]pidev.co
// img-src: https://static.klaviyo.com

// Google Analytics 4 + GTM
// script-src: https://www.googletagmanager.com https://www.google-analytics.com https://googleads.g.doubleclick.net
// connect-src: https://www.google-analytics.com https://analytics.google.com https://region1.google-analytics.com
// img-src: https://www.google-analytics.com https://www.googletagmanager.com

// Meta Pixel
// script-src: https://connect.facebook.net
// connect-src: https://www.facebook.com https://pixel.facebook.com
// img-src: https://www.facebook.com https://pixel.facebook.com

Step 2: Build incrementally

Do not try to write the complete policy in one go. Start with Content-Security-Policy-Report-Only (only available via HTTP headers on Plus), let it run for a week, collect all violations, then build your whitelist from the actual data.

On standard Shopify where you cannot use report-only mode, the alternative is to deploy the CSP in a development theme first, test every page and interaction, and fix violations before pushing to the live theme. The app stack audit process is essential preparation for this.

Nonces, hashes, and inline scripts

The 'unsafe-inline' directive is the weak point in most Shopify CSP implementations. It allows any inline script to execute, which largely defeats the purpose of script-src for XSS prevention. There are two alternatives: nonces and hashes.

Nonces

A nonce is a random, single-use value generated on each request. You add it to both the CSP header and the script tag:

// CSP header
Content-Security-Policy: script-src 'nonce-abc123def456';

// HTML
<script nonce="abc123def456">
  // This script will execute
</script>

<script>
  // This script will be blocked (no matching nonce)
</script>

On standard Shopify, nonces are impractical because Liquid templates are cached and you cannot generate a unique nonce per request without server-side control. On Hydrogen (headless) builds, nonces are straightforward because you control the server.

Hashes

Script hashes are more practical on Shopify. You compute the SHA-256 (or SHA-384/SHA-512) hash of the inline script content and include it in the CSP:

// If your inline script is exactly:
// console.log('hello');

// The SHA-256 hash is:
// 'sha256-TWupyvVdPa1DyFqLnQMqRp0wrNRb7/VgFNiEkaWSdU8='

// Your CSP becomes:
script-src 'sha256-TWupyvVdPa1DyFqLnQMqRp0wrNRb7/VgFNiEkaWSdU8=' https://cdn.shopify.com;

This works well for static inline scripts in your theme. The hash only matches if the script content is identical byte-for-byte, so an attacker cannot inject different code. However, any change to the inline script (even adding a space) invalidates the hash.

Generating hashes for your theme scripts

# Generate a CSP hash for an inline script
echo -n "your inline script content here" | openssl dgst -sha256 -binary | openssl base64

# Or use Node.js
node -e "
  const crypto = require('crypto');
  const script = 'your inline script content here';
  const hash = crypto.createHash('sha256').update(script).digest('base64');
  console.log(\`'sha256-\${hash}'\`);
"

For Shopify themes with multiple inline scripts (analytics snippets, app embed blocks, etc.), you need to compute and maintain hashes for each one. This becomes a maintenance burden but provides genuinely stronger security than 'unsafe-inline'.

Comparison of unsafe-inline, nonce-based, and hash-based CSP approaches
Nonces and hashes provide significantly better security than unsafe-inline, but each has practical trade-offs on the Shopify platform.

CSP violation reporting

CSP reporting tells you when the browser blocks a resource, which is invaluable for both security monitoring and debugging broken functionality. There are two reporting mechanisms:

report-uri (deprecated but widely supported)

Content-Security-Policy: default-src 'self'; report-uri https://your-endpoint.example.com/csp;

report-to (modern replacement)

Report-To: {"group":"csp","max_age":86400,"endpoints":[{"url":"https://your-endpoint.example.com/csp"}]}
Content-Security-Policy: default-src 'self'; report-to csp;

Both require HTTP headers, so reporting is only available on Shopify Plus with a proxy layer. For standard Shopify, you lose reporting capability entirely with the meta tag approach — another reason to consider the Plus upgrade for security-sensitive stores.

Reporting services

Processing raw CSP reports is noisy. A single page load can generate dozens of reports from browser extensions, VPNs, and antivirus software injecting their own scripts. Dedicated services like Report URI, Sentry, or CSP Wizard aggregate, deduplicate, and filter reports so you can focus on genuine violations.

The typical reporting workflow:

  1. Deploy CSP in report-only mode with a reporting endpoint.
  2. Let it run for 1-2 weeks across all traffic.
  3. Review reports, distinguishing genuine policy gaps from browser extension noise.
  4. Update the policy to allow legitimate resources.
  5. Switch from report-only to enforcing mode.
  6. Keep reporting enabled to catch new violations from app updates or new integrations.

Testing and rollout strategy

Deploying CSP on a live Shopify store without proper testing is a recipe for breaking your site. Here is the rollout strategy we follow:

Phase 1: Audit (1-2 days)

Catalogue every external resource your store loads. Use the browser console script shown earlier, plus manual inspection of your theme code and app embed blocks. Document the complete list of domains per directive.

Phase 2: Development theme testing (3-5 days)

Create a duplicate of your live theme. Add the CSP meta tag. Test every page type: homepage, collection, product, cart, search, blog, custom pages, and any special landing pages. Test every interactive feature: add to cart, quick view, reviews, chat widgets, pop-ups, newsletter forms.

<!-- Start with a permissive policy and tighten iteratively -->
<meta http-equiv="Content-Security-Policy" content="
  default-src 'self' https: data:;
  script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;
  style-src 'self' 'unsafe-inline' https:;
  img-src 'self' data: https:;
  connect-src 'self' https:;
  font-src 'self' https: data:;
  frame-src https:;
  object-src 'none';
  base-uri 'self';
">

This permissive starting point allows everything over HTTPS. Gradually restrict each directive to specific domains, testing after each change. When something breaks, check the browser console for CSP violation messages — they tell you exactly which resource was blocked and which directive caused it.

Phase 3: Staging deployment (1 week)

If you have a staging environment or can use Shopify’s theme preview, deploy the policy and have your team browse the store normally for a week. This catches edge cases that targeted testing misses.

Phase 4: Production deployment

Deploy to the live theme. Monitor closely for the first 48 hours. Have a rollback plan ready (removing or loosening the CSP meta tag).

CSP rollout timeline showing audit, development testing, staging, and production phases
A structured rollout prevents the most common CSP deployment mistake: breaking your live store by deploying an untested policy.

Ongoing CSP maintenance

A CSP is not a set-and-forget configuration. It needs to be updated whenever you:

  • Install or remove a Shopify app
  • Add a new analytics or marketing pixel
  • Embed content from a new domain (videos, social feeds, maps)
  • Update an existing app (which may load resources from new domains)
  • Change your theme or add new custom sections

Build CSP checks into your change management process. Whenever a new app is installed, the CSP audit should be part of the implementation checklist — the same way you would check performance impact and security implications.

Common maintenance mistakes

We see these regularly in Shopify development audits:

  • Wildcard domains — using https://*.google.com instead of specific subdomains. Wildcards are convenient but vastly expand the attack surface.
  • Stale whitelists — domains from removed apps still in the policy. This does not cause breakage but unnecessarily widens the policy.
  • unsafe-eval — added to fix a single app and then forgotten. 'unsafe-eval' allows eval() and Function() constructors, significantly weakening script-src protection.
  • No versioning — CSP changes not tracked in version control. When something breaks, there is no way to identify which policy change caused it.

CSP in your Liquid theme version control

Store your CSP as a Liquid snippet for maintainability:

<!-- snippets/csp-meta.liquid -->
{%- comment -%}
  Content Security Policy
  Last updated: 2026-03-16
  Updated by: Andrew Simpson
  Reason: Added Klaviyo domains for email signup widget
{%- endcomment -%}
<meta http-equiv="Content-Security-Policy" content="
  default-src 'self';
  script-src 'self' 'unsafe-inline'
    https://cdn.shopify.com
    https://cdn.shopifycloud.com
    https://monorail-edge.shopifysvc.com
    https://static.klaviyo.com
    https://www.googletagmanager.com;
  style-src 'self' 'unsafe-inline'
    https://fonts.googleapis.com
    https://cdn.shopify.com;
  font-src 'self'
    https://fonts.gstatic.com
    https://cdn.shopify.com;
  img-src 'self' data:
    https://cdn.shopify.com
    https://cdn.shopifycloud.com;
  connect-src 'self'
    https://monorail-edge.shopifysvc.com
    https://a.klaviyo.com;
  frame-src 'none';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;
">

Then in theme.liquid:

<head>
  {% render 'csp-meta' %}
  <!-- rest of head -->
</head>

This keeps the CSP in a dedicated, version-controlled file with change documentation built in.


Content Security Policy on Shopify is a trade-off between security and practicality. A perfect CSP (no unsafe-inline, no wildcards, every domain explicitly listed) is achievable but requires significant investment in auditing, testing, and ongoing maintenance. A reasonable CSP (explicit domain whitelisting with unsafe-inline for scripts) provides meaningful protection against supply chain attacks and data exfiltration with manageable maintenance overhead.

Start where you are. Even a basic CSP that blocks object-src, sets base-uri, and whitelists known script domains is significantly better than no CSP at all. Tighten iteratively as you learn what your store actually needs.

If you need help implementing CSP on your Shopify store or auditing your current security posture, get in touch. Security is a standard part of our Shopify development process.