This is the reference guide for the Arcjet Next.js SDK, available on
GitHub and licensed under the Apache 2.0
license.
What is Arcjet?Arcjet helps developers protect their apps
in just a few lines of code. Bot detection. Rate limiting. Email validation. Attack protection. Data redaction. A developer-first approach to security.
Installation
In your project root, run the following command to install the SDK:
We recommend creating a single instance of the Arcjet object and reusing it
throughout your application. This is because the SDK caches decisions and
configuration to improve performance.
The pattern we use is to create a utility file that exports the Arcjet object
and then import it where you need it. See our example Next.js
app for how
this is done.
Rule modes
Each rule can be configured in either LIVE or DRY_RUN mode. When in
DRY_RUN mode, each rule will return its decision, but the end conclusion will
always be ALLOW.
This allows you to run Arcjet in passive / demo mode to test rules before
enabling them. Arcjet will log what it would have done.
Multiple rules
You can combine rules to create a more complex protection strategy. For example,
you can combine rate limiting and bot protection rules to protect your API from
automated clients.
The following environment variables can be used to configure the SDK at runtime:
ARCJET_BASE_URL - Will override the decision API which the SDK communicates
with. This defaults to https://decide.arcjet.com and should only be changed
if directed by Arcjet support.
ARCJET_LOG_LEVEL - The log level to use, either debug, info, warn, or
error. Defaults to warn. If a rule is in dry run mode, a warning will be
output with the decision that would have been applied.
ARCJET_ENV - Set to development to force Arcjet into development mode.
This will allow private/internal addresses so that the SDKs work correctly
locally. You usually do not need to set this because it uses NODE_ENV when
set. See
Troubleshooting
for when this may be needed.
Custom logging
The SDK uses a lightweight logger which mirrors the
Pinostructured
logger
interface. You can use this to customize the logging output.
First, install the required packages:
Then, create a custom logger that will log to JSON in production and pretty
print in development:
Finally, Next.js
requires
marking Pino as an external package in your Next.js config file:
Protect
Arcjet provides a single protect function that is used to execute your
protection rules. This requires a request object which is the request
argument as passed to the Next.js request handler. Rules you add to the SDK may
require additional details, such as the validateEmail rule requiring an
additional email prop.
This function returns a Promise that resolves to an ArcjetDecision object,
which provides a high-level conclusion and detailed explanations of the decision
made by Arcjet.
Arcjet can protect Next.js pages that are server components (the default).
Client components cannot be protected because they run on the client side and do
not have access to the request object.
Server actions
Arcjet supports server actions in both Next.js 14 and 15 for all features except
sensitive data detection. You need to call a utility function request() that
accesses the headers we need to analyze the request.
These examples will throw when there is an error
(docs),
but you can use
useFormState
to return errors to the form. If you are using React 19, use
useActionState instead.
Decision
The protect function 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 with req_ for decisions
involving the Arcjet cloud API. For decisions taken locally, the prefix is
lreq_.
conclusion ("ALLOW" | "DENY" | "CHALLENGE" | "ERROR") - 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 of ArcjetRuleResult objects
containing the results of each rule that was executed.
ttl (uint32) - The time-to-live for the decision in seconds. This is the
time that the decision is valid for. After this time, the decision will be
re-evaluated. The SDK automatically caches DENY decisions for the length of
the TTL.
ip (ArcjetIpDetails) - An object containing Arcjet’s analysis of the
client IP address. See IP analysis below for more information.
Conclusion
The ArcjetDecision object has the following methods that should be used to
check the conclusion:
isAllowed() (bool) - The request should be allowed.
isDenied() (bool) - The request should be denied.
isErrored() (bool) - There was an unrecoverable error.
Reason
The reason property of the ArcjetDecision object contains an ArcjetReason
object which provides more detailed information about the conclusion. This is
the final decision reason and is based on the configured rules.
The ArcjetReason object has the following methods that can be used to check
which rule caused the conclusion:
isBot() (bool) - Returns true if the bot protection rules have been
applied and the request was considered to have been made by a bot.
isRateLimit() (bool) - Returns true if the rate limit rules have been
applied and the request has exceeded the rate limit.
isShield() (bool) - Returns true if the shield rules have been applied
and the request is suspicious based on analysis by Arcjet Shield.
isEmail() (bool) - Returns true if the email validation rules have been
applied and the email address has a problem.
isError() (bool) - Returns true if there was an error processing the
request.
Results
The results property of the ArcjetDecision object contains an array of
ArcjetRuleResult objects. There will be one for each configured rule so you
can inspect the individual results:
id (string) - The ID of the rule result. Not yet implemented.
state (ArcjetRuleState) - Whether the rule was executed or not.
conclusion (ArcjetConclusion) - The conclusion of the rule. This will be
one of the above conclusions: ALLOW, DENY, CHALLENGE, or ERROR.
reason (ArcjetReason) - An object containing more detailed information
about the conclusion for this rule. Each rule type has its own reason object
with different properties.
You can iterate through the results and check the conclusion for each rule.
This example will log the full result as well as each rate limit rule:
The state property of the ArcjetRuleResult object is an ArcjetRuleState.
Each rule is evaluated individually and can be in one of the following states:
DRY_RUN - The rule was executed in dry run mode. This means that the rule
was executed but the conclusion was not applied to the request. This is useful
for testing rules before enabling them.
RUN - The rule was executed and the conclusion was applied to the request.
NOT_RUN - The rule was not executed. This can happen if another rule has
already reached a conclusion that applies to the request. For example, if a
rate limit rule is configured then these are evaluated before all other rules.
If the client has reached the maximum number of requests then other rules will
not be evaluated.
CACHED - The rule was not executed because the previous result was cached.
Results are cached when the decision conclusion is DENY. Subsequent requests
from the same client will not be evaluated against the rule until the cache
expires.
Rule reason
The reason property of the ArcjetRuleResult object contains an
ArcjetReason object which provides more detailed information about the
conclusion for that configured rule.
Shield
The ArcjetReason object for shield rules has the following properties:
As of SDK version 1.0.0-alpha.11, the ArcjetDecision object contains an ip
property. This includes additional data about the client IP address:
IP location
The IP location fields may be undefined, but you can use various methods to
check their availability. Using the methods will also refine the type to remove
the need for null or undefined checks.
hasLatitude() (bool): returns whether the latitude and accuracyRadius
fields are available.
hasLongitude() (bool): returns whether the longitude and
accuracyRadius fields are available.
hasAccuracyRadius() (bool): returns whether the longitude, latitude,
and accuracyRadius fields are available.
hasTimezone() (bool): returns whether the timezone field is available.
hasPostalCode() (bool): returns whether the postalCode field is
available.
hasCity() (bool): returns whether the city field is available.
hasRegion() (bool): returns whether the region field is available.
hasCountry() (bool): returns whether the country and countryName
fields are available.
hasContinent() (bool): returns whether the continent and continentName
fields are available.
latitude (number | undefined): the latitude of the client IP address.
longitude (number | undefined): the longitude of the client IP address.
accuracyRadius (number | undefined): how accurate the location is in
kilometers.
timezone (string | undefined): the timezone of the client IP address.
postalCode (string | undefined): the postal or zip code of the client IP
address.
city (string | undefined): the city of the client IP address.
region (string | undefined): the region of the client IP address.
country (string | undefined): the country code the client IP address.
countryName (string | undefined): the country name of the client IP
address.
continent (string | undefined): the continent code of the client IP
address.
continentName (string | undefined): the continent name of the client IP
address.
Location accuracy
IP geolocation can be notoriously inaccurate, especially for mobile devices,
satellite internet providers, and even just normal users. Likewise with the
specific fields like city and region, which can be very inaccurate. Country
is usually accurate, but there are often cases where IP addresses are
mis-located. These fields are provided for convenience e.g. suggesting a user
location, but should not be relied upon by themselves.
IP AS
This is useful for identifying the network operator of the client IP address.
This is useful for understanding whether the client is likely to be automated or
not, or being stricter with requests from certain networks.
The IP AS fields may be undefined, but you can use the hasASN() method to
check their availability. Using this method will also refine the type to remove
the need for null-ish checks.
hasASN() (bool): returns whether all of the ASN fields are available.
asn (string | undefined): the autonomous system (AS) number of the client
IP address.
asnName (string | undefined): the name of the AS of the client IP address.
asnDomain (string | undefined): the domain of the AS of the client IP
address.
asnType ('isp' | 'hosting' | 'business' | 'education'): the type of the AS
of the client IP address. Real users are more likely to be on an ISP or
business network rather than a hosting provider. Education networks often have
a single or small number of IP addresses even though there are many users. A
common mistake is to block a single IP because of too many requests when it is
a university or company network using
NAT (Network Address
Translation) to give many users the same IP.
asnCountry (string | undefined): the country code of the AS of the client
IP address. This is the administrative country of the AS, not necessarily the
country of the client IP address.
IP type
The service field may be undefined, but you can use the hasService()
method to check the availability. Using this method will also refine the type to
remove the need for null-ish checks.
hasService() (bool): whether the service field is available.
service (string | undefined): the name of the service associated with the
IP address—e.g. Apple Private Relay.
isHosting() (bool): returns whether the IP address of the client is owned
by a hosting provider. Requests originating from a hosting provider IP
significantly increase the likelihood that this is an automated client.
isVpn() (bool): returns whether the IP address of the client is owned by a
VPN provider. Many people use VPNs for privacy or work purposes, so by itself
this is not an indicator of the client being automated. However, it does
increase the risk score of the client and depending on your use case it may be
a characteristic you wish to restrict.
isProxy() (bool): returns whether the IP address of the client is owned by
a proxy provider. Similar to isVpn(), but proxies are more likely to involve
automated traffic.
isTor() (bool): returns whether the IP address of the client is known to
be part of the Tor network. As with isVpn(), there are legitimate uses for
hiding your identity through Tor, however it is also often a way to hide the
origin of malicious traffic.
isRelay() (bool): returns whether the IP address of the client is owned by
a relay service. The most common example is Apple iCloud Relay, which
indicates the client is less likely to be automated because Apple requires a
paid subscription linked to an Apple account in good standing.
For the IP address 8.8.8.8 you might get the following response. Only the
fields we have data for will be returned:
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 1000ms
when NODE_ENV or ARCJET_ENV is development and 500ms otherwise. However,
in most cases, the response time will be less than 20-30ms.
If there is an error condition, Arcjet will return an ERRORconclusion.
Sometimes it is useful to add additional protection via a rule based on the
logic in your handler; however, you usually want to inherit the rules, cache,
and other configuration from our primary SDK. This can be achieved using the
withRule function which accepts an ad-hoc rule and can be chained to add
multiple rules. It returns an augmented client with the specialized protect
function.
Arcjet will automatically detect the IP address of the client making the request
based on the context provided. The implementation is open source in our
@arcjet/ip package.
In development environments (NODE_ENV === "development" or
ARCJET_ENV === "development"), we allow private/internal addresses so that the
SDKs work correctly locally.
Client override
The default client can be overridden. If no client is specified, a default one
will be used. Generally you should not need to provide a client - the Arcjet
Next.js SDK will automatically handle this for you, including when using the
Edge Runtime.
When a Node.js version goes end of life, we will bump the major version of the
Arcjet SDK. Technical support is provided for the current major
version of the Arcjet SDK for all users and for the current and previous major
versions for paid users. We will provide security fixes for the current and
previous major SDK versions.
Next.js
Arcjet supports the current and previous major versions of Next.js for both the
app router and pages router. When a new major version of Next.js is released, we
will bump the major version of the Arcjet SDK.
Previous supported major version: Next.js 14.x
Current supported major version: Next.js 15.x
The pages router will be supported for as long as Next.js supports it.
Technical support is provided for the current major
version of the Arcjet SDK for all users and for the current and previous major
versions for paid users. We will provide security fixes for the current and
previous major versions.