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.json is 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 environmentUse
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

  1. Enable edge decision and confirm the plan tier
  2. In console Edge Decision, pick a rule template and the pass / fail actions
  3. Download the official PHP pack, Cloudflare Worker, or Cloudflare Pages (side by side — pick one)
  4. Deploy with the matching steps below
  5. 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.

FieldValuesMeaning
name1–128 charactersScene name
rule_template_idrequiredA visible ad-rule template in the account (country, UA, IP, and so on)
pass_actionallow / html / redirect / pathAction when the rule passes
pass_filerequired for htmlFilename only, e.g. land.html
pass_urlrequired for redirecthttp/https URL, or a site path starting with /
pass_pathrequired for pathSite path starting with /
fail_actionhtml / redirect / path / denyAction when the rule fails
fail_filerequired for html; default safe.htmlFilename only
fail_urlrequired for redirectSame as pass_url
fail_pathrequired for pathSame as pass_path
deny_status403 / 404 / 204 / 410; default 404Used only with deny
append_query0 / 1; default 1For redirect and path, append the current query string
fail_modehtml / allow; default htmlOn transport failure or unreadable JSON: safe page or landing page
statusnormal / disabled; default normaldisabled 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/https with 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/json

Pack timeout is 2500 ms. Body:

FieldRequiredConstraintsHow the script collects it
ipyesvalid IPv4 / IPv6CF-Connecting-IP → X-Real-IP → first X-Forwarded-For → REMOTE_ADDR
uayesnon-empty, max 1024User-Agent
referernomax 2048Referer
languagenomax 256Accept-Language
urlyesmax 4096; must include scheme and hostFull current URL (with query)
scene_idyespositive integer; must match the key’s sceneConstant 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:

actionOther data fieldsScript behavior
allow(none)Serve the landing page. PHP / Cloudflare use pack ALLOW_HTML_FILE (default land.html)
htmlpage: landing (pass) or safe (fail); file: HTML filenameServe that file
redirecturl; append_query (boolean)302 to an http/https URL or a site path
pathpath; append_query (boolean)Open a same-site path; the address bar does not change
denystatus: 403 / 404 / 204 / 410Return 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):

HTTPMeaning
401Missing or invalid key
403Not enabled, scene disabled, scene_id mismatch, or account inactive
400Invalid request fields, or invalid rule template
429Daily quota exhausted
503Platform temporarily unavailable

PHP vs Cloudflare

The same action is aligned; the implementation follows the runtime:

actionPHP (PHP 8.3)Cloudflare
html / allowRead a file next to index.phpNo local disk — same-origin GET that filename
pathinclude under the document root; realpath must stay inside itfetch the same-origin path and return the body
redirect302 on both; append_query on by defaultSame
denyMatching HTTP statusSame

A path must start with /. It cannot contain .., //, http, or backslashes. Redirects cannot use protocol-relative //evil.com.

Console setup

  1. After enablement, open Edge Decision
  2. Configure the scene as above
  3. Download the official PHP pack, Cloudflare Worker, or Cloudflare Pages
  4. 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.

  1. Download the official PHP pack and unzip it
  2. Place it in the site directory (index.php next to the page files)
  3. Apply the pack’s .htaccess or nginx.conf.example so unmatched requests go to index.php. The site root / must hit index.php; an existing index.html in the same directory can serve / as a static homepage and skip the decision
  4. 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.js
  • wrangler.toml.example
  • README.txt
  • placeholder HTML named from the scene

No functions/ or _routes.json. Do not attach Pages middleware to the same route.

  1. Download Cloudflare Worker from the console
  2. Put land.html / safe.html (or the filenames from the scene) where they can be same-origin GET
  3. Follow the README: copy wrangler.toml.example to wrangler.toml and fill in the account and route
  4. 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
  1. Place functions/_middleware.js in the Pages project’s functions/ directory
  2. Place _routes.json at the Pages project root (next to functions). If static output lives in public/ (Astro / Vite), use public/_routes.json so the published root includes it. Pages requires _routes.json
  3. Place land.html / safe.html (or the scene filenames) at the site root, next to functions. Do not put them under the same URL prefix as a nested Function (for example functions/go plus static /go/*.html) — Pages will auto-exclude Functions and the middleware will not run
  4. 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.json with include /*
  5. 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

SymptomCheck
No Edge Decision entryEnabled? Admin account?
Need a new key or suspect a leakRotate, then download and overwrite the deployed script
Always the safe page after uploadRules as intended? Placeholders replaced? Filenames match?
Site root / never runs the decision, always a static homepageRewrites to index.php? Does a same-directory index.html steal the homepage?
Worker / Pages always shows the safe page, or html does not renderCan 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 runsIs _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 renderPHP: file inside the document root, rewrites to index.php? Cloudflare: can the same-origin path be GET?
Landing page while the platform is downFallback set to allow (risky)

Related: Console overview · Rule Templates · FAQ.