---
title: "Astro"
description: "Send an Astro contact form to FormParcel. A plain form posts to your address and lands on your thank-you page, with an optional script that submits with fetch."
url: "https://formparcel.live/docs/guides/astro"
---

# Astro

Send an Astro contact form to FormParcel. A plain form posts to your address and lands on your thank-you page, with an optional script that submits with fetch.

Untested: Not yet tested on a live site. The steps follow how the form address works today; if one differs, [tell us](https://formparcel.live/contact).

## Before you start

You need a form address from the dashboard. It looks like `https://f.formparcel.live/f/f_k7QmXp2aVb9sTn4cLw8RhE`, and your own ID replaces the last part. If you have not made one yet, start with [the quickstart](https://formparcel.live/docs/quickstart). The form needs no server, so it works on a fully static Astro site.

## The form

Astro renders HTML, so the form is ordinary HTML that posts to FormParcel. Add a thank-you page to your site, and point `_next` at it.

Example form address. Use yours from the dashboard.

```html title="src/pages/contact.astro"
---
// src/pages/contact.astro
const endpoint = "https://f.formparcel.live/f/f_k7QmXp2aVb9sTn4cLw8RhE";
---
<form action={endpoint} method="POST" onsubmit="if(!window.FormParcel){event.preventDefault();var f=this;f.setAttribute('data-fp-wait','1');setTimeout(function(){if(f.getAttribute('data-fp-wait')){f.removeAttribute('data-fp-wait');HTMLFormElement.prototype.submit.call(f)}},10000)}">
  <!-- Spam trap: people never see it, bots fill it in. Keep it hidden. -->
  <div aria-hidden="true" style="position:absolute;left:-10000px;">
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" />
  </div>

  <!-- Send visitors to a page on your own site after they submit. -->
  <input type="hidden" name="_next" value="https://your-site.example/thanks" />

  <label for="name">Name</label>
  <input id="name" name="name" required />
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required />
  <label for="message">Message</label>
  <textarea id="message" name="message" required></textarea>
  <button type="submit">Send</button>
</form>
<!-- FormParcel bot check: no cookies, no widget. Without it, submissions wait in your Spam folder. -->
<script is:inline src="https://f.formparcel.live/f/formparcel.js" async></script>
```

- `_next` must be a page on the client’s own site, and the site’s domain must be set in FormParcel. Otherwise the visitor sees a short FormParcel confirmation page. The trust rule is on [special fields](https://formparcel.live/docs/reference/fields#redirects).
- Keep the `_gotcha` field hidden. It is the spam trap: a real visitor never fills it in.
- Keep the script line after the form. It is FormParcel’s bot check, and `is:inline` makes Astro leave it as it is. Without it, leads wait in your Spam folder. See [Spam protection](https://formparcel.live/docs/setup/spam-protection#the-bot-check).
- Every field needs a `name`. Each name becomes a column in the Sheet.

> **Bot check.** A lead sent without FormParcel's bot check is saved but held in your Spam folder, not delivered. That is every post from a server, a webhook or `curl`, and every post from a page that does not load the script. The dashboard then shows **Bot check not installed** under Needs action. Fix: keep the script line on every page that has the form. [Learn more](https://formparcel.live/docs/reference/bot-check)

## Optional: submit with fetch

If you would rather keep the visitor on the page, add a small script that submits with fetch and shows a message. The form still works without it. Keep the bot check’s script line: the script asks it for a proof before each send.

```html title="In the same file"
<form id="contact" action="https://f.formparcel.live/f/f_k7QmXp2aVb9sTn4cLw8RhE" method="POST">
  <!-- same fields as above, including the spam trap and _next -->
</form>
<p id="contact-status" role="status"></p>
<!-- Keep the bot check's script line from above. -->

<script>
  declare global {
    interface Window {
      FormParcel?: { proof: (endpoint: string) => Promise<string> };
    }
  }
  const form = document.querySelector<HTMLFormElement>("#contact");
  const status = document.querySelector<HTMLElement>("#contact-status");

  form?.addEventListener("submit", async (event) => {
    // formparcel.js may hold a post until its proof is ready and then send the form again: let that send through here.
    if (event.defaultPrevented) return;
    event.preventDefault();
    event.stopImmediatePropagation();
    // One proof per submission. The bot check may still be loading on a slow phone, so wait for it (10 seconds at most). If it cannot load, the lead is still sent and waits in your Spam folder.
    const fp = window.FormParcel ?? (await new Promise<Window["FormParcel"]>((ok) => { addEventListener("formparcel:ready", () => ok(window.FormParcel), { once: true }); setTimeout(() => ok(undefined), 10000); }));
    const _fp_proof = await fp?.proof(form.action).catch(() => undefined);
    const response = await fetch(form.action, {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify({ ...Object.fromEntries(new FormData(form)), _fp_proof }),
    });
    // 204: the post was dropped as spam (the trap field was filled). Nothing was saved.
    const result = response.status === 204 ? null : await response.json();
    if (status) status.textContent = result?.accepted ? "Thanks, we got your message." : "Your message was not sent.";
    if (result?.accepted) form.reset();
  });
</script>
```

- With fetch the answer is JSON. `deliveryStatus: "pending"` still means the lead was accepted: do not send it again. Every status is on [the fetch guide](https://formparcel.live/docs/guides/fetch).
- If you set allowed origins on the form, add your site’s host, and `localhost` while you test locally.

## Check it

Submit the form once and open its receipt in the dashboard under **Leads**. If nothing arrives, go through [Troubleshooting](https://formparcel.live/docs/help/troubleshooting).
