DNAbyD3Docs
Middleware

Cloudflare

One line in your existing Worker, or a thin worker in front of your origin.

DNA runs in your Cloudflare account, in front of your site. It checks each request before your site sees it, and lets it through or returns a 403.

  • Your pages go straight from Cloudflare to your site, never through D3.
  • Cloudflare's WAF, Bot Management, and DDoS protection run first. DNA sees what they let through and doesn't change how they work.

Choose an install

Where your site runsInstallWhat it takes
On a Worker you deployWrap itOne line of code
Anywhere else: nginx, a VPS, another cloudThe deploy buttonA few clicks, no code
On a Worker or Pages project you can't editA separate workerA small worker + service binding
Behind Cloudflare, and you'd rather not deploy anythingPaste a SnippetOne pasted file (paid plans)

All installs need an API key.

Wrap your Worker

  1. Install @d3-inc/d3-edge-cloudflare-adapter:

    npm install @d3-inc/d3-edge-cloudflare-adapter
  2. Wrap your handler:

    // src/index.ts: the only code change
    import { withD3Edge } from '@d3-inc/d3-edge-cloudflare-adapter';
    
    export default withD3Edge({
      async fetch(request, env, ctx) {
        // your existing worker, unchanged
      },
    });
  3. Set your API key as a secret and redeploy. No route changes or ORIGIN binding are needed; your handler is the origin.

    npx wrangler secret put DNA_API_KEY
    npx wrangler deploy

Behavior:

  • Blocked requests get a 403 before your fetch runs. Other requests reach it unchanged.
  • Non-fetch handlers (scheduled, queue, …) are untouched.
  • With DNA_API_KEY unset, withD3Edge is a transparent no-op. See Configuration reference.

Production only

Cloudflare also serves your Worker on *.workers.dev and on per-version preview URLs, and each one shows in Sites as its own site. Turn them off so DNA runs on your production domain alone, which must be a route or custom domain:

workers_dev = false
preview_urls = false

For a staging Worker, add an environment. Wrangler doesn't copy [vars] into one, so staging runs without DNA and answers with x-d3-edge: disabled:

[env.staging]
routes = [{ pattern = "staging.example.com", custom_domain = true }]
npx wrangler deploy --env staging

The deploy button

For sites hosted outside Cloudflare. The button deploys a small worker in front of your site from your browser. No terminal or code needed.

Deploy to Cloudflare

  1. Cloudflare copies the worker template into your GitHub account.
  2. It asks for one secret: paste your API key.
  3. It deploys the worker into your Cloudflare account.
  4. Connect the worker to your site: in the Cloudflare dashboard, go to Workers & Pages → your worker → Settings → Domains & Routes and add a route like www.example.com/*. Your DNS record doesn't change, and allowed traffic continues to your site.
  5. Verify the install.
  • Everything the flow creates belongs to you.
  • Every request through the worker counts against your Cloudflare Workers plan. The $5/month plan covers 10 million requests. The free tier caps at 100k a day, which a busy site can hit.
  • To see each step, install the same worker by hand.

By hand: a separate worker

The same worker the deploy button creates. This is the only install for a site on a Worker or Pages project you can't edit.

  1. Create a minimal Workers project if you don't have one:

    npm create cloudflare@latest my-site-d3-edge -- --type=hello-world -y
    cd my-site-d3-edge
  2. Install the adapter:

    npm install @d3-inc/d3-edge-cloudflare-adapter
  3. Replace the generated src/index.ts so it re-exports the adapter:

    // src/index.ts
    import worker from '@d3-inc/d3-edge-cloudflare-adapter';
    
    export default worker;
  4. Replace the generated wrangler config with one that attaches to your hostname. This is a zone route: your DNS record doesn't change, and passed traffic reaches your existing origin with no further config.

    # wrangler.toml
    name = "my-site-d3-edge"
    main = "src/index.ts"
    compatibility_date = "2025-06-01"
    
    routes = [{ pattern = "www.example.com/*", zone_name = "example.com" }]
  5. Set your API key as a secret, not in [vars]:

    npx wrangler secret put DNA_API_KEY
  6. Deploy with npx wrangler deploy, then verify the install.

Origin on a Worker or Pages project

If you can edit the project, wrap it instead. Otherwise, in step 4 attach with a custom domain and a service binding:

routes = [{ pattern = "www.example.com", custom_domain = true }]

[[services]]
binding = "ORIGIN"
service = "your-pages-or-worker-project"
  • The worker takes over the hostname and forwards allowed traffic worker-to-worker, with no public hop.
  • A custom domain replaces the hostname's DNS record, so the worker becomes the origin.
  • ORIGIN (or ORIGIN_URL, for origins reachable only by URL) is required. Without it, passed traffic has nowhere to go.

Paste a Snippet

A Cloudflare Snippet is a single file pasted into your zone, with no Worker. Snippets are included at no extra cost on paid plans; the free tier doesn't have them. Blocking and availability work as in the worker installs.

The file and full guide: github.com/d3-inc/d3-edge-adapters/cloudflare-snippet.

  1. Paste snippet.js into Rules → Snippets in your zone.
  2. Set apiKey to your API key.
  3. Scope it with a snippet rule like http.host eq "www.example.com".
  4. Deploy.

Differences from the worker:

  • CONFIG holds the settings in the configuration reference, as apiKey, enabled, failMode, timeoutMs and paymentTimeoutMs.
  • enabled: false is the kill switch.
  • ORIGIN and ORIGIN_URL don't exist. Passed traffic continues to whatever your zone already routes to.
  • An agent that signs an upload over 1 MB shows as Name only. To verify uploads of any size, use a worker install.
  • A Snippet's log doesn't show your site's response times and errors.

Setup (guided)

During early access, we can do the install with you. The session deploys the worker, attaches the route, and sets the secrets in your account. Everything it creates is yours to change or remove. It takes about half an hour. Book a setup.

Have ready:

  1. An API key for your org.
  2. The hostname to protect, and its origin type (Worker, Pages, or external).
  3. Someone with access to the Cloudflare zone.

Configuration reference

The worker reads these variables. They live in your account, including after a guided setup. They need @d3-inc/d3-edge-cloudflare-adapter 0.9.0 or later.

VariableTypeDefaultDescription
DNA_API_KEYstring (secret)unsetYour organization's API key. Unset: pure passthrough; the worker only proxies.
DNA_ENABLED"false"unsetfalse turns DNA off without removing the key.
DNA_FAIL_MODE"open" | "closed"openopen: serve the request if no decision arrives. closed: block it.
DNA_TIMEOUT_MSnumber (ms)2000Cutoff for a decision.
DNA_PAYMENT_TIMEOUT_MSnumber (ms)10000Cutoff for a request that presents a payment, which settles inside the call. It gets the page only once paid.
DNA_ENDPOINTstring (URL)https://edge-api.d3.com/v1/decisionDecision endpoint. Leave unset unless we give you another.
DNA_DEBUG_LOG_RAW_PAYLOAD"true"unsetDebug only: forwards every header but Authorization and Cookie for the request record.
ORIGINservice bindingn/aStandalone worker only: binding to a Worker/Pages origin. Takes precedence over ORIGIN_URL.
ORIGIN_URLstring (URL)n/aStandalone worker only: explicit origin URL, for origins that aren't Workers.

Kill switch: set DNA_ENABLED = "false" (only false turns it off), or detach the route. Before 0.9.0, unset POLICY_WORKER_URL instead. The standalone worker becomes a transparent proxy and the wrapper a transparent no-op, so your site never depends on DNA to serve.

Earlier variable names

Installs set up with the earlier names keep working unchanged, and a DNA_ name wins when both are set. With the earlier names, POLICY_WORKER_URL is still what switches DNA on. DEBUG_LOG_RAW_PAYLOAD is read only while POLICY_WORKER_URL is set.

Earlier nameNow
POLICY_ADAPTER_KEYDNA_API_KEY
POLICY_SHARED_SECRETDNA_API_KEY
POLICY_WORKER_URLDNA_ENDPOINT, now optional
POLICY_TIMEOUT_MSDNA_TIMEOUT_MS
POLICY_PAYMENT_TIMEOUT_MSDNA_PAYMENT_TIMEOUT_MS
FAIL_MODEDNA_FAIL_MODE
DEBUG_LOG_RAW_PAYLOADDNA_DEBUG_LOG_RAW_PAYLOAD

To move over, update the package to 0.9.0 or later, then rename each variable and drop POLICY_WORKER_URL. If you turned DNA off by leaving POLICY_WORKER_URL unset, set DNA_ENABLED = "false" first: DNA_API_KEY switches DNA on by itself.

Response shape

Every response has an x-d3-edge header: disabled, pass, block, fail-open, fail-closed, payment-required, payment-redirect, or payment-stub. The payment-* values appear only on access and pricing.

A blocked request gets a 403 with a JSON body:

{ "blocked": true, "ruleId": "…", "tier": "…", "identity": "…" }

ruleId names the rule under Rules that stopped the request.

On this page