Edge decision & official scripts
Keep traffic on your own site. Connect platform decisions with the official PHP pack, Cloudflare Worker, or Cloudflare Pages: allow, serve a page, open a same-site path, redirect, or return a status code.
When visitors hit your own site, an official script asks the platform for a result, then allows, serves a page, opens a same-site path, issues a 302, or returns a status code. You do not need to move the domain or the site onto our servers.
Three official packs, aligned in behavior — pick the one that matches the site:
- PHP pack (
{name}-php.zip): traditional hosting; place it in the site directory when the host runs PHP 8.3. Always one zip; Apache / Nginx rewrite samples live in the same pack - Cloudflare Worker (
{name}-cf-worker.zip): origin already on Cloudflare, bind a Worker route - Cloudflare Pages (
{name}-cf-pages.zip): the site is on Cloudflare Pages._routes.jsonis required
Worker and Pages share the Workers runtime but are mounted differently. The console downloads two separate zips. Do not attach Worker and Pages middleware to the same route.
When to use it
- You want the domain and site to stay on your side
- You want the official script instead of writing your own integration
Hosted delivery (pointing the domain at the platform) still works.
Which script
| Site environment | Use |
|---|---|
| Your own server or hosting with PHP 8.3 | {name}-php.zip |
| The site is already on Cloudflare (origin + route) | {name}-cf-worker.zip |
| The site is on Cloudflare Pages | {name}-cf-pages.zip |
All three call the same decision API (POST /v1/decision, Bearer). The script does not probe the PHP version at runtime.
How to use
- Enable edge decision and confirm the plan tier
- In console Edge Decision, pick a rule template and the pass / fail actions
- Download the official PHP pack, Cloudflare Worker, or Cloudflare Pages (side by side — pick one)
- Deploy with the matching steps below
- When visitors open the site, the script applies the result
Scene settings
Each scene in Edge Decision has one API key, one rule template, and a set of actions.
| Field | Values | Meaning |
|---|---|---|
name | 1–128 characters | Scene name |
rule_template_id | required | A visible ad-rule template in the account (country, UA, IP, and so on) |
pass_action | allow / html / redirect / path | Action when the rule passes |
pass_file | required for html | Filename only, e.g. land.html |
pass_url | required for redirect | http/https URL, or a site path starting with / |
pass_path | required for path | Site path starting with / |
fail_action | html / redirect / path / deny | Action when the rule fails |
fail_file | required for html; default safe.html | Filename only |
fail_url | required for redirect | Same as pass_url |
fail_path | required for path | Same as pass_path |
deny_status | 403 / 404 / 204 / 410; default 404 | Used only with deny |
append_query | 0 / 1; default 1 | For redirect and path, append the current query string |
fail_mode | html / allow; default html | On transport failure or unreadable JSON: safe page or landing page |
status | normal / disabled; default normal | disabled makes the decision API return 403 |
Filename, URL, and path rules:
- HTML filename:
^[A-Za-z0-9._-]+\.html$, max 128; no..,/, or\ - Redirect target: max 1024.
http/httpswith a host, or a site path starting with/; no//evil.com,.., or backslashes - Site path: max 1024, must start with
/; no..,//,http, backslashes, or whitespace. A query string after?is allowed
Pass / fail actions take effect as soon as you save. The key, scene ID, fail_mode, and default HTML filenames are constants in the script — change those, then download again and overwrite.
After create or rotate you can download the PHP pack, Cloudflare Worker, and Cloudflare Pages anytime (Peek into the ZIP; same key, as often as needed). The live key changes only on rotate. After rotate or deleting the scene, the old key stops working immediately — re-download and overwrite the deployed script.
Decision request
The official script posts:
POST /v1/decision
Authorization: Bearer <scene key>
Content-Type: application/json
Accept: application/jsonPack timeout is 2500 ms. Body:
| Field | Required | Constraints | How the script collects it |
|---|---|---|---|
ip | yes | valid IPv4 / IPv6 | CF-Connecting-IP → X-Real-IP → first X-Forwarded-For → REMOTE_ADDR |
ua | yes | non-empty, max 1024 | User-Agent |
referer | no | max 2048 | Referer |
language | no | max 256 | Accept-Language |
url | yes | max 4096; must include scheme and host | Full current URL (with query) |
scene_id | yes | positive integer; must match the key’s scene | Constant written at download time |
{
"ip": "203.0.113.10",
"ua": "Mozilla/5.0 ...",
"referer": "https://example.com/",
"language": "en-US,en;q=0.9",
"url": "https://yoursite.com/",
"scene_id": 12
}A mismatched scene_id or a disabled scene returns 403. Geo, ASN, and proxy signals are computed on the platform from ip and related fields. The script does not call a third-party IP database on your side.
Decision response
On success, HTTP 200:
{
"code": 200,
"msg": "SUCCESS",
"data": { "action": "allow" }
}data fields depend on action:
action | Other data fields | Script behavior |
|---|---|---|
allow | (none) | Serve the landing page. PHP / Cloudflare use pack ALLOW_HTML_FILE (default land.html) |
html | page: landing (pass) or safe (fail); file: HTML filename | Serve that file |
redirect | url; append_query (boolean) | 302 to an http/https URL or a site path |
path | path; append_query (boolean) | Open a same-site path; the address bar does not change |
deny | status: 403 / 404 / 204 / 410 | Return only that HTTP status; no body |
If code is not 200, the request fails, or JSON cannot be parsed, the scene’s fail_mode runs (html → safe page, allow → landing page). Script responses send Cache-Control: no-store.
Typical public-API failures (the official script always uses fallback; visitors never see these codes):
| HTTP | Meaning |
|---|---|
| 401 | Missing or invalid key |
| 403 | Not enabled, scene disabled, scene_id mismatch, or account inactive |
| 400 | Invalid request fields, or invalid rule template |
| 429 | Daily quota exhausted |
| 503 | Platform temporarily unavailable |
PHP vs Cloudflare
The same action is aligned; the implementation follows the runtime:
action | PHP (PHP 8.3) | Cloudflare |
|---|---|---|
html / allow | Read a file next to index.php | No local disk — same-origin GET that filename |
path | include under the document root; realpath must stay inside it | fetch the same-origin path and return the body |
redirect | 302 on both; append_query on by default | Same |
deny | Matching HTTP status | Same |
A path must start with /. It cannot contain .., //, http, or backslashes. Redirects cannot use protocol-relative //evil.com.
Console setup
- After enablement, open Edge Decision
- Configure the scene as above
- Download the official PHP pack, Cloudflare Worker, or Cloudflare Pages
- Open Decision logs for successes and failures. Filter by scene / passed / action / IP
The page header shows today’s used / remaining. Daily decisions follow your plan.
PHP pack
The site runs PHP 8.3 with outbound HTTPS to the platform. The pack does not check the PHP version at runtime. The official PHP pack is always one zip ({name}-php.zip). Place the files in the site directory; unmatched requests go to index.php.
- Download the official PHP pack and unzip it
- Place it in the site directory (
index.phpnext to the page files) - Apply the pack’s
.htaccessornginx.conf.exampleso unmatched requests go toindex.php. The site root/must hitindex.php; an existingindex.htmlin the same directory can serve/as a static homepage and skip the decision - Replace placeholders with real HTML; filenames must match the console
What’s in the pack: index.php (https endpoint, key, and scene constants already filled in), placeholder HTML named from the scene, README.txt, .htaccess, and nginx.conf.example. Requests that hit a real file (including static assets with an extension) skip the decision. Landing pages should be served by the script — do not rely on opening /land.html directly to run the rules.
Cloudflare Worker
When the origin is already on Cloudflare and you bind a Worker route, download {name}-cf-worker.zip. Same runtime and visitor collection as Pages: CF-Connecting-IP first, then UA, Referer, Accept-Language, and the full URL, posted to the decision API with Bearer. No Node fs. html / path same-origin GET the file. Requests with a file extension pass through without a decision.
What’s in the pack:
worker.jswrangler.toml.exampleREADME.txt- placeholder HTML named from the scene
No functions/ or _routes.json. Do not attach Pages middleware to the same route.
- Download Cloudflare Worker from the console
- Put
land.html/safe.html(or the filenames from the scene) where they can be same-origin GET - Follow the README: copy
wrangler.toml.exampletowrangler.tomland fill in the account and route - Deploy with wrangler and bind the Worker to the site route
For html, the Worker fetches the file from the same origin (fetch(new URL('/' + file, request.url))). Those files must be reachable on the origin.
Cloudflare Pages
When the site is on Cloudflare Pages, download {name}-cf-pages.zip. Use functions/_middleware.js (Pages Functions, same runtime as Workers). The official layout is site-wide middleware at the site root, not a nested path such as /go. Do not attach a Worker to the same route.
What’s in the pack:
functions/_middleware.js_routes.json(required for Pages)README.txt- placeholder HTML named from the scene
- Place
functions/_middleware.jsin the Pages project’sfunctions/directory - Place
_routes.jsonat the Pages project root (next tofunctions). If static output lives inpublic/(Astro / Vite), usepublic/_routes.jsonso the published root includes it. Pages requires_routes.json - Place
land.html/safe.html(or the scene filenames) at the site root, next tofunctions. Do not put them under the same URL prefix as a nested Function (for examplefunctions/goplus static/go/*.html) — Pages will auto-exclude Functions and the middleware will not run - If the project already has
functions/_middleware.js(locale redirects, geo blocks), do not overwrite the whole file with the pack. Merge the decision logic, or mount the pack on a separate path, and keep_routes.jsonwithinclude /* - Deploy with Pages as usual
Pages has no local disk. For html / path, same-origin GET: prefer Pages ASSETS; otherwise same-origin fetch with an internal header so the request does not re-enter the decision.
Usage
Only a successful public decision counts toward the day. Quota is counted on the platform. Exhausted quota or a temporary platform outage uses fallback.
Common issues
| Symptom | Check |
|---|---|
| No Edge Decision entry | Enabled? Admin account? |
| Need a new key or suspect a leak | Rotate, then download and overwrite the deployed script |
| Always the safe page after upload | Rules as intended? Placeholders replaced? Filenames match? |
Site root / never runs the decision, always a static homepage | Rewrites to index.php? Does a same-directory index.html steal the homepage? |
Worker / Pages always shows the safe page, or html does not render | Can the same-origin file be GET? Is the route hitting the script? On Pages, is the HTML at the site root, and is _routes.json published? |
| Pages middleware never runs | Is _routes.json (include /*) in the published root? Did landing HTML share a Function URL prefix and get auto-excluded? Did an existing root _middleware overwrite the whole pack file? |
path does not render | PHP: file inside the document root, rewrites to index.php? Cloudflare: can the same-origin path be GET? |
| Landing page while the platform is down | Fallback set to allow (risky) |
Related: Console overview · Rule Templates · FAQ.