Skip to content

Contact form

The Contact section’s form has no JavaScript by design — it submits to your chosen form provider, who handles the email, spam filtering, and (with most providers) sends you a notification. You set the provider once in site.ts.

src/config/site.ts
contactForm: {
provider: "formspree" as "formspree" | "formsubmit" | "netlify" | null,
formspreeId: "your-form-id",
// formsubmitEmail: "you@example.com",
// Netlify: just set provider to "netlify" — no extra fields.
}

If provider is null (or missing required fields), the form falls back to a mailto: action so it still does something useful.

Free tier: 50 submissions/month. No domain restriction.

  1. Sign up at formspree.io.
  2. Create a new form. Copy the form ID — it’s the last segment of the action URL: https://formspree.io/f/xyzabc123xyzabc123.
  3. Set:
contactForm: {
provider: "formspree",
formspreeId: "xyzabc123",
}

The rendered form:

<form name="contact" method="POST" action="https://formspree.io/f/xyzabc123">
<!-- your fields -->
</form>

Submissions land in your Formspree dashboard and are emailed to the address on the form’s settings page.

Free, no signup. Just your email address.

  1. Set:
contactForm: {
provider: "formsubmit",
formsubmitEmail: "you@yourdomain.com",
}
  1. Submit the form once with any data — FormSubmit will email you a confirmation link to verify ownership. Click it. After that, real submissions deliver to your inbox.

The rendered form:

<form name="contact" method="POST" action="https://formsubmit.co/you@yourdomain.com">

Only works when you deploy to Netlify. Free up to 100 submissions/month/site.

  1. Set:
contactForm: { provider: "netlify" }
  1. Deploy to Netlify. The first deploy registers the form.
  2. Submissions appear in your Netlify dashboard → Forms tab.

The rendered form (Netlify-specific):

<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
<!-- your fields -->
</form>

The data-netlify="true" attribute and the hidden form-name input are what Netlify’s build pipeline scans for.

contactForm: { provider: null }

Falls back to a mailto: action — clicking submit opens the user’s email client with the message body prefilled. Not great UX, but the form isn’t broken.

Open src/components/Contact.astro. The frontmatter resolves the provider into the right action / method / hidden inputs at build time. The form HTML it emits is plain — no client JS, works without JavaScript enabled.

src/components/Contact.astro (excerpt)
if (cf.provider === "formspree" && cf.formspreeId) {
action = `https://formspree.io/f/${cf.formspreeId}`;
} else if (cf.provider === "formsubmit" && cf.formsubmitEmail) {
action = `https://formsubmit.co/${cf.formsubmitEmail}`;
} else if (cf.provider === "netlify") {
netlify = true;
} else {
action = `mailto:${contactEmail}`;
method = "GET";
}

If you use Web3Forms, Basin, getform.io, or something else — edit Contact.astro and add a new branch:

} else if (cf.provider === "web3forms" && cf.web3formsAccessKey) {
action = "https://api.web3forms.com/submit";
hiddenAccessKey = cf.web3formsAccessKey;
}

…then add the new option to the provider union in site.ts.