Fix "Refused to Execute Inline Script" CSP Error
You shipped a page, opened the console, and saw a red wall of text like this:
Refused to execute inline script because it violates the following
Content Security Policy directive: "script-src 'self'". Either the
'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...')
is required to enable inline execution.
This is not a bug in your code. It is your Content Security Policy doing exactly what it was configured to do: blocking any inline <script> (or inline <style>, which produces the near-identical "Refused to apply inline style" message) that does not carry an explicit, pre-approved credential. The browser will not run that block until you prove the script is trusted. Below are the fixes, ordered from most secure to least, plus the trap most tutorials skip.
Why the script is blocked
CSP's script-src directive (and style-src for styles) defaults to refusing inline code entirely. The reason is XSS: if an attacker can inject markup into your page, an open inline policy means their <script> runs with full trust. So the browser demands one of three proofs that you authored this specific block: a nonce, a hash, or the blanket 'unsafe-inline' keyword. With none present, every inline script and every inline onclick is refused. The fix is to supply the right proof, and the choice of proof is a security decision, not a formality.
Fix 1 (best): move the code to an external file
The cleanest fix is to stop having inline scripts at all. Cut the contents of the <script> into a real file and load it:
<!-- before -->
<script>init(); track('pageview');</script>
<!-- after -->
<script src="/js/app.js"></script>
Now your policy can stay script-src 'self' with no per-block exceptions. External files are cacheable, easier to audit, and play well with strict CSP. For third-party scripts you load this way, pair the tag with a Subresource Integrity (integrity="sha384-...") attribute so a compromised CDN cannot swap the file. If you can externalize everything, do it and skip the rest.
Fix 2: add a nonce (best for server-rendered pages)
When you must keep an inline block (common for bootstrap config or framework hydration), a nonce is the recommended approach. A nonce is a random, single-use token your server generates fresh on every response, places in the header, and echoes onto each trusted tag:
Content-Security-Policy: script-src 'nonce-r4nd0mBase64Value'
<script nonce="r4nd0mBase64Value">init();</script>
Two rules are non-negotiable. The nonce must be unpredictable (use a cryptographically secure random generator, not a counter or timestamp) and it must be regenerated per request — a static nonce baked into a cached HTML file is no better than 'unsafe-inline', because an attacker can just read it and reuse it. Nonces shine on dynamically rendered pages where the server controls both the header and the HTML. They are also the foundation of a strict CSP using 'strict-dynamic', which lets a nonced script load further scripts without you enumerating every URL.
Fix 3: add a hash (best for static, unchanging blocks)
If the page is fully static and you cannot run server code to inject a nonce, use a hash. CSP lets you allowlist an inline block by the SHA digest of its exact contents. The browser does the work for you: the console error usually prints the precise value it computed, for example sha256-abc123...=. Copy that token straight into your policy:
Content-Security-Policy: script-src 'self' 'sha256-abc123...='
If the console did not print it, hash the script body yourself. The digest is taken over the exact bytes between the opening and closing tags — not the tags themselves, and not including surrounding whitespace you did not intend. A single changed character, even a stray space or a different line ending, changes the hash and re-breaks the page, so hashes are brittle for code that changes often. Generate the SHA-256 digest with the hash generator, then base64-encode it and prefix with sha256- (or use sha384/sha512, which CSP also accepts).
The trap: 'unsafe-hashes', event handlers, and style attributes
A plain hash covers a full <script> element, but it does not cover inline event-handler attributes like onclick="doThing()" or inline style="..." attributes. Those produce their own refusals, and a normal hash will not unblock them. To allow them by hash you need the separate 'unsafe-hashes' keyword, hashing only the attribute's value (for onclick="doThing()" that is doThing()):
script-src 'self' 'unsafe-hashes' 'sha256-hashOfTheHandlerBody'
The word "unsafe" is a warning, not decoration: 'unsafe-hashes' weakens your policy because the same handler text is allowed wherever it appears in the DOM, including in injected markup. The better move is to remove inline handlers entirely — attach them with addEventListener in an external file, and move style attributes into a stylesheet or a nonced/hashed style block. Treat 'unsafe-hashes' as a last resort for legacy code you cannot refactor today.
Why 'unsafe-inline' is the wrong escape hatch
It is tempting to add 'unsafe-inline' and move on. Resist it. That keyword disables the entire inline protection, re-opening the exact XSS hole CSP exists to close. Worse, it is mostly redundant: under CSP Level 2 and later, when a nonce or hash source is present in script-src, browsers that support those features ignore 'unsafe-inline'. So you can keep 'unsafe-inline' in the list purely as a fallback for ancient browsers while modern ones enforce the strict nonce/hash path. Shipping 'unsafe-inline' as your only inline allowance means you have a policy that compiles but protects nothing.
Assemble and verify the policy
Once you know which proof you need, build the full header rather than hand-editing one directive at a time — it is easy to break default-src or forget style-src falls back to it. Use the CSP generator to compose a complete, valid policy with your nonce or hash sources in place, then confirm the response header is actually being sent (a common cause of "my fix did nothing" is the header being set in an HTML <meta> tag, where some directives, like reporting and frame-ancestors, are ignored). Check the live header with the HTTP header inspector. For the deeper background on directives, report-only mode, and how the pieces fit together, see our Content Security Policy guide. Reload, watch the console, and the refusal should be gone.
Frequently Asked Questions
Your Content Security Policy blocked an inline script tag because that script carried no approved credential. The script-src directive does not include 'unsafe-inline', and the block has no matching nonce or hash. The browser refuses to run it until you prove the script is trusted by adding one of those three things.
Use a nonce when your server renders the page dynamically and can generate a fresh random token per response. Use a hash when the page and the inline block are completely static and never change. Hashes are brittle because any byte change breaks them; nonces require server-side generation. Externalizing the script is better than both.
Chrome and other Chromium browsers usually print the exact required value in the console error itself, formatted as sha256-... — copy it directly into your script-src. If it is not shown, compute the SHA-256 digest over the exact bytes between the script tags, base64-encode it, and prefix it with sha256-.
It usually does allow inline code, but it is the wrong fix. 'unsafe-inline' disables CSP's main XSS protection. Under CSP Level 2 and later, if a nonce or hash is also present in script-src, modern browsers ignore 'unsafe-inline' entirely, so it only serves as a fallback for very old browsers, not as a real solution.
A normal hash covers full script elements, not inline event-handler attributes like onclick or inline style attributes. To allow those by hash you must add the 'unsafe-hashes' keyword and hash the attribute's value, which weakens the policy. The safer fix is to remove inline handlers and bind events with addEventListener in an external file instead.