Cloudflare Worker Rust
This module enables the use of DataDome Bot Protection on Cloudflare, for Workers written in Rust. It is distributed as the datadome-cloudflare-worker crate.
Looking for the JavaScript version? See Cloudflare Worker.
Installation
Prerequisites
- The server-side key available in your DataDome dashboard
-
Wrangler 4 installed and configured.
-
A stable Rust toolchain with the
wasm32-unknown-unknowntarget andworker-build:rustup target add wasm32-unknown-unknown cargo install worker-build -
A Rust Worker project. If you don't have one, create it with
cargo generate cloudflare/workers-rs.
Protect your traffic
Update an existing Rust Worker to start protecting your traffic with DataDome Bot Protection:
-
Add the crate to your
Cargo.toml, next to theworkercrate you already depend on:[dependencies] datadome-cloudflare-worker = "1.0.0" worker = "0.8" -
In your entry file (
src/lib.rs), hand every request to the module before your own logic:use datadome_cloudflare_worker::DataDome; use worker::*; #[event(fetch)] async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> { DataDome::new() .on_allowed_request(my_handler) .handle_request(req, &env) .await } // Your code: produces the response for every request DataDome lets through, usually by forwarding it to your origin. async fn my_handler(req: Request) -> Result<Response> { Fetch::Request(req).send().await }on_allowed_requestis required: your origin is always called through it.handle_requestonly exists once it has been called, so forgetting it is a compile error.- When DataDome blocks the request, the challenge or block response goes to the client directly and
my_handleris skipped. To run code on it, addon_challenged_request(see Run code on challenged requests). - Settings come from the
DATADOME_*variables and secrets of the Worker, or from the builder (see Configure in code).
-
Make sure your
wrangler.tomlbuilds the Worker withworker-buildand declares the route to protect:name = "my-worker" main = "build/index.js" compatibility_date = "2026-01-01" route = { pattern = "example.org/*", zone_name = "example.org" } [build] command = "worker-build --release" -
Create a Worker secret named
DATADOME_SERVER_SIDE_KEY:wrangler secret put DATADOME_SERVER_SIDE_KEYA prompt appears. Enter the value of your DataDome server-side key you can find in your DataDome Dashboard.
-
Optional, to inject the JS Tag into your HTML pages: create a second secret named
DATADOME_CLIENT_SIDE_KEYwith your client-side key, found on the same dashboard page (see Enable JS Tag injection). Skip it if your pages already load the JS Tag.wrangler secret put DATADOME_CLIENT_SIDE_KEY -
Deploy:
wrangler deploy
Congrats! 🎉 Your traffic is now protected by DataDome Bot Protection.
Configuration
Every setting is resolved for each request from three sources. When the same setting is set in more than one, the one listed last wins:
- Built-in defaults.
- The builder setters, in code (see Configure in code).
- Worker variables and secrets named
DATADOME_*, set inwrangler.tomlor withwrangler secret put.
Values of variables are strings: booleans are true (case-insensitive), numbers are parsed, lists are JSON arrays of strings, regular expressions are patterns. An empty or unparsable value is ignored and the previous source applies.
Server-side settings
| Setting | Description | Required | Default |
|---|---|---|---|
DATADOME_SERVER_SIDE_KEY | DataDome server-side key. Store it as a secret. | Yes | — |
DATADOME_TIMEOUT | Timeout of the call to the DataDome API, in milliseconds (see How do I change the timeout value?) | Optional | 300 |
DATADOME_ENDPOINT | Host of the DataDome API | Optional | api-cloudflare.datadome.co |
DATADOME_URL_REGEX_EXCLUSION | Regular expression to ignore all matching URLs (see URL matching) | Optional | — |
DATADOME_STATIC_ASSET_EXCLUSION | Regular expression of static-asset extensions to ignore. An empty value disables the list. | Optional | Default static-asset pattern |
DATADOME_ENCODED_CHAR_INCLUSION | Regular expression of encoded characters; a static-asset URL whose path contains a match is inspected anyway. An empty value disables it. | Optional | %23|%25|%2f|%3b|%3f |
DATADOME_REMOVE_MATRIX_PARAMS | Strip matrix parameters (;name=value segment suffixes, RFC 3986) from the path before matching | Optional | true |
DATADOME_URL_REGEX_INCLUSION | Regular expression to process only matching URLs, applied after the exclusions | Optional | — |
DATADOME_IP_EXCLUSION | JSON array of IPv4 and IPv6 addresses or CIDR ranges whose traffic is ignored, for example ["192.168.0.1", "10.0.0.0/8"] | Optional | — |
DATADOME_ENABLE_GRAPHQL_SUPPORT | Extract the GraphQL operation type, name, and count from requests to the GraphQL endpoint (see Enable GraphQL support) | Optional | false |
DATADOME_GRAPHQL_ENDPOINT | Comma-separated list of GraphQL endpoint paths; a request matches when its path contains one of them | Optional | graphql |
DATADOME_ENABLE_MCP_SUPPORT | Report MCP information for requests to the MCP endpoint (see Enable MCP support) | Optional | false |
DATADOME_MCP_ENDPOINT | Comma-separated list of MCP endpoint paths, matched like the GraphQL ones | Optional | mcp |
DATADOME_ENABLE_REFERRER_RESTORATION | Restore the original Referer header after a challenge (see Restore the referrer) | Optional | false |
DATADOME_MAXIMUM_BODY_SIZE | Maximum number of request body bytes read for the GraphQL and MCP fields | Optional | 25600 |
DATADOME_ENABLE_DEBUGGING | Log every step of the DataDome process in the Workers logs, not only the errors | Optional | false |
Custom fields, user and product identifiers are set in code, per request (see Add custom fields).
Client-side settings
| Setting | Description | Required | Default |
|---|---|---|---|
DATADOME_CLIENT_SIDE_KEY | Client-side key from the DataDome dashboard. When set, the JS Tag is injected into HTML responses (see Enable JS Tag injection) | Optional | — |
DATADOME_JS_URL | URL of the JS Tag, for a first-party setup | Optional | https://js.datadome.co/tags.js |
DATADOME_JS_ENDPOINT | Endpoint of the JS Tag, added to its options | Optional | — |
DATADOME_JS_TAG_OPTIONS | JS Tag options, as a JSON object, written as window.ddoptions | Optional | {} |
DATADOME_JS_URL_REGEX_EXCLUSION | Regular expression of URLs that must not get the JS Tag | Optional | — |
DATADOME_JS_URL_REGEX_INCLUSION | Regular expression of URLs that get the JS Tag; all pages when unset | Optional | — |
URL matching
Patterns are matched against host + path (for example www.example.com/products/1.html), without the query string. The path is used exactly as received, percent-encoded. The JS Tag patterns are matched against the full URL instead.
The default static-asset pattern is:
(?i)\.(avi|avif|bmp|css|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|json|less|map|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|otf|pdf|png|svg|svgz|swf|tif|tiff|ttf|wav|webm|webp|woff|woff2|xml|zip)$
Regex supportRegular expressions use the Rust
regexsyntax. Look-around and backreferences are not supported; an invalid pattern is ignored and the default is kept.
Advanced Configuration
Configure in code
The builder has one setter per setting, named like the variable without the DATADOME_ prefix, in lower case. A variable or secret with the same name still wins:
DataDome::new()
.timeout(500)
.url_regex_exclusion(Regex::new("^/health$").unwrap())
.ip_exclusion(["10.0.0.0/8"])
.on_allowed_request(my_handler)
.handle_request(req, &env)
.awaitRun code on challenged requests
To log, count, or notify on challenged requests, or to adapt the response, add on_challenged_request. It runs once per challenged request (challenge page, hard block, redirect, 429), after the response is built and before it is sent, and returns the response to send to the client.
The hook receives:
- the client request, carrying the enriched headers (
x-datadome-botname,x-datadome-isbot, ...); - the DataDome response, with its status and the headers about to be sent to the client (
X-DataDome,X-DataDome-CID,Set-Cookie, ...).
DataDome::new()
.on_allowed_request(my_handler)
.on_challenged_request(|req, response| async move {
let botname = req.headers().get("x-datadome-botname")?.unwrap_or_default();
let isbot = req.headers().get("x-datadome-isbot")?.unwrap_or_default();
console_log!("challenged {} {} -> {} (botname: {botname}, isbot: {isbot})", req.method(), req.path(), response.status_code());
Ok(response)
})
.handle_request(req, &env)
.awaitDo not forward the request from this hook. An error it returns is propagated to the caller, like an error from your handler. Allowed, skipped, and bypassed requests reach your handler instead. Without the hook, the response is sent untouched.
Add custom fields
Custom fields featureDataDome lets you enrich in real time our detection engine by sending us some custom fields with your business data. These fields can be used for specific detection models.
👋 Please reach out to our support team for reviewing the data received.
Pass them with request_metadata, as a list of field name and value. A value is a fixed string, a request header (DataDome::header), or a closure computing it from the request (DataDome::from_fn); an absent header, an empty value or None leaves the field unset. The fields are CustomFieldString1..3, CustomFieldInteger1..2, CustomFieldFloat1, UserID (opaque identifier, never personal data), and ProductID; all values are sent as strings and cut to the length the API accepts:
DataDome::new()
.request_metadata([
("CustomFieldString1", "standard".into()),
("ProductID", DataDome::header("x-product-id")),
("UserID", DataDome::header("x-user-id")),
// Computed: sent only when the header is a valid integer.
("CustomFieldInteger1", DataDome::from_fn(|req| {
let value = req.headers().get("x-cart-size").ok().flatten()?;
value.trim().parse::<i64>().ok().map(|n| n.to_string())
})),
])
.on_allowed_request(my_handler)
.handle_request(req, &env)
.awaitA header value must be set by a trusted proxy that strips it from client requests.
Restore the referrer
After passing a DataDome challenge on browsers other than Firefox, the referrer value is updated to the current URL, which can lead to inconsistent results in website analytics.
Set DATADOME_ENABLE_REFERRER_RESTORATION to true to restore the Referer header to its original value for your origin. Contact our support team first: they will review your requirements and provide you with the best recommendations.
Enable GraphQL support
Set DATADOME_ENABLE_GRAPHQL_SUPPORT to true.
Once enabled, POST requests targeting a GraphQL endpoint with content-type: application/json will be analyzed to extract GraphQL operation name and type.
Multiple endpoints can be configured using the DATADOME_GRAPHQL_ENDPOINT setting.
Enable MCP support
Set DATADOME_ENABLE_MCP_SUPPORT to true.
Once enabled, requests targeting a Model Context Protocol (MCP) endpoint will be analyzed to extract MCP properties according to the official specification:
mcp-session-idandmcp-protocol-versionheadersjsonRpcVersion,jsonRpcRequestId, andmcpMethodrequest body fieldsmcpParamsClientInfoNameandmcpParamsClientInfoVersionrequest body fields when the method isinitializemcpParamsToolNamerequest body field when the method istools/call
Multiple endpoints can be configured using the DATADOME_MCP_ENDPOINT setting.
Enable JS Tag injection
Set DATADOME_CLIENT_SIDE_KEY to your client-side key, as a secret like the server-side key (step 5 of Protect your traffic). The module then inserts the JS Tag loader at the beginning of the <head> or <body> of every HTML page your origin returns for an allowed request:
<script>
window.ddjskey = 'YOUR_CLIENT_SIDE_KEY';
window.ddoptions = {};
</script>
<script src="https://js.datadome.co/tags.js" async></script>Use DATADOME_JS_URL, DATADOME_JS_ENDPOINT, and DATADOME_JS_TAG_OPTIONS to change the script URL and the options, and DATADOME_JS_URL_REGEX_INCLUSION or DATADOME_JS_URL_REGEX_EXCLUSION to choose the pages.
If your pages already load the JS Tag, leave
DATADOME_CLIENT_SIDE_KEYunset.
Logging
Enriched headers
The DataDome API returns enriched headers with each request. The module copies the ones the API lists in x-datadome-request-headers onto the request, and the ones in x-datadome-headers onto the response sent to the client. Where to read them depends on the response:
- Allowed request: your handler receives the request with the headers, and the response it returns gets the response headers.
- Challenged request: your handler is not called; the
on_challenged_requesthook receives the request with the headers, and the response with its headers. - Not analysed (excluded URL, timeout, or error of the API call, answer without a verdict): no header is added, and your handler receives the request untouched.
async fn my_handler(req: Request) -> Result<Response> {
for (name, value) in req.headers().entries() {
if name.starts_with("x-datadome") {
console_log!("{name}: {value}");
}
}
Fetch::Request(req).send().await
}DataDome logs
-
Enable the Workers logs in your
wrangler.toml:[observability.logs] enabled = true -
Deploy with
wrangler deploy. -
By default, the module logs only the requests DataDome could not analyse (error of the API call) and the post-replay failures. Timeouts are logged only in debug mode. Set
DATADOME_ENABLE_DEBUGGINGtotrueto log every step, including timeouts and excluded URLs.
The logs are JSON lines with the following shape:
{
"step": "check Protection API response status",
"result": "allow request",
"reason": "response is allowed by DataDome",
"company": "DataDome"
}Uninstallation
- Remove
datadome-cloudflare-workerfrom yourCargo.toml. - In
src/lib.rs, call your handler directly instead ofDataDome::new()...handle_request. - Delete the
DATADOME_*variables from yourwrangler.toml, and the secrets withwrangler secret delete DATADOME_SERVER_SIDE_KEYandwrangler secret delete DATADOME_CLIENT_SIDE_KEY. - Deploy with
wrangler deploy.
FAQ
How do I change the timeout value?
Set DATADOME_TIMEOUT in milliseconds, in wrangler.toml or with the timeout setter. When the timeout expires, the module logs the step and forwards the request to your handler untouched.
How do I test or debug the Worker?
Tail the logs with wrangler tail --format pretty, or read them in the Cloudflare dashboard once the Workers logs are enabled (see DataDome logs). With DATADOME_ENABLE_DEBUGGING set to true, every request logs its response and the reason.
The request is forwarded to your handler unprotected, with a log line, when:
- The DataDome API did not answer within
DATADOME_TIMEOUT(logged in debug mode only). - The DataDome API could not be reached, or answered without the
x-datadomeresponseheader.
To test the challenge, send requests with the BLOCKUA (blocked, HTTP 403) and CI-Allowed (allowed, HTTP 200) user agents:
curl -i -A CI-Allowed https://example.org/
curl -i -A BLOCKUA https://example.org/Are requests in the cache protected by DataDome?
Yes. The Worker is executed on the incoming request before the Cloudflare cache lookup, like the JavaScript module.
Updated about 5 hours ago

