Filters reference
Arcjet filters let you block requests using
Wireshark-like display
filter
expressions over HTTP headers, IP addresses, and other request fields. This
allows you to quickly enforce rules like allow/deny by country, network, or
user-agent
.
Plan availability
Section titled “Plan availability”Arcjet filter rules is available on all pricing plans.
Configuration
Section titled “Configuration”Allow or deny filters are configured using one or more expressions:
allow
- A list of expressions that will result in anALLOW
decision if the request matches. If none of the expressions match, the request will be denied.deny
- A list of expressions that will result in aDENY
decision if the request matches. If none of the expressions match, the request will be allowed.- These lists are mutually-exclusive, so you can only use one or the other.
They are constructed with the filters(options)
function and configured by
FilterOptionsAllow
or FilterOptionsDeny
:
type FilterOptions = | { allow: ReadonlyArray<string>; deny?: never | undefined; mode?: ArcjetMode | undefined; } | { allow?: never | undefined; deny: ReadonlyArray<string>; mode?: ArcjetMode | undefined; };
type ArcjetMode = "LIVE" | "DRY_RUN";
Example: Allow non-VPN, US-based GET requests
Section titled “Example: Allow non-VPN, US-based GET requests”In this example, the expression matches non-VPN GET requests from the US. Requests matching the expression are allowed and all others are denied.
import { filter } from "@arcjet/nest";// ...// This is part of the rules constructed using withRule or a guard// ...filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE",});
import arcjet, { filter } from "@arcjet/bun";import { env } from "bun";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
export default { fetch: aj.handler(async (req) => { const decision = await aj.protect(req);
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }), port: 3000,};
import arcjet, { filter } from "@arcjet/bun";import { env } from "bun";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
export default { fetch: aj.handler(async (req) => { const decision = await aj.protect(req);
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }), port: 3000,};
import arcjet, { filter } from "@arcjet/deno";
const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, // Get your site key from https://app.arcjet.com rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
Deno.serve( { port: 3000 }, aj.handler(async (req) => { const decision = await aj.protect(req);
if (decision.isDenied()) { return new Response("Forbidden", { status: 403 }); }
return new Response("Hello world"); }),);
import arcjet, { filter } from "@arcjet/next";import { NextResponse } from "next/server";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
// In Next.js, the handler matches the HTTP method of the request so the// http.request.method field filter is somewhat redundant. However, you could// use this filter in middleware instead to block requests before they reach// your route handlers.export async function GET(req: Request) { const decision = await aj.protect(req);
if (decision.isDenied()) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); }
return NextResponse.json({ message: "Hello world", });}
import arcjet, { filter } from "@arcjet/next";import { NextResponse } from "next/server";
const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
// In Next.js, the handler matches the HTTP method of the request so the// http.request.method field filter is somewhat redundant. However, you could// use this filter in middleware instead to block requests before they reach// your route handlers.export async function GET(req) { const decision = await aj.protect(req);
if (decision.isDenied()) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); }
return NextResponse.json({ message: "Hello world", });}
import arcjet, { filter } from "@arcjet/remix";import type { LoaderFunctionArgs } from "@remix-run/node";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
export async function loader(args: LoaderFunctionArgs) { const decision = await aj.protect(args);
if (decision.isDenied()) { throw new Response("Forbidden", { statusText: "Forbidden", status: 403, }); }
return null;}
import arcjet, { filter } from "@arcjet/remix";
const aj = arcjet({ key: process.env.ARCJET_KEY, rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
export async function loader(args) { const decision = await aj.protect(args);
if (decision.isDenied()) { throw new Response("Forbidden", { statusText: "Forbidden", status: 403, }); }
return null;}
import http from "node:http";import arcjet, { filter } from "@arcjet/node";
const aj = arcjet({ key: process.env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
const server = http.createServer(async function (req, res) { const decision = await aj.protect(req);
if (decision.isDenied()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); return; }
res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello world" }));});
server.listen(8000);
import http from "node:http";import arcjet, { filter } from "@arcjet/node";
const aj = arcjet({ key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
const server = http.createServer(async function ( req: http.IncomingMessage, res: http.ServerResponse,) { const decision = await aj.protect(req);
if (decision.isDenied()) { res.writeHead(403, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Forbidden" })); return; }
res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello world" }));});
server.listen(8000);
import { env } from "$env/dynamic/private";import arcjet, { filter } from "@arcjet/sveltekit";import { error } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your site key from https://app.arcjet.com rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
export async function handle({ event, resolve }) { const decision = await aj.protect(event);
if (decision.isDenied()) { return error(403, "Forbidden"); }
return resolve(event);}
import { env } from "$env/dynamic/private";import arcjet, { filter } from "@arcjet/sveltekit";import { type RequestEvent, error } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ filter({ allow: [ // Requests matching this expression will be allowed. All other // requests will be denied. 'http.request.method eq "GET" and ip.src.country eq "US" and not ip.src.vpn', ], mode: "LIVE", }), ],});
export async function handle({ event, resolve,}: { event: RequestEvent; resolve: (event: RequestEvent) => Response | Promise<Response>;}): Promise<Response> { const decision = await aj.protect(event);
if (decision.isDenied()) { return error(403, "Forbidden"); }
return resolve(event);}
Expression language
Section titled “Expression language”The expression language is based on Wireshark-like
display
filter
expressions. Like other programming languages, there are fields (ip.src.vpn
),
functions (lower()
), operators (or
, matches
), and values ("curl"
).
Example: Matching VPN, Tor, or Curl requests
Section titled “Example: Matching VPN, Tor, or Curl requests”This example matches VPN or Tor traffic and basic Curl requests.
ip.src.vpn orip.src.tor orlower(http.request.headers["user-agent"]) matches "curl"
To use it as a filter deny rule so that any requests matching the expression are denied (and all others allowed), you would configure Arcjet like this:
filter({ deny: [ 'ip.src.vpn or ip.src.tor or lower(http.request.headers["user-agent"]) matches "curl"', ], mode: "LIVE",}),
Example: Matching requests from specific countries
Section titled “Example: Matching requests from specific countries”This example matches requests from the European Union:
ip.src.country in {"AT" "BE" "BG" "CY" "CZ" "DE" "DK" "EE" "ES" "FI" "FR" "GR" "HR" "HU" "IE" "IT" "LT" "LU" "LV" "MT" "NL" "PL" "PT" "RO" "SE" "SI" "SK"}
To use it as a filter allow rule so that only requests matching the expression are allowed (and all others denied), you would configure Arcjet like this:
filter({ allow: [ 'ip.src.country in {"AT" "BE" "BG" "CY" "CZ" "DE" "DK" "EE" "ES" "FI" "FR" "GR" "HR" "HU" "IE" "IT" "LT" "LU" "LV" "MT" "NL" "PL" "PT" "RO" "SE" "SI" "SK"}', ], mode: "LIVE",}),
Fields
Section titled “Fields”Field | Type | Example value |
---|---|---|
http.host | string | "example.com" |
http.request.cookie | Map<string> | {NEXT_LOCALE: "en-US", …} |
http.request.headers | Map<string> | {"user-agent": "Mozilla/5.0 (Macintosh; …"} |
http.request.method | string | "GET" |
http.request.uri.args | Map<string> | {q: "search"} |
http.request.uri.path | string | "/quick-start" |
ip.src.accuracy_radius | string | "2" |
ip.src.asnum.country | string | "US" |
ip.src.asnum.domain | string | "google.com" |
ip.src.asnum.name | string | "Google, LLC." |
ip.src.asnum.type | string | "business" |
ip.src.asnum | string | "15169" |
ip.src.city | string | "Mountain View" |
ip.src.continent.name | string | "North America" |
ip.src.continent | string | "NA" |
ip.src.country.name | string | "United States" |
ip.src.country | string | "US" |
ip.src.crawler.name | string | "" |
ip.src.crawler | boolean | false |
ip.src.hosting | boolean | false |
ip.src.lat | string | "37.33939" |
ip.src.lon | string | "-121.89496" |
ip.src.mobile | boolean | false |
ip.src.postal_code | string | "94043" |
ip.src.proxy | boolean | false |
ip.src.region | string | "California" |
ip.src.relay | boolean | false |
ip.src.service | string | "" |
ip.src.timezone.name | string | "America/Los_Angeles" |
ip.src.tor | boolean | false |
ip.src.vpn | boolean | false |
ip.src | Ip | 8.8.8.8 |
All ip.src.*
fields reflect information about the client IP address (the
request source IP). These fields require data from our IP reputation database so
a call to the Arcjet API is required. Other fields are analyzed locally.
Arcjet only ever makes a single call to our Cloud API regardless of the rule configuration. The Arcjet API has been designed for high performance and low latency, and is deployed to multiple regions around the world. The SDK will automatically use the closest region which means the total overhead is typically no more than 20-30ms, often significantly less. See the Architecture reference for details.
Operators
Section titled “Operators”Operator | Example expression |
---|---|
and (&& ) | http.request.method eq "GET" and http.request.uri.path eq "/quick-start" |
contains | http.request.uri.path contains "quick-start" |
eq (== ) | http.request.method eq "GET" |
ge (>= ) | len(http.request.uri.args["q"]) ge 1 |
gt (> ) | len(http.request.uri.args["q"]) gt 0 |
in | ip.src in { 185.199.108.153 185.199.109.153 } |
le (<= ) | len(http.request.uri.args["q"]) le 3 |
lt (< ) | len(http.request.uri.args["q"]) lt 4 |
matches (~ ) | http.request.headers["user-agent"] matches "Chrome" |
ne (!= ) | http.request.method ne "GET" |
not (! ) | not ip.src.vpn |
or (|| ) | ip.src.vpn or ip.src.relay |
strict wildcard | http.request.uri.path strict wildcard "/articles/*" |
wildcard | http.request.uri.path wildcard "/articles/*" |
xor (^^ ) | ip.src.vpn xor ip.src.relay |
Functions
Section titled “Functions”Function | Example |
---|---|
len | len(http.request.uri.args["q"]) gt 0 |
lower | lower(ip.src.country) contains "be" |
upper | upper(http.request.headers["user-agent"]) contains "CHROME" |
Values
Section titled “Values”Strings
Section titled “Strings”String values are enclosed in double quotes ("
).
Double quotes within the string must be escaped with a backslash (\"
).
http.host eq "example.com"
Booleans
Section titled “Booleans”Boolean values are either true
or false
.
You can negate a boolean value with the not
(!
) operator.
ip.src.vpn
not ip.src.vpn
IP addresses
Section titled “IP addresses”IP addresses are a special type which must not be quoted like strings.
ip.src eq 185.199.108.153
ip.src in { 185.199.108.153 185.199.109.153 185.199.110.153 185.199.111.153 }
ip.src in { 192.0.2.0/24 }
Maps (or associative arrays) are a collection of keys to values.
They cannot be written literally in the expression language, but they exist for
the fields http.request.cookie
, http.request.headers
, and
http.request.uri.args
.
http.request.headers["user-agent"] matches "Chrome"
Decision
Section titled “Decision”Arcjet provides a single protect
function that is used to execute your
protection rules.
This function returns a Promise
that resolves to an ArcjetDecision
object.
This contains the following properties:
id
(string
) - The unique ID for the request. This can be used to look up the request in the Arcjet dashboard. It is prefixed withreq_
for decisions involving the Arcjet cloud API. For decisions taken locally, the prefix islreq_
.conclusion
(ArcjetConclusion
) - The final conclusion based on evaluating each of the configured rules. If you wish to accept Arcjet’s recommended action based on the configured rules then you can use this property.reason
(ArcjetReason
) - An object containing more detailed information about the conclusion.results
(ArcjetRuleResult[]
) - An array ofArcjetRuleResult
objects containing the results of each rule that was executed.ip
(ArcjetIpDetails
) - An object containing Arcjet’s analysis of the client IP address. See the SDK reference for more information.
You check if a deny conclusion has been returned by a filter rule by using
decision.isDenied()
and decision.reason.isFilterRule()
respectively.
You can iterate through the results and check whether a filter rule was applied:
for (const result of decision.results) { console.log("Rule Result", result);}
Error handling
Section titled “Error handling”Arcjet is designed to fail open so that a service issue or misconfiguration does
not block all requests. The SDK will also time out and fail open after 500ms
when NODE_ENV
is production
and 1000ms otherwise. However, in most cases,
the response time will be less than 20-30ms.
If there is an error condition when processing the rule, Arcjet will return an
ERROR
result for that rule and you can check the message
property on the rule’s
error result for more information.
If all other rules that were run returned an ALLOW
result, then the final Arcjet
conclusion will be ERROR
.
Testing
Section titled “Testing”Arcjet runs the same in any environment, including locally and in CI. You can
use the mode
set to DRY_RUN
to log the results of rule execution without
blocking any requests.
We have an example test framework you can use to automatically test your rules. Arcjet can also be triggered based using a sample of your traffic.
See the Testing section of the docs for details.
Limitations
Section titled “Limitations”- A maximum of 10 expressions can be specified.
- Each expression can be a maximum of 1024 bytes.