Form lifecycle management: draft, published, closed. Use when publishing forms, understanding public URLs, configuring captcha, or managing form branding.
Installs into .claude/skills of the current project.
Are you the author of Form Publishing?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/builderio-form-publishing)
---
name: form-publishing
description: >-
Form lifecycle management: draft, published, closed. Use when publishing
forms, understanding public URLs, configuring captcha, or managing form
branding.
---
# Form Publishing
## Form Lifecycle
Forms move through three statuses:
| Status | Meaning | Public access |
| ----------- | ------------------------------------ | ------------- |
| `draft` | Work in progress, not publicly available | No |
| `published` | Live and accepting responses | Yes |
| `closed` | No longer accepting responses | Shows closed message |
## Publishing a Form
```bash
# Create as draft (default)
pnpm action create-form --title "Survey" --fields '[...]'
# Publish when ready
pnpm action update-form --id <form-id> --status published
# Close when done collecting responses
pnpm action update-form --id <form-id> --status closed
```
## Public URLs
Published forms are accessible at:
```
/f/<slug>
```
The slug is auto-generated from the title + a short unique suffix:
- Title: "Contact Form" -> Slug: `contact-form/a1b2c3`
- Full URL: `https://yourapp.com/f/contact-form/a1b2c3`
The slug updates automatically when the title changes.
## Captcha Protection
Public form submissions can be protected with Cloudflare Turnstile (opt-in). This prevents bot submissions without degrading the user experience.
## Branding
Public forms display a "Built with Agent-Native" badge by default. This can be configured in the form settings.
## Form Settings
Each form has a `settings` JSON object:
```json
{
"submitText": "Submit",
"successMessage": "Thank you! Your response has been recorded.",
"redirectUrl": null,
"completionMode": "message",
"completionRefreshSeconds": 5,
"showProgressBar": false,
"emailOnNewResponses": false,
"anonymous": false,
"integrations": []
}
```
| Setting | Type | Description |
| ------------------ | ------- | ------------------------------------------ |
| `submitText` | string | Custom submit button text |
| `successMessage` | string | Message shown after successful submission |
| `redirectUrl` | string | URL to redirect to after submission |
| `completionMode` | string | `message`, `redirect`, `message_then_refresh`, or `refresh` |
| `completionRefreshSeconds` | number | Delay before refreshing for `message_then_refresh` (1–3600) |
| `showProgressBar` | boolean | Show progress bar for multi-section forms |
| `emailOnNewResponses` | boolean | Email the form owner's account when someone submits a response |
| `anonymous` | boolean | Suppress IP, submitter identity, chat/run ids, page URL, and client-surface metadata for every response |
| `integrations` | array | Webhook/Slack/Discord/Google Sheets notification configs |
`emailOnNewResponses` sends through the configured email provider
(`RESEND_API_KEY` / `SENDGRID_API_KEY`). A submission still succeeds when
delivery fails, so check server logs when a notification never arrives.
For a genuinely anonymous form, set `anonymous: true` when creating the form.
Do not describe an ordinary published form as anonymous: published forms accept
public responses, but only anonymous mode suppresses identifying and source
metadata.
Unset `completionMode` keeps legacy behavior: forms with a `redirectUrl` redirect
and all other forms show the success message. A redirect with an invalid URL
falls back to the success message.
## Integration Types
Forms can notify external services on submission:
| Type | Description |
| --------------- | ------------------------------- |
| `webhook` | POST JSON to any URL |
| `slack` | Send to a Slack channel through an Incoming Webhook URL |
| `discord` | Send to a Discord webhook |
| `google-sheets` | Send response JSON to a deployed Apps Script `/exec` URL |
These are outbound form destinations configured in the form builder's
**Integrations** tab. They are separate from the managed Slack/Messaging
connection. Google Sheets Apps Script handlers should parse
`JSON.parse(e.postData.contents)`; a spreadsheet URL or `/dev` URL will not
receive submissions.
## Related Skills
- **form-building** — Creating and structuring forms
- **form-responses** — Viewing data after forms are published