Jotform is one of those services that looks simple from the outside: paste an embed, publish the page, done.

Then CSP blocks it.

Usually the first breakage shows up as:

  • the form iframe never loads
  • custom scripts from Jotform get refused
  • form submissions fail silently
  • file uploads or payment widgets break
  • the browser console fills with Refused to frame, Refused to connect, or Refused to load script

If you’re embedding Jotform on a site with a real Content Security Policy, you need to explicitly allow the Jotform origins your embed uses. There isn’t a single universal one-liner that covers every Jotform feature, because the exact domains can vary by embed type, region, and enabled integrations.

I’d start narrow, verify in the browser console, and only then widen the policy.

The main CSP directives that matter for Jotform

For most Jotform embeds, these are the directives you’ll touch:

  • frame-src: allow the Jotform iframe
  • script-src: allow Jotform JS if you use their script embed
  • style-src: sometimes needed for embedded styling
  • img-src: logos, icons, uploaded assets
  • connect-src: XHR/fetch/websocket calls from the embed
  • form-action: usually stays on your site unless your own page posts elsewhere
  • frame-ancestors: controls who can embed your page, not Jotform’s page

The most common mistake is mixing up frame-src and frame-ancestors.

  • Use frame-src to allow your page to embed Jotform.
  • Use frame-ancestors to control whether someone else can embed your page.

Minimal CSP for a basic Jotform iframe embed

If you’re using a plain iframe embed, start here:

Content-Security-Policy:
  default-src 'self';
  frame-src 'self' https://form.jotform.com https://www.jotform.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  script-src 'self';
  connect-src 'self';
  base-uri 'self';
  object-src 'none';
  form-action 'self';
  frame-ancestors 'self';

This works when:

  • your page itself does not load Jotform JavaScript
  • Jotform is only rendered inside an iframe
  • you don’t need extra Jotform assets outside the iframe

A matching HTML snippet:

<iframe
  src="https://form.jotform.com/260000000000000"
  width="100%"
  height="600"
  frameborder="0"
  title="Contact form">
</iframe>

If the iframe is blocked, the browser console will usually tell you the exact blocked origin. Add that origin to frame-src.

CSP for Jotform’s script embed

Some teams use Jotform’s JavaScript embed instead of a hardcoded iframe. In that case, script-src matters too.

Example policy:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://form.jotform.com https://www.jotform.com;
  frame-src 'self' https://form.jotform.com https://www.jotform.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  connect-src 'self' https://form.jotform.com https://www.jotform.com;
  base-uri 'self';
  object-src 'none';
  form-action 'self';
  frame-ancestors 'self';

Example embed:

<script src="https://form.jotform.com/jsform/260000000000000"></script>

If you already run a nonce-based CSP, don’t casually throw in 'unsafe-inline' for scripts just to make this work. Keep your script policy strict and add only the required Jotform hosts.

A stricter production-friendly baseline

A lot of production policies already look something like the real-world header below:

content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-N2M4NjhlYzktNTRjYy00N2ZkLTk5NTctNmVlODUzYzRmYWUz' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com;
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com;
frame-src 'self' https://consentcdn.cookiebot.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'

To add Jotform safely, I’d extend only the directives that need it.

Example: add Jotform to an existing strict policy

Content-Security-Policy:
  default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
  script-src 'self' 'nonce-{RANDOM_NONCE}' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com https://form.jotform.com https://www.jotform.com;
  style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com;
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com https://form.jotform.com https://www.jotform.com;
  frame-src 'self' https://consentcdn.cookiebot.com https://form.jotform.com https://www.jotform.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

A couple of practical notes:

  • If you only use an iframe embed, you may not need Jotform in script-src.
  • If Jotform assets come from a different subdomain in your setup, add that exact host instead of broad wildcards.
  • If you use 'strict-dynamic', remember host allowlists can behave differently depending on whether a nonce/hash-trusted script loads more scripts.

If you want a “just make it work” version

I don’t love broad allowlists, but sometimes you need to get a launch unstuck and tighten later.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://*.jotform.com;
  style-src 'self' 'unsafe-inline' https://*.jotform.com;
  img-src 'self' data: https:;
  connect-src 'self' https://*.jotform.com;
  frame-src 'self' https://*.jotform.com;
  base-uri 'self';
  object-src 'none';
  form-action 'self';
  frame-ancestors 'self';

This is broader than I’d keep long term, but it’s useful for confirming whether CSP is the actual problem.

Then tighten it based on observed requests.

Nginx example

add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' https://form.jotform.com https://www.jotform.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  connect-src 'self' https://form.jotform.com https://www.jotform.com;
  frame-src 'self' https://form.jotform.com https://www.jotform.com;
  base-uri 'self';
  object-src 'none';
  form-action 'self';
  frame-ancestors 'self';
" always;

Apache example

Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://form.jotform.com https://www.jotform.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://form.jotform.com https://www.jotform.com; frame-src 'self' https://form.jotform.com https://www.jotform.com; base-uri 'self'; object-src 'none'; form-action 'self'; frame-ancestors 'self';"

Express / Node.js example

app.use((req, res, next) => {
  res.setHeader(
    "Content-Security-Policy",
    [
      "default-src 'self'",
      "script-src 'self' https://form.jotform.com https://www.jotform.com",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "connect-src 'self' https://form.jotform.com https://www.jotform.com",
      "frame-src 'self' https://form.jotform.com https://www.jotform.com",
      "base-uri 'self'",
      "object-src 'none'",
      "form-action 'self'",
      "frame-ancestors 'self'"
    ].join("; ")
  );
  next();
});

Report-Only first, always

If this is a live site, use Content-Security-Policy-Report-Only before enforcement.

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://form.jotform.com https://www.jotform.com;
  frame-src 'self' https://form.jotform.com https://www.jotform.com;
  connect-src 'self' https://form.jotform.com https://www.jotform.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  base-uri 'self';
  object-src 'none';
  form-action 'self';
  frame-ancestors 'self';

That gives you a safe way to catch missing hosts before users do.

Common Jotform CSP breakages

1. The iframe is blocked

Console error points to frame-src.

Fix:

frame-src 'self' https://form.jotform.com https://www.jotform.com;

2. Script embed does nothing

Console error points to script-src.

Fix:

script-src 'self' https://form.jotform.com https://www.jotform.com;

3. Submission or validation fails

Usually connect-src.

Fix:

connect-src 'self' https://form.jotform.com https://www.jotform.com;

4. Images or branding assets are missing

Usually img-src.

Fix:

img-src 'self' data: https:;

That https: allowance is broad, but for third-party embeds it’s often the least annoying option.

If you want one sane default for most Jotform deployments, use this and adjust from console errors:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://form.jotform.com https://www.jotform.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  connect-src 'self' https://form.jotform.com https://www.jotform.com;
  frame-src 'self' https://form.jotform.com https://www.jotform.com;
  base-uri 'self';
  object-src 'none';
  form-action 'self';
  frame-ancestors 'self';

That’s not the most locked-down policy possible, but it’s clean, understandable, and usually enough to ship.

For stricter patterns and ready-to-use policy variants, see the official CSP docs and the policy examples at https://csp-examples.com.