Astro ships almost no JavaScript by default, which is great for speed but means bot protection has to be deliberate. A small client island loads Prynt, and an Astro API route verifies the result, giving your forms a real visitor ID and bot score.
This guide adds device intelligence to Astro without bloating the page: a client script for identification, and a server endpoint for verification. Because Astro lets you mix static and server-rendered routes, you can keep marketing pages fully static and reserve the small amount of JavaScript for the handful of pages that accept input.
Load the agent in a client script
Astro pages are static by default, so the agent goes in a <script> that runs in the browser. It stays out of your server output entirely.
---
// signup.astro
---
<form id="signup" method="POST" action="/api/signup">
<input name="email" type="email" />
<input type="hidden" name="pryntId" id="pryntId" />
<button>Sign up</button>
</form>
<script>
import Prynt from '@prynt/js'
const agent = await Prynt.load({
apiKey: import.meta.env.PUBLIC_PRYNT_KEY,
endpoint: 'https://api.pryntid.com'
})
document.querySelector('#signup').addEventListener('submit', async () => {
const result = await agent.identify()
document.querySelector('#pryntId').value = result.requestId
})
</script>
The hidden input carries the requestId to your endpoint. The browser only collects the signal.
Verify in an Astro API route
With the node or serverless adapter, an API route verifies the event using your secret key. This is where the decision lives.
// src/pages/api/signup.js
export const POST = async ({ request }) => {
const data = await request.formData()
const id = data.get('pryntId')
const res = await fetch(
`https://api.pryntid.com/v1/events/${id}`,
{ headers: { Authorization: `Bearer ${import.meta.env.PRYNT_SECRET}` } }
)
const event = await res.json()
if (event.bot.result === 'automated') {
return new Response('Blocked', { status: 403 })
}
// store event.visitorId with the account
return new Response('OK')
}
Storing visitorId lets you catch one device farming many signups. The full field set, including proxy and ASN flags, is in the bot detection overview.
If you deploy Astro as a fully static site with no server runtime, you can still capture the event ID in the browser and post it to a separate serverless function or your existing API for verification. The rule never changes: the browser collects, the server decides. Keeping your secret key out of any client bundle is what makes the decision trustworthy, so store it as an environment variable your endpoint reads at request time and never expose it through a PUBLIC_ prefixed value.
Guard the form element
For lower-effort coverage, let Form Shield watch the form and add behavioral signals for scripted fills.
agent.protectForm(document.querySelector('#signup'), {
autoGuard: true,
expectedScripts: ['latin']
})
expectedScripts flags submissions in unexpected writing systems, an easy win against generic spam bots. Combined with server verification, it gives you both a behavioral and an identity signal from a single small script.
Keep it light and truthful
Astro’s whole appeal is minimal client JavaScript, and the agent is small enough not to undo that; load it only on pages with forms or logins. Use the score to branch rather than hard-block, so a flaky signal never locks out a real reader.
Prynt is a managed cloud service, so geolocation, VPN detection, and bot scoring arrive without any backend to run, and the free tier covers a new site’s traffic. Load the agent only where it earns its place, verify on the server, and branch on the result, and you get real protection while keeping the near-zero-JavaScript profile Astro is known for. Add a key, drop in the client script and API route, and check the pricing and free plan to start protecting your Astro forms today.
Try it free
Prynt is device intelligence with a free tier — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics, powered by a cross-site network. Start free.