JSON JSONP

Understand JSONP, the workaround browsers used before CORS existed for making cross-origin JSON-like requests, and why you should reach for CORS instead today.

The Cross-Origin Problem JSONP Solved

Browsers enforce a same-origin policy that blocks JavaScript from reading responses from a different domain, port, or protocol using XMLHttpRequest. Before Cross-Origin Resource Sharing (CORS) became widely supported around 2010, there was no standard, safe way to fetch data from another domain's API. Developers noticed one loophole: <script> tags were exempt from the same-origin policy, because loading a script from any domain (like a CDN) was already common practice. JSONP was built on top of that loophole.

How JSONP Actually Works

A JSONP server doesn't return plain JSON — it returns JavaScript source code: a JSON value wrapped in a call to a function name that the client supplied through a query string parameter, conventionally called callback. The client defines a global function with that exact name ahead of time, then injects a <script> tag whose src points at the JSONP endpoint. When the browser loads and executes that script, it calls the pre-defined function, passing the data as a normal argument.

Consuming a JSONP endpoint

<!DOCTYPE html>
<html>
<body>

<h2>Result</h2>
<p id="demo"></p>

<script>
function handleData(data) {
  console.log('Received via JSONP:', data);
}

const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleData';
document.body.appendChild(script);

// The server's response body is literally JavaScript text, like:
// handleData({"id":1,"name":"Sample"});
</script>

</body>
</html>
  • The client defines a global function that will receive the data once it arrives.
  • The client builds a URL with a callback query parameter set to that function's name.
  • The client creates a <script> element with that URL as its src and appends it to the page.
  • The browser requests the URL exactly like it would for any other script — the same-origin policy does not apply.
  • The server wraps the JSON payload in a call to the requested function name and returns it as JavaScript.
  • The browser executes the returned script, which invokes the client's function with the data as an argument.

Why JSONP Is Risky and Mostly Obsolete

JSONP has serious downsides that CORS avoids entirely. It only supports GET requests, since the payload rides along in a URL and a script tag has no concept of a request body. It offers no clean way to detect failures — a script tag that fails to load just fails silently, with no status code to inspect. Most importantly, a JSONP response isn't data, it's executable code: the remote server can put anything it wants inside that function call, including code that has nothing to do with your callback. CORS solves the exact same cross-origin problem while keeping requests as actual data-only responses that the browser parses, not executes.

The modern CORS equivalent with fetch

<!DOCTYPE html>
<html>
<body>

<h2>Result</h2>
<p id="demo"></p>

<script>
async function loadData() {
  const response = await fetch('https://api.example.com/data');
  // No callback param needed — the server sends an
  // Access-Control-Allow-Origin header, and fetch handles the rest.
  const data = await response.json();
  console.log('Received via fetch:', data);
}

loadData();
</script>

</body>
</html>

A legacy PHP JSONP endpoint

<!DOCTYPE html>
<html>
<body>

<h2>Result</h2>
<p id="demo"></p>

<script>
// The following PHP code runs on the server, not in the browser.
// It is shown here for reference and does not execute in this client-side sandbox.
/*
<?php
$data = ['id' => 1, 'name' => 'Sample'];
$json = json_encode($data);

$callback = $_GET['callback'] ?? '';

// Only allow safe function-name characters to avoid injecting code
if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $callback)) {
  header('Content-Type: application/javascript');
  echo "$callback($json);";
} else {
  header('Content-Type: application/json');
  echo $json;
}
*/

document.getElementById("demo").innerHTML = "This example shows a legacy server-side PHP JSONP endpoint (see the comment above). PHP runs on the server and cannot execute in the browser.";
</script>

</body>
</html>
AspectJSONPCORS
TransportScript tag injectionStandard HTTP via fetch/XHR
HTTP methodsGET onlyGET, POST, PUT, DELETE, etc.
Error handlingSilent failure, no status codesFull HTTP status codes and network errors
Security modelExecutes arbitrary remote codeParses data only, server opts in per-origin
Browser support neededNone special — works everywhereRequires CORS headers from the server
Note: Loading a JSONP script means giving that remote server full permission to run arbitrary JavaScript inside your page, with access to cookies, the DOM, and anything else your page can touch. Only ever point a JSONP callback at a server you fully trust, and never use it for anything involving authenticated or sensitive data.
Note: If you spot a callback= query parameter or a script tag pointed at an API endpoint in an old codebase, that's JSONP. It's usually a sign the integration predates that API's CORS support — worth checking whether the provider now offers a plain CORS-enabled JSON endpoint you can swap in with fetch().

Exercise: JSON JSONP

What problem was JSONP originally created to work around?