Vercel BotID vs Arcjet
Arcjet is an alternative to Vercel BotID for protecting your applications from bots. Both products offer bot detection and mitigation features, but there are some key differences to consider when choosing between them.
Key differences: Vercel BotID vs Arcjet
Section titled “Key differences: Vercel BotID vs Arcjet”-
Security platform: Arcjet is a comprehensive security platform that offers a wide range of protections including bot detection, rate limiting, email validation, sensitive info detection, shield WAF (web application firewall), filter rules for expression-based traffic control, and AI Runtime Protection (bot detection and prompt injection for AI endpoints, per-user token budget enforcement, and PII detection in AI flows). Vercel BotID is primarily focused on bot detection and mitigation. Vercel’s broader platform services include WAF and rate limiting, but they are separate from BotID and must be configured independently.
-
Integration: Arcjet is designed to integrate directly into your codebase, allowing you to customize protections for different parts of your application. Vercel BotID is primarily designed as a wrapper around your Next.js application in the instrumentation client or within specific event handlers for other frameworks. Protected paths must be configured separately from the code for BotID whereas Arcjet configuration happens in code. Vercel’s BotID doesn’t work with traditional HTML forms whereas Arcjet can protect any type of request handler.
-
Agentic workloads and non-HTTP entry points: Vercel BotID is built around inbound HTTP requests — it issues a token in the browser and verifies it server-side on a protected route. It has nothing to say about what happens after a request enters your application. That blind spot is where most agent damage actually occurs: a model fetches a web page and re-enters its context loaded with hidden instructions; a tool call burns tokens in a runaway loop; a background job ships a support ticket to a third-party LLM with PII attached. None of these surface as a fresh HTTP request BotID can verify. Arcjet Guards applies token-bucket rate limiting, prompt injection detection, and PII detection inline at the points an agent actually fans out: inside MCP tool handlers, between agent steps, in queue consumers, and across multi-service pipelines. You call
guard()with the input directly; noRequestobject required. JS/TS via@arcjet/guard; Python viaarcjet. -
Platform independence: Arcjet is platform-agnostic and can be used with any hosting provider or framework for JavaScript, TypeScript, and Python. Vercel BotID only works for JS/TS applications hosted on Vercel.
-
Developer experience: Arcjet is called like any other library from your codebase and provides a response with detailed analysis of each request. Rules are just code so can be varied dynamically based on request context e.g. changing rate limits based on user subscription level or allowing / denying certain bots based on the type of traffic you expect. Results can be inspected to understand the decision and customize the response. For operational changes that can’t wait for a deploy, Arcjet Remote Rules add dashboard- or MCP-managed rate limit, bot, filter, and Shield rules that take effect immediately. Vercel BotID provides few configuration options and returns only minimal information about the request.
-
Advanced client signals and filter rules: Arcjet provides advanced bot signals, a browser-based detection layer that runs a WebAssembly module on the client. The module collects environment signals — headless-browser markers, automation framework fingerprints, browser-quirk inconsistencies, and timing patterns — sends them to the Arcjet API, and stores a continue token in the
aj_signalscookie that persists across navigations. On the next server-side request,detectBotevaluates the cookie alongside its regular server-side classification (user agent matching, IP reputation, verified-bot lists) and produces a single decision. A failed signals check surfaces asARCJET_SIGNALSindecision.reason, so you can branch your response per failure mode. Each rule supportsLIVE/DRY_RUNmodes for safe rollout — shadow it against real traffic, review would-be denials in the Arcjet dashboard, then promote.Because the cookie is only set after the WebAssembly module runs in a real browser, its absence is itself a useful signal. A one-line filter rule (
len(http.request.cookie["aj_signals"]) eq 0) denies anything that never loaded the script — catchingcurl, scripted clients, and headless setups that skip JavaScript beforedetectBotis even evaluated. BotID’s client script issues a per-request browser token instead, with no cross-request signal you can enforce as a filter, and it does not expose filter rules for composing cookie, header, IP, or geo conditions in code alongside your other bot and rate limiting rules.Vercel’s BotID Deep Analysis (powered by Kasada) provides similar in-browser bot detection to Arcjet’s advanced client signals, but it is Pro/Enterprise only and bills $1 per 1,000
checkBotId()calls — at high volumes that can add up quickly. Arcjet advanced signals usage maps todetectBotcalls; adding the signals script to every page (recommended, so the cookie is set before the user reaches a protected route) doesn’t cost anything on its own.
Comparison
Section titled “Comparison”| Area | Arcjet | Vercel BotID |
|---|---|---|
| Features | App-level security SDK: bot management, rate limiting, WAF, email verification, PII detection/redaction, filter rules, and AI Runtime Protection (prompt injection, token budgets, PII in AI flows). | Bot detection/verification. WAF & rate limiting are separate Vercel features. No AI runtime protection. |
| Hosting | Platform-agnostic. Use on any provider/framework. | Only works on Vercel. |
| Local development | Same behavior as production. | Returns isBot: false locally; supports developmentOptions to bypass/override. |
| Integration model | Call from code in any handler. | Client script + server checkBotId(); you must list protected paths |
| HTML forms | Works with native forms and any HTTP request. | Native HTML form POST is not supported. You must refactor your forms. |
| Configuration | Rules-as-code; configure bots to allow by name and category. Returns detailed context you can log/inspect. Filter rules let you compose cookie, header, IP reputation, and geo conditions alongside bot and rate limiting rules using an expression DSL. | Minimal config, no ability to choose which bots to allow or deny, server API essentially returns isBot (boolean) with minimal detail. No filter rule language. |
| Advanced bot signals | Browser-based WebAssembly signal collection: detects headless browsers and sophisticated scrapers. Persistent aj_signals cookie reused across navigations. Composes with server-side bot detection, IP reputation, and a missing-cookie filter rule into a single detectBot decision. LIVE / DRY_RUN rollout modes. ARCJET_SIGNALS denial reason for inspectable responses. | Deep Analysis (Pro/Enterprise, $1 per 1,000 checkBotId() calls): client-side ML script issues a per-request token validated server-side. No persistent browser signal you can enforce as a filter; no rollout mode equivalent to DRY_RUN; result is essentially boolean. |
| AI protection | AI Runtime Protection: bot + prompt injection detection for AI endpoints, per-user token budget enforcement, PII detection in AI flows — all in the same SDK. | No AI runtime protection features. |
| Agentic / non-HTTP | Arcjet Guards: call guard() directly from MCP tool handlers, between agent steps, inside queue consumers, and across multi-service pipelines. Same rule primitives (token bucket, prompt injection, PII detection) as HTTP, same LIVE/DRY_RUN modes. JS/TS via @arcjet/guard; Python via arcjet. | No equivalent. BotID is built around an inbound HTTP request and a verified browser token — it cannot see tool calls, queued jobs, or fan-out work inside the application. |
| Pricing (bot feature) | See the pricing page for details. | Basic analysis: free on all plans. Deep Analysis (powered by Kasada): Pro and Enterprise only, $1 per 1,000 checkBotId() calls. |
When to choose which
Section titled “When to choose which”- Choose Arcjet if you want security independence regardless of where you deploy, need native form support, want rules-as-code with inspectable decisions, have applications built in multiple languages (JavaScript, TypeScript, and/or Python), want to be able to test locally with the same behavior as production, need advanced client signals for headless browser detection without CAPTCHAs, want filter rules to compose cookie/IP/geo conditions with bot and rate limiting rules in code, are building AI applications that need prompt injection detection, per-user token budgets, or PII protection, or are shipping an agent, MCP server, or background pipeline where the risky surface is what happens after the HTTP request enters your app — all in one SDK.
- Choose BotID if you’re fully on Vercel, want a quick boolean bot check, don’t need to make any customizations, and are fine configuring rate limits/WAF separately in the Vercel Firewall.
Does BotID work without Vercel?
Section titled “Does BotID work without Vercel?”No. Vercel BotID requires hosting on Vercel to function. Arcjet is platform-agnostic and can be used with any hosting provider or framework, including running locally.
What about my agent’s tool calls, queue workers, and pipelines?
Section titled “What about my agent’s tool calls, queue workers, and pipelines?”BotID verifies an inbound HTTP request using a browser-issued token. Once a
request is inside your application, anything an agent does next — fetching
a web page and re-feeding it to the model, calling tools in a loop, shipping
work onto a background queue, fanning out across services — is invisible to
BotID. Those are the points where most agent abuse and cost-runaway happens.
Arcjet Guards applies token-bucket
rate limiting, prompt injection detection, and PII detection inline at
those points. You call guard() with the input directly (no Request
needed); rules are the same shape as your HTTP rules, so policy stays
consistent between routes and agent code. Available in JS/TS via
@arcjet/guard and Python via arcjet.
Can I configure which bots to allow or block?
Section titled “Can I configure which bots to allow or block?”With Arcjet, you can customize which bots to allow or block based on your application’s needs. You can allow well-known good bots by name (e.g., Googlebot) and category (e.g. search engines) while blocking malicious ones.
Vercel BotID does not provide configuration options to choose which bots to allow or deny - you only get the details of which bots were detected afterwards.
For example, this Arcjet configuration allows search engine bots but blocks all others:
const aj = arcjet({ key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ // Create a bot detection rule detectBot({ mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only // Block all bots except the following allow: [ "CATEGORY:SEARCH_ENGINE", // Google, Bing, etc // Uncomment to allow these other common bot categories // See the full list at https://arcjet.com/bot-list //"CATEGORY:MONITOR", // Uptime monitoring services //"CATEGORY:PREVIEW", // Link previews e.g. Slack, Discord ], }), ],});Arcjet can then be called from within any request handler and the results inspected:
export async function GET(req: Request) { const decision = await aj.protect(req);
if (decision.isDenied()) { if (decision.reason.isBot()) { return NextResponse.json( { error: "No bots allowed", reason: decision.reason }, { status: 403 }, ); } else { return NextResponse.json( { error: "Forbidden", reason: decision.reason }, { status: 403 }, ); } }
if (decision.ip.isVpn()) { return NextResponse.json( { error: "VPNs are not allowed" }, { status: 403 }, ); }
if (decision.ip.hasCountry() && decision.ip.country === "US") { // Return a custom response for the United States return NextResponse.json({ message: "Hello from the US!" }); }
return NextResponse.json({ message: "Hello world" });}Compare this to Vercel BotID where you can only inspect minimal details about the results:
import { checkBotId } from "botid/server";import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) { const botResult = await checkBotId(); const { isBot, verifiedBotName, isVerifiedBot, verifiedBotCategory } = botResult;
if (isBot) { return NextResponse.json({ error: "Access denied" }, { status: 403 }); }
return NextResponse.json({ message: "Hello world" });}