CSP won't break PWA installability if configured correctly
CSP is compatible with PWA install prompts when you configure it to allow your service worker and app origins.
Content Security Policy (CSP) is a security layer that prevents cross-site scripting (XSS) and injection attacks by defining which sources of content are safe to load. Some PWA developers worry that CSP headers might blocking service worker registration or install prompts, but CSP doesn't break PWA installability if you configure it correctly.
The key is understanding what CSP controls and what it doesn't. CSP governs what resources can be loaded from where, but it doesn't directly block browser features like PWA installation prompts or service worker lifecycle operations—those are controlled by permissions and app manifest validation.
What CSP actually restricts for PWAs
CSP affects these PWA-relevant operations:
- Script loading — which origins can run JavaScript in your app
- Service worker imports —
importScripts()calls in your service worker code - Worker-src directives — which origins can spawn worker processes
- Frame and connect sources — where your app can fetch data or embed iframes
- Inline scripts and event handlers — whether
onclickoreval()-like code is allowed
CSP does NOT affect:
- PWA install prompt eligibility (that's based on manifest validation and HTTPS)
- Service worker registration itself (you can register a SW file from your origin)
- Service worker lifecycle events (
install,activate,fetch) - Background sync or push notification permissions
- Manifest file loading from your origin
If your PWA fails to show an install prompt, CSP is almost never the culprit—check your manifest, service worker registration timing, and HTTPS setup first.
How CSP and service workers work together
Service workers have a special relationship with CSP:
- Service workers run in a worker context with their own CSP evaluation
- The service worker file itself must load from your origin (or be same-origin)
- Scripts imported via
importScripts()must satisfy CSP'sworker-srcorscript-srcdirectives - The controlled page's CSP applies to page resources, not necessarily the service worker's execution
A common misconception is that CSP blocks service worker registration entirely. In reality, CSP blocks what the service worker can load, not whether it can be registered.
Minimal CSP for PWA installability
Here's a baseline CSP that works with PWAs without breaking installability:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline';
worker-src 'self';
connect-src 'self' https://your-api.com;
frame-src 'self';
img-src 'self' data: https:;Why this works:
'self'allows your app's originworker-src 'self'allows service workers from your originscript-src 'self' 'unsafe-inline'allows inline scripts (many frameworks need this)connect-srcallows API calls where you expect them- No overly restrictive
script-srcorworker-srcthat would block your SW file
You can tighten this over time, but start with something permissive enough that doesn't block known resources.
Common CSP mistakes that break PWAs
These CSP patterns cause PWA or service worker failures:
- Blocking your own origin:
Content-Security-Policy: script-src https://cdn.example.com 'unsafe-inline';Why it fails: Your app's origin isn't in script-src, so your main app scripts fail to load.
Fix: Add 'self' or your specific origin.
- Confusing
script-srcwithworker-src:
Content-Security-Policy: script-src 'self'; worker-src 'none';Why it fails: worker-src: 'none' blocks service worker imports and execution.
Fix: Use worker-src 'self' or omit worker-src (defaults to script-src).
- Blocking
importScripts()in service workers:
Content-Security-Policy: worker-src 'self' https://trusted-libs.com;But the service worker does:
importScripts('https://untrusted-lib.com/sw-lib.js');Why it fails: Import origin isn't in worker-src.
Fix: Add https://untrusted-lib.com to worker-src or move the script to your origin.
- Mixing local development with production CSP:
Running dev server at http://localhost:3000
CSP header: default-src 'self'; // blocks mixed localhost/httpsWhy it fails during dev: localhost:3000 requests get blocked by CSP meant for https://yourapp.com.
Fix: In development, either disable CSP or use permissive http://localhost:* in your dev build.
CSP and service worker debugging checklist
Use this checklist when troubleshooting CSP-related PWA issues:
- [ ] Check your current CSP in DevTools → Application → Headers or Response headers
- [ ] Look for CSP violations in Console → Violations tab
- [ ] Verify service worker file loads successfully in Service Worker tab
- [ ] Check if
importScripts()calls are failing with CSP errors - [ ] Test with CSP disabled to confirm CSP is the issue
- [ ] Check if inline scripts in your app need
unsafe-inline - [ ] Verify
default-srcdoesn't block'self'(your own origin must be allowed) - [ ] Confirm
worker-srcallows your SW origin (usually'self') - [ ] Check for external font/icon CDNs that need
font-srcorimg-srcexemptions - [ ] Test in installed PWA mode—CSP can behave differently in standalone windows
Framework-specific CSP patterns
Different PWA frameworks have CSP requirements:
React (Create React App):
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
connect-src 'self';React often needs unsafe-inline for development-time hot reloading and inline styles.
Vue.js (Vite/CRA):
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
worker-src 'self';Vue templates compile to inline scripts, so unsafe-inline is typically required in dev.
Next.js App Router:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval';
style-src 'self' 'unsafe-inline';
worker-src 'self';Next.js uses WebAssembly and dynamic imports, which require specific CSP directives.
Static site generators (Hugo, Jekyll):
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
worker-src 'self';SQL sites can use the strictest CSP since there's no runtime framework.
CSP migration strategy
If you're adding CSP to an existing PWA:
- Start with
Content-Security-Policy-Report-Onlyto see violations without blocking anything - Monitor violations for 1-2 weeks to understand your app's resource usage
- Add required origins to CSP directives based on violation reports
- Gradually tighten CSP by removing overly broad directives like
unsafe-inline - Test after each change to ensure PWA still functions and installs correctly
- Switch from Report-Only to enforced once violations stabilize
Never switch directly from no CSP to a locked-down policy—you'll break your own app.
CSP and mixed content
PWA installability HTTPS requirements sometimes conflict with legacy CSP patterns:
Problem: CSP headers block http:// resources, but your app loads fonts or images from non-HTTPS URLs.
Result: Browser blocks the resource, potentially breaking app rendering or install prompt.
Fix:
- Migrate allResources to HTTPS
- If must load HTTP resources, request protocol-relative URLs (
//example.com/resource) - Add specific HTTP origins to CSP temporarily, but plan HTTPS migration
- Check DevTools console for mixed content warnings
Note: PWA install prompts require HTTPS origin, so HTTP resources inside a HTTPS PWA are already suspect.
CSP reporting and monitoring
For production PWAs, enable CSP violation reporting:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'unsafe-inline';
worker-src 'self';
report-uri /csp-violations;
report-to csp-endpoint;Set up a Report-To endpoint to collect violations:
// In your service worker or app
self.addEventListener('report', (event) => {
if (event.type === 'cspViolation') {
// Log or send to your analytics
console.log('CSP Violation:', event.violation);
}
});This helps you catch CSP regressions before they break PWA functionality.
Legacy browser fallbacks
Older browsers have incomplete CSP support:
- Old Edge (EdgeHTML): Limited CSP 1 support
- IE 11: No CSP support
- Old Safari: Missing some CSP 3 directives
If your PWA targets older browsers:
- Feature detect CSP before assuming certain directives work
- Use
metaCSP tags for maximum compatibility (incomplete directive syntax may be ignored) - Avoid the newest CSP features (like
script-src-elem,script-src-attr) unless you check browser support - Provide graceful degradation if CSP breaks app functionality
For modern PWAs, you can safely assume CSP 2+ support, but test on your target browsers.
Practical OpenPWA angle
From an installability and security perspective:
- CSP adds security without breaking install prompts when configured correctly
- Security and installability aren't tradeoffs—both are required for user trust
- Users notice security warnings in the browser console, even if they don't read them
- IT security reviews often require CSP for enterprise PWA approval
- App store reviewers for PWAs (where applicable) may check for security headers
A PWA with a well-configured CSP is more likely to pass security reviews, work reliably on enterprise-managed devices, and avoid unexpected breakages from browser security updates.
Next steps
If you're building a new PWA:
- Add CSP during initial development, not as an afterthought
- Use Report-Only mode initially while figuring out required origins
- Document any required CSP exemptions for external services
- Test in installed PWA mode—CSP can behave differently than in browser tabs
If you're adding CSP to an existing PWA:
- Start with Report-Only CSP to collect violations without blocking
- Fix legitimate violations by migrating resources or updating CSP
- Gradually migrate from
unsafe-inlineto stricter script-src policies - Monitor production CSP violations for regressions after deployment
CSP doesn't break PWAs—it makes them more secure when you understand what it controls and how to configure it for your app's needs.