# Cloudflare's Challenge Platform and cf_chl Parameters Explained

> What /cdn-cgi/challenge-platform, window._cf_chl_opt, the cf_chl cookies and the __cf_chl_ URL parameters are, and what an automated client should do.

- Source: https://zerocaptcha.io/blog/cloudflare-challenge-platform-explained
- Published: 2026-10-01
- Author: ZeroCaptcha Engineering

The Cloudflare **challenge platform** is the machinery behind Cloudflare's challenge pages
("Just a moment…") and its JavaScript detections. Its scripts are served from
`/cdn-cgi/challenge-platform/` on the protected site's **own hostname**, a challenge page carries
its settings in a `window._cf_chl_opt` object, the response carries the header
`cf-mitigated: challenge`, and a passed challenge ends with a `cf_clearance` cookie. The `cf_chl_*`
cookies and the `__cf_chl_` URL parameters you may see along the way are internal to Cloudflare
and undocumented; the only piece a client can build on is `cf_clearance`.

This explainer takes each visible piece in turn, with what Cloudflare documents about it, as
checked on 1 October 2026. Automate only sites you are allowed to: see
[responsible captcha automation](https://zerocaptcha.io/guides/responsible-captcha-automation).

## The pieces of a challenge response

When a WAF rule, Bot Management or a rate limiting rule decides to challenge a request, Cloudflare
answers with an interstitial page instead of the site's content. Here is what an HTTP client sees:

| Piece | What it is | Documented? |
| --- | --- | --- |
| Status `403` | Cloudflare lists "WAF Custom or Managed Rules with challenge or block actions" among its causes of a 403 | Yes |
| `cf-mitigated: challenge` header | Set on every challenge page; "challenge" is its only value | Yes |
| `Content-Type: text/html` | "All challenge responses have `text/html` as the content-type, even if you requested a different resource type" | Yes |
| Scripts under `/cdn-cgi/challenge-platform/` | The challenge's code, served from the site's own hostname | Yes, as a path to allow |
| `window._cf_chl_opt` | The challenge page's settings object; Cloudflare sets `cTplC: 1` in it when a custom template is in use | Partly |
| Cookies `cf_chl_rc_i`, `cf_chl_rc_ni`, `cf_chl_rc_m` | "for internal use which allows Cloudflare to identify production issues on clients" | Yes, as internal |
| Query parameters beginning `__cf_chl_` | Added to URLs by the challenge flow | No |
| `cf_clearance` cookie | Set when the visitor passes; proves the visitor passed | Yes |

The first scripts load from the site's own origin, but the check itself also needs
`challenges.cloudflare.com`, the domain the Cloudflare Turnstile widget loads from too. Cloudflare
says that "Challenge Pages are issued through the Cloudflare Challenge Platform, which uses the
same underlying technology as Turnstile." When a blocker or a network filter stops that domain,
the page asks you to unblock it
(see [Please unblock challenges.cloudflare.com](https://zerocaptcha.io/blog/please-unblock-challenges-cloudflare-com)).

## /cdn-cgi/challenge-platform

Cloudflare names the path in two places. Its JavaScript detections are injected as a script
whose path follows the pattern `/cdn-cgi/challenge-platform/…`, only "in response to requests for
HTML pages or page views, excluding AJAX calls", and their result is stored in `cf_clearance` for
15 minutes. And its guidance for custom challenge pages says: "Do not block
`/cdn-cgi/challenge-platform/`" through the page's Content Security Policy.

For a site owner, that means allowing the site's own origin in `script-src`, using nonces for
inline scripts as the JavaScript detections page asks, and keeping Transform Rules that rewrite
response headers off challenge responses (the rule expression is below). A Cloudflare Turnstile
widget needs
`https://challenges.cloudflare.com` in `script-src` and `frame-src` as well, and pre-clearance
mode needs `'self'` in `connect-src` for its calls to `/cdn-cgi/`.

For an automated client, `/cdn-cgi/challenge-platform/` requests in a browser's network log are a
sign that a challenge or JavaScript detections ran on that page view. They are not an API to call:
the scripts' URLs and payloads are undocumented and change.

## window._cf_chl_opt

A challenge page defines a JavaScript object named `_cf_chl_opt` on `window`. Cloudflare mentions
it once, in its custom challenge page requirements: it "sets `cTplC: 1` in `window._cf_chl_opt`
when custom templates are active". Nothing else about its fields is documented. In a browser you
drive, its presence is a quick check that you are looking at a challenge page rather than the site:

```js
// Run in the page, for example with Playwright's page.evaluate().
() => ({
  challengePage: typeof window._cf_chl_opt === "object",
  title: document.title,
});
```

The header check below is better, because it needs no browser and no guess about page internals.

## The __cf_chl_ URL parameters

While a challenge runs, the address bar can show query parameters whose names begin with
`__cf_chl_`. You may find such URLs in logs, in links people copy from their browser, or even in
search results for Cloudflare's own documentation. Cloudflare's documentation, checked on
1 October 2026, does not describe them. What follows is our advice, not Cloudflare's:

- **Strip them before you store or queue a URL.** They make the same page look like many
  different URLs, which fills a crawler's queue with duplicates.
- **Don't treat them as a pass.** The documented proof of a passed challenge is the `cf_clearance`
  cookie. Nothing in the documentation says a URL parameter grants access, so don't build on one.
- **Don't replay them.** They belong to one visitor's challenge.

This Python helper removes them and keeps every other parameter:

```python
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit


def strip_challenge_params(url):
    parts = urlsplit(url)
    query = [
        (name, value)
        for name, value in parse_qsl(parts.query, keep_blank_values=True)
        if not name.startswith("__cf_chl_")
    ]
    return urlunsplit(parts._replace(query=urlencode(query)))


print(strip_challenge_params("https://shop.example.com/item?id=7&__cf_chl_example=abc"))
```

## The cf_chl cookies and cf_clearance

The `cf_chl_rc_i`, `cf_chl_rc_ni` and `cf_chl_rc_m` cookies are, in Cloudflare's words, "for
internal use which allows Cloudflare to identify production issues on clients". Cloudflare sets the
`Partitioned` attribute on them and on `cf_clearance`, and `cf_clearance` also carries
`SameSite=None; Secure`, so "a clearance obtained in one top-level context is not reused in a
different top-level context".

`cf_clearance` is the result. It "proves to Cloudflare that the visitor is a verified human and has
passed Cloudflare's client-side verifications", it lasts for the zone's Challenge Passage, and it is
tied to the visitor and device that earned it: in practice the IP address and user agent. The
[cf_clearance cookie explained](https://zerocaptcha.io/guides/cf-clearance-cookie-explained) covers how to use one, and
[the __cf_bm cookie](https://zerocaptcha.io/blog/cf-bm-cookie-explained) the bot-detection cookie beside it.

## Detect a challenge in code

Cloudflare documents one reliable signal: the `cf-mitigated` header. Its own example is
JavaScript:

```js
const response = await fetch("https://shop.example.com/api/items");
if (response.headers.get("cf-mitigated") === "challenge") {
  console.log("Cloudflare challenged this request; it needs a cf_clearance cookie.");
} else {
  console.log(response.status);
}
```

A site owner can also match challenge responses in rules: Cloudflare's example for keeping
Transform Rules off challenge pages is
`not cf.response.error_type in {"managed_challenge" "iuam" "legacy_challenge" "country_challenge"}`,
which names the kinds of challenge response the field reports (`iuam` is
[Under Attack mode](https://zerocaptcha.io/blog/cloudflare-under-attack-mode)).

## Getting past it, where you are allowed to

A challenge page is passed by a browser that runs its scripts, or by a service that does so for you.
ZeroCaptcha's challenge task passes the page through your own proxy and returns the cf_clearance cookie with the user agent it is bound to, which your client then sends through the same proxy.
See [Cloudflare WAF and 5-second challenges](https://zerocaptcha.io/docs/challenges) and the
[Cloudflare WAF and 5-second challenge solver](https://zerocaptcha.io/cloudflare-challenge-solver). If a page keeps coming back after you
pass it, [the challenge loop article](https://zerocaptcha.io/blog/cloudflare-challenge-loop) lists the causes.

## Sources

- [Cloudflare challenges: challenge pages](https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/)
  (checked 1 October 2026).
- [Cloudflare challenges: detect a challenge response](https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/detect-response/)
  (checked 1 October 2026).
- [Cloudflare challenges: additional configuration](https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/additional-configuration/),
  for `/cdn-cgi/challenge-platform/`, `window._cf_chl_opt` and `cf.response.error_type` (checked
  1 October 2026).
- [Cloudflare challenges: clearance](https://developers.cloudflare.com/cloudflare-challenges/concepts/clearance/)
  (checked 1 October 2026).
- [Cloudflare challenges: challenge solve issues](https://developers.cloudflare.com/cloudflare-challenges/troubleshooting/challenge-solve-issues/)
  (checked 1 October 2026).
- [Cloudflare challenges: JavaScript detections](https://developers.cloudflare.com/cloudflare-challenges/challenge-types/javascript-detections/)
  (checked 1 October 2026).
- [Cloudflare: Cloudflare cookies](https://developers.cloudflare.com/fundamentals/reference/policies-compliances/cloudflare-cookies/)
  and [SameSite cookie interaction with Cloudflare](https://developers.cloudflare.com/waf/troubleshooting/samesite-cookie-interaction/)
  (checked 1 October 2026).
- [Cloudflare: Error 403](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/4xx-client-error/error-403/)
  (checked 1 October 2026).
- [Cloudflare Turnstile: Content Security Policy](https://developers.cloudflare.com/turnstile/reference/content-security-policy/)
  (checked 1 October 2026).

## Questions

### What is /cdn-cgi/challenge-platform?

It is the path on a Cloudflare-proxied site's own hostname from which Cloudflare serves the scripts of its challenge platform: challenge pages and JavaScript detections. A site's Content Security Policy must not block it.

### What are the __cf_chl_ parameters in a URL?

They are query parameters Cloudflare's challenge platform can add to a URL while a challenge runs. Cloudflare's documentation does not describe them, so treat them as internal: strip them from URLs you store, and rely on the cf_clearance cookie, which is the documented proof of a passed challenge.

### How do I detect a Cloudflare challenge page in code?

Check the cf-mitigated response header. Cloudflare sets it to challenge on every challenge page, and the response's content type is text/html whatever you asked for.
