Forms — Tustel Astro
Tustel has one form, the project enquiry on the Contact page, and it works as soon as you run the site.
Visitors leave their name, e-mail, organization, project type, budget, timeline and a message. The form checks the required fields, shows a spinner while sending, says “Thank you” and clears the fields, keeps what the visitor typed when something fails (“Something went wrong”), and drops spam with a hidden honeypot field.
Out of the box it runs in demo mode: nothing is sent, and the fields are logged in the browser console. That’s fine while you build, but useless once you publish, so pick a destination before launch.
The quickest way to receive submissions
Section titled “The quickest way to receive submissions”| You want | Mode | Hosting |
|---|---|---|
| An email for every enquiry, with no server to run | Static: a form service (Formspree, Web3Forms, Getform, Basin) | Any static host |
| Enquiries in your CRM, Slack, a spreadsheet… | Static: a webhook (Zapier, Make, n8n, Pipedream) | Any static host |
| Resend or Mailchimp directly, with no third party in between | Server: the site’s own route (below) | A host with an Astro adapter |
Most sites want static mode: it keeps the site 100% static and takes two minutes.
-
Create a form at your service and copy its endpoint URL (Formspree:
https://formspree.io/f/<id>). -
Copy the example settings and paste the URL:
Terminal window cp .env.example .envPUBLIC_FORM_ENDPOINT=https://formspree.io/f/abcdwxyzWeb3Forms also needs
PUBLIC_FORM_ACCESS_KEY. -
Restart
npm run dev, send a test enquiry, and check that it arrived. -
If your host builds the site, put the same variable in its dashboard before deploying:
.envstays on your computer (it is not in Git). The value is written into the page when the site builds, so rebuild after changing it.
The endpoint is public (it is in the page), which is how these services are designed to work; they filter spam on their side. Details for each service are in Forms reference.
Server mode
Section titled “Server mode”For Resend or Mailchimp without a third-party service, the form posts to a route on your own site:
-
Add the adapter for your host:
npx astro add vercel(ornetlify,node, …). -
Set
PUBLIC_FORM_MODE=server, and choose where enquiries go withFORM_PROVIDER:You want Set An email for every enquiry FORM_PROVIDER=resend+RESEND_API_KEY,FORM_EMAIL_TO,FORM_EMAIL_FROMIt in your CRM, Slack, a spreadsheet… FORM_PROVIDER=webhook+FORM_WEBHOOK_URL(Zapier, Make, n8n, your own API)The sender in a Mailchimp audience FORM_PROVIDER=mailchimp+MAILCHIMP_API_KEY,MAILCHIMP_AUDIENCE_IDThe server log only, while you test FORM_PROVIDER=console(the default)Several at once work:
FORM_PROVIDER=resend,mailchimp. -
Restart
npm run devand send a test enquiry. The form now posts to/api/forms/contact(src/lib/forms/route.ts, added bysrc/integrations/forms.ts); every page stays static, only that route runs on the server. -
Put the same variables in your host’s dashboard before deploying, and redeploy after changing them.
Details for each provider, the webhook payload, Mailchimp consent settings and how to write your own provider are in Forms reference.
What the form asks for
Section titled “What the form asks for”| Field | Name it arrives under | Required |
|---|---|---|
| Name | name |
yes |
email |
yes | |
| Organization | organization |
|
| Project Type (Photography, Brand Identity, Web Design, Creative Direction, Art Direction, Other) | project |
yes |
| Estimated Budget (Under $2,000, $2,000 – $5,000, $5,000 – $10,000, $10,000+) | budget |
yes |
| Timeline | timeline |
|
| Message | message |
Each submission carries the title “New project enquiry”, so you can tell it apart in your inbox or tool: a form
service or webhook receives it as subject, with form (contact) next to the fields. In server mode, Resend puts
the sender’s name after it in the email’s subject and sets the reply address to the sender’s e-mail; Mailchimp stores
the name as FNAME, tags the contact with the title and keeps the rest as a note; a webhook receives every field, as
"" when left empty, with its label.
Changing the form
Section titled “Changing the form”The labels, placeholders, the choices of the two selects, the note next to the button (“All fields marked with * are
required.”), the button texts (“Send message”, “Thank you”, “Something went wrong”) and the title of each submission
(notification) are content: contactForm in src/content/pages/contact.json. The note’s asterisk is drawn in
orange; keep one in the text.
A field arrives at your service under its name, so keep the names you already map somewhere.
The form shows the fields of contactForm.fields in the order they are listed: two columns from tablets up (one on
phones), with the message across both. It sends every field in that list, and in server mode
src/lib/forms/definitions.ts reads the same list to check the required ones again, so the list is the only place to
edit:
-
To make a field required, set
"required": trueon it. Its label gets an orange asterisk, the browser checks it before sending and, in server mode, the route checks it again. -
To add a field, add an entry (mind the comma between entries), for example a phone number:
"phone": { "name": "phone", "label": "Phone", "placeholder": "+1 555 0100", "type": "tel", "autoComplete": "tel" }It appears in the form and arrives with the others. The types are
text(the default),email,tel,date,select(withoptions; the placeholder is its empty first choice) andtextarea, which takes the full width. -
To remove a field, delete its entry. The style guide’s sample form shows the
nameandprojectfields:npm run checknames the style guide if you remove one of them.
Testing before launch
Section titled “Testing before launch”- Submit the form on your own site after deploying, with a real address.
- Check the destination (inbox, spreadsheet, audience) and reply to the test.
- Try a failure on purpose: point
PUBLIC_FORM_ENDPOINTat a wrong URL in a test build (or set a wrong API key in server mode), submit, and confirm the visitor sees “Something went wrong” and their text is still there.
The honeypot stops the usual bots: a bot that fills the hidden field is shown “Thank you” and nothing is sent. If you
start getting spam anyway, use a form service in static mode (Formspree, Web3Forms and Basin all include spam
filtering), or add a captcha to the form component (src/design-system/components/forms/Form.astro).
Privacy
Section titled “Privacy”Submissions go only where you point them; nothing is sent anywhere else. Most countries expect you to say what happens
to the data people send you. Tustel has no privacy policy page and no consent checkbox: if you need a policy, add a page
for it (Pages and sections) and mention it in the Contact page’s text (contactPage.text).
The form doesn’t ask for marketing consent, so if you send enquiries to Mailchimp, keep MAILCHIMP_STATUS at
transactional (the default): those contacts can get your replies, not your campaigns.