Forms reference
The Contact form and the career application form work in one of three ways. You choose with environment variables
(copy .env.example to .env, or set them in your host’s dashboard); no code changes.
| Mode | Set | Hosting | Where submissions go |
|---|---|---|---|
| Demo (default) | nothing | any static host | Nowhere: the form shows “Thank you” and logs the fields in the browser console. |
| Static | PUBLIC_FORM_ENDPOINT |
any static host | Posted from the browser to a form service or webhook. |
| Server | PUBLIC_FORM_MODE=server + FORM_PROVIDER |
a host with an Astro adapter | Delivered by the site’s own route to console, webhook, Mailchimp or Resend. |
Every mode shows the same states on the Submit button (spinner, “Thank you”, “Something went wrong”), keeps the typed values after an error, resets the form after success and drops spam caught by the hidden honeypot field.
Static mode: form services and webhooks
Section titled “Static mode: form services and webhooks”Set PUBLIC_FORM_ENDPOINT to the URL the service gives you. The forms post multipart/form-data with
Accept: application/json, and the service must answer with a 2xx status.
| Service | PUBLIC_FORM_ENDPOINT |
Notes |
|---|---|---|
| Formspree | https://formspree.io/f/<form id> |
Emails you each submission. |
| Web3Forms | https://api.web3forms.com/submit |
Also set PUBLIC_FORM_ACCESS_KEY. |
| Getform, Basin | the form’s endpoint URL | |
| Zapier, Make, n8n, Pipedream | the webhook (“catch hook”) URL | Route submissions anywhere: Mailchimp, Google Sheets, Slack, a CRM. |
Fields sent: every input by its name (firstName, lastName, email, phone, services — once per checked
option — message; applications also position, linkedin, otherSocial, website), plus form (contact or
application) and subject (e.g. “New contact enquiry”).
The endpoint is public (it is in the page), which is how these services are designed to work; they filter spam themselves.
Server mode: your own route
Section titled “Server mode: your own route”For Mailchimp or Resend without a third-party service, run the forms on your server:
- Add the adapter for your host:
npx astro add vercel(ornetlify,node, …). - Set
PUBLIC_FORM_MODE=serverand choose providers withFORM_PROVIDER:
FORM_PROVIDER |
What happens | Variables |
|---|---|---|
console (default) |
Printed to the server log. | — |
webhook |
Posted as JSON to a URL. | FORM_WEBHOOK_URL, optional FORM_WEBHOOK_SECRET (sent as Authorization: Bearer …) |
mailchimp |
Sender added or updated in an audience, tagged with the form and chosen services, the rest saved as a note. | MAILCHIMP_API_KEY, MAILCHIMP_AUDIENCE_ID, optional MAILCHIMP_STATUS |
resend |
Emailed to you; replying answers the sender. | RESEND_API_KEY, FORM_EMAIL_TO, FORM_EMAIL_FROM |
Use several at once with a comma: FORM_PROVIDER=resend,mailchimp. The forms then post to POST /api/forms/contact and
/api/forms/application (src/lib/forms/route.ts, added by src/integrations/forms.ts). Without an adapter the build
stops with NoAdapterInstalled. Astro rejects cross-site posts to the route, so only your own pages can submit.
Mailchimp notes
Section titled “Mailchimp notes”- API key: Profile → Extras → API keys. It ends in your data center (
…-us21). - Audience ID: Audience → Settings → Audience name and defaults.
- Consent: new contacts are added as
transactional— they can receive replies, not campaigns. Usependingto send a double opt-in email, orsubscribedonly if the form asks for marketing consent. - Merge fields: first name →
FNAME, last name →LNAME, phone →PHONE. Change the map at the top ofproviders/mailchimp.tsif your audience uses other tags.
Your own provider
Section titled “Your own provider”Create src/lib/forms/providers/<name>.ts exporting a FormProvider ({ name, send(submission) } — throw to show the
error state), register it in providers/index.ts and set FORM_PROVIDER=<name>. Helpers in types.ts:
valueOf(submission, name), toLines(submission), env(name).
Changing a form
Section titled “Changing a form”- Fields and labels live in the page copy:
src/content/pages/contact.jsonandsrc/content/pages/career.json(nameis the key services receive,labelis what people read). - Required fields for server mode are listed in
src/lib/forms/definitions.ts; keep them in step with therequiredflags in the copy, which the browser checks first.