Refused to execute inline script because it violates the Content Security Policy

Your page works without a Content-Security-Policy, but with one the console fills with “Refused to execute inline script” and buttons stop working. The policy is doing its job; the inline code just needs a way to prove it's yours.

Read the error

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-tbHiLfFxk+3UKR1aZbQCNmx+rx8ndFa/zkXGLnfe+Ic='), or a nonce ('nonce-...')
is required to enable inline execution.

The directive in quotes is the one that decided, even if your policy only has default-src (it is the fallback). The hash is the SHA-256 of the exact script text the browser saw, so you can copy it straight into the policy if the script never changes. (This one is the hash of startApp().)

Which fix to use

SituationBest fix
Your own small inline scriptMove it to /js/app.js and load it with <script src>; 'self' then covers it
Server-rendered page (Express, Django, Rails, Next.js)A fresh random nonce on every response, on the header and each <script> tag
Static page, script never changesAdd its 'sha256-…' hash
onclick="…" or href="javascript:…"Use addEventListener in a file; nonces never apply to attributes
Third-party snippet that injects more scripts (analytics, tag managers)Nonce on the snippet plus 'strict-dynamic'
Message says “evaluate a string as JavaScript”That's eval, new Function or setTimeout("string"), not an inline script: remove it, or as a last resort add 'unsafe-eval'

Using a nonce

// Express
import crypto from 'node:crypto';
app.use((req, res, next) => {
  res.locals.nonce = crypto.randomBytes(16).toString('base64');
  res.setHeader('Content-Security-Policy',
    `script-src 'nonce-${res.locals.nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'`);
  next();
});
// template
<script nonce="<%= nonce %>">startApp()</script>

The nonce must be new for every response and unguessable. A nonce hard-coded in a config file, or a page served from a cache with the same nonce for everyone, gives no protection. The browser hides the attribute value from scripts on the page, so it can't be read back by injected code.

Using a hash

Hash exactly the text between <script> and </script>, including leading spaces and the final newline, then add it in single quotes: script-src 'self' 'sha256-…='. A minifier or a template that changes indentation changes the hash, which is the usual reason a hash "stops working". The CSP explainer computes the hash in your browser and can test the script against your whole policy.

Inline styles have the same rule

Refused to apply inline style is the style-src version. <style> blocks accept nonces and hashes like scripts. style="…" attributes only work with 'unsafe-inline' (or 'unsafe-hashes' plus a hash), so move them into classes. Many sites accept style-src 'self' 'unsafe-inline' as a trade-off; inline CSS is far less dangerous than inline script.

Frameworks

Roll it out without breaking things

Send the policy as Content-Security-Policy-Report-Only first (see CSP Report-Only vs Enforce). The browser reports every violation in the console (and to report-uri if set) but blocks nothing, so you can fix them before switching to the enforcing header.

Related

Mixed Content blocked · CORS errors explained · CSRF token missing or incorrect · CSP header explainer · Refused to display in a frame · X-Frame-Options explainer · CSP Report-Only vs Enforce · Refused to connect (connect-src) · Trusted Types

Questions

How do I fix 'Refused to execute inline script'?

Move the script into a .js file, or allow it with a per-response nonce or the sha256 hash quoted in the error. Adding 'unsafe-inline' also works but removes most of the protection.

Why is my sha256 hash not working?

The hash must match the script text exactly, including whitespace and line breaks. Minifiers and templates often change it. It also has to be in single quotes inside the directive.

Why doesn't the nonce fix my onclick handler?

Nonces and hashes only apply to script elements. Inline event handlers need 'unsafe-inline' or 'unsafe-hashes', so move them to addEventListener.

Why is unsafe-inline ignored in my policy?

When a directive also has a nonce or hash, browsers ignore 'unsafe-inline'. It's kept only as a fallback for very old browsers.

What does 'strict-dynamic' do?

It trusts scripts loaded by a script that already has a valid nonce or hash, and ignores host allowlists, which makes nonce policies work with loaders and tag managers.

Published .

Try it

Related guides

Newest guides