diff --git a/content/collections/fieldtypes/form.md b/content/collections/fieldtypes/form.md index 741e08936..6b28ddaba 100644 --- a/content/collections/fieldtypes/form.md +++ b/content/collections/fieldtypes/form.md @@ -22,7 +22,7 @@ options: name: query_scopes type: string description: > - Allows you to specify a [query scope](/extending/query-scopes-and-filters#scopes) which should be applied when retrieving selectable assets. You should specify the query scope's handle, which is usually the name of the class in snake case. For example: `MyAwesomeScope` would be `my_awesome_scope`. + Allows you to specify a [query scope](/extending/query-scopes-and-filters#scopes) which should be applied when retrieving selectable forms. You should specify the query scope's handle, which is usually the name of the class in snake case. For example: `MyAwesomeScope` would be `my_awesome_scope`. related_entries: - fdb45b84-3568-437d-84f7-e3c93b6da3e6 - aa96fcf1-510c-404b-9b63-cea8942e1bf8 @@ -31,6 +31,8 @@ related_entries: The Form fieldtype is gives your users a way to pick a form to include along with the current entry. How that form is implemented or shows up on the page is up to you. +Not to be confused with [form fieldtypes](/forms#form-fieldtypes) — the fields you add _inside_ a form when building it. This fieldtype is for selecting an entire form from elsewhere, like an entry. + ## Data Storage The Form fieldtype stores the `handle` of a single form as a string, or an array of handles if `max_items` is greater than 1. @@ -45,4 +47,20 @@ The Form fieldtype provides a few useful variables: * `api_url` * `honeypot` -You can use the [`form:create`](/tags/form-create) tag to render a `
` on your page. +You can pass the `handle` to the [`{{ form:create }}`](/tags/form-create) tag to render a `` on your page: + +::tabs + +::tab antlers +```antlers +{{ form:create :in="form_fieldtype:handle" }} + ... +{{ /form:create }} +``` +::tab blade +```blade + + ... + +``` +:: diff --git a/content/collections/pages/conditional-fields.md b/content/collections/pages/conditional-fields.md index c4b077334..eb197d685 100644 --- a/content/collections/pages/conditional-fields.md +++ b/content/collections/pages/conditional-fields.md @@ -382,4 +382,4 @@ For more advanced conditional validation, take a look at Laravel's `required_if` ## Templating -You can take advantage of Conditional Fields on your front-end Forms to automatically generate dynamic forms and logic. [Learn more about it](/tags/form-create#conditional-fields). +You can take advantage of Conditional Fields on your front-end Forms to automatically generate dynamic forms and logic. [Learn more about it](/tags/form-create#logic-conditional-fields). diff --git a/content/collections/pages/events.md b/content/collections/pages/events.md index 36ffbd45a..d1175b24a 100644 --- a/content/collections/pages/events.md +++ b/content/collections/pages/events.md @@ -552,6 +552,18 @@ public function handle(FormBlueprintFound $event) } ``` +### FormCreated +`Statamic\Events\FormCreated` + +Dispatched after a form has been created. + +``` php +public function handle(FormCreated $event) +{ + $event->form; +} +``` + ### FormCreating `Statamic\Events\FormCreating` @@ -1131,7 +1143,7 @@ public function handle(StaticCacheCleared $event) ### SubmissionCreated `Statamic\Events\SubmissionCreated` -Dispatched after a form submission has been created. This happens after a form has been submitted on the front-end. +Dispatched after a form submission (including [partial submissions](/repositories/form-submission-repository#partial-submissions)) has been created. This happens after a form has been submitted on the front-end. ``` php public function handle(SubmissionCreated $event) @@ -1145,7 +1157,7 @@ If you're looking to prevent a form being submitted or trigger validation errors ### SubmissionCreating `Statamic\Events\SubmissionCreating` -Dispatched before a submission is created. You can return `false` to prevent it from being created. +Dispatched before a submission (including [partial submissions](/repositories/form-submission-repository#partial-submissions)) is created. You can return `false` to prevent it from being created. ``` php public function handle(SubmissionCreating $event) @@ -1154,6 +1166,20 @@ public function handle(SubmissionCreating $event) } ``` +### SubmissionFinalized +`Statamic\Events\SubmissionFinalized` + +Dispatched when a submission is finalized — either when a regular form is submitted, or when the final page of a multi-page form is submitted and its [partial submission](/repositories/form-submission-repository#partial-submissions) becomes complete. + +Unlike `SubmissionCreated`, this event never fires for partial submissions, making it the best place to hook in logic that should only run once a submission is truly complete. + +``` php +public function handle(SubmissionFinalized $event) +{ + $event->submission; +} +``` + ### SubmissionDeleted `Statamic\Events\SubmissionDeleted` diff --git a/content/collections/pages/forms.md b/content/collections/pages/forms.md index ea3b6f7c2..2629e4843 100644 --- a/content/collections/pages/forms.md +++ b/content/collections/pages/forms.md @@ -7,6 +7,13 @@ intro: 'Forms are a natural part of the internet experience and a core component related_entries: - e4f4f91e-a442-4e15-9e16-3b9880a25522 --- + +Forms comes in two flavours: built-in and [Forms Pro](#forms-pro). + +If you’re building a typical contact form, RSVP, or similar, the built-in Forms feature will probably be all you need. It’s included with Statamic core, so there’s nothing extra to install or purchase. + +If you need [more](https://youtu.be/uNy_MLr8mXA?si=66HfGKi1asv6PmgM&t=13), Forms Pro is a separate paid add-on with advanced features like third-party integrations, enhanced workflows, and other powerful tools. You can [try it out for free locally](#installation). + ## Overview Statamic forms collect submissions, provide reports on them on aggregate, and display user submitted data on the [frontend](/frontend). The end-to-end solution includes tags, settings, and a dedicated area of the Control Panel. @@ -19,78 +26,34 @@ Okay, let's just pretend you're a famous celebrity's _web developer_. You've bee - name - age -- level of adoration (appreciation, fixation, or obsession) +- level of adoration - message ### Create the form First, head to `/cp/forms` in the Tools area of the Control Panel and click the **Create Form** button. Alternately you can create a `.yaml` file in `resources/forms` which will contain all the form's settings. -Each form should contain a title. Optionally it may also have an email configuration. +Each form should contain a title. ```yaml title: Super Fans -email: [] -``` - -### The blueprint - -The [blueprint](blueprints) is where you define your form's `fields` and validation rules to be used on form submission. - -The blueprint is located in `resources/blueprints/forms/{handle}.yaml` - -```yaml -fields: - - - handle: name - field: - display: Name - type: text - validate: required - - - handle: age - field: - display: Age - type: text - validate: required|integer - - - handle: adoration - field: - display: Level of Adoration - type: text - validate: required - - - handle: comment - field: - display: Comment - type: textarea - validate: required ``` -:::warning -The `message` variable is a Laravel reserved word within this email context, so you should avoid using that as a field handle if you intend on using the email feature. +:::tip +Statamic Core allows you to create a single form. To create any more, either enable [Statamic Pro](/getting-started/licensing) or install [Forms Pro](#forms-pro). ::: -If you use the Control Panel to build your blueprint, you will find that there's only a subset of fieldtypes available to you. -These are the fields that have corresponding views ready to be used on the front-end. +### Add your fields -If you'd like to include more fieldtypes, you can opt into each one by calling `makeSelectableInForms` on the respective class within a service provider: +With your form created, you can start adding fields using the **Form Builder** in the Control Panel. Each field you add is a [form fieldtype](#form-fieldtypes) — like a short answer, dropdown, or file upload — and you can configure its display name, validation rules, and [logic](/conditional-fields) without leaving the Control Panel. -```php -Statamic\Fieldtypes\Section::makeSelectableInForms(); -``` - -You can also do the opposite and prevent a fieldtype from being used in forms by calling `makeUnselectableInForms` on the respective class within a service provider: - -```php -Statamic\Fieldtypes\Dictionary::makeUnselectableInForms(); -``` +For our EF-Mail form, you'd add a Name field for the name, a Number for the age, a Star Rating for the level of adoration, and a Long Answer for the message. ### The template Several [tags](/tags/form) are provided to help you manage your form. You can explore these at your leisure, but for now here's a look at a basic form template. -This example dynamically renders each input's HTML. You could alternatively write the HTML yourself, perform conditions on the field's `type`, or even [customize the automatic HTML](/tags/form-create#dynamic-rendering). +This example loops over your form's fields and dynamically renders each input's HTML, so you don't need to hardcode field handles. You could alternatively write the HTML yourself, perform conditions on the field's `type`, or even [customize the automatic HTML](/tags/form-create#pre-rendered-field-html). ::tabs @@ -171,13 +134,144 @@ This example dynamically renders each input's HTML. You could alternatively writ // This is just a submit button. @endif + ``` :: +## Form fieldtypes + +When building a form, you add fields using **form fieldtypes**. These are purpose-built versions of Statamic's [fieldtypes](/fieldtypes), designed specifically for collecting input from your visitors and rendering on the front-end. Each one comes with a corresponding front-end view, so it's ready to drop into your templates. + +The following form fieldtypes are available: + +| Fieldtype | Description | +|---|---| +| Group | Group related fields together. | +| Spacer | Add visual spacing between form fields. | +| Heading | A heading to organize your form. | +| Paragraph | A paragraph to provide information in your form. | +| Banner | A banner to highlight important information in your form. | +| Short Answer | A simple field for short, one-line answers. | +| Long Answer | A larger field for detailed responses, comments, or messages. | +| Dropdown | A dropdown list where respondents pick from your options. | +| Yes / No | A simple yes or no question. | +| Multiple Choice | A question with a range of answer options. Respondents can only choose one answer. | +| Checkboxes | Respondents can select multiple options from a list. | +| Image Choice | An image-based choice selector for visual options. | +| Toggle | A simple yes or no switch. | +| Dictionary | Select from a predefined list like countries, timezones, or currencies. | +| Opinion Scale | An opinion scale for measuring agreement or satisfaction. | +| Star Rating | A star rating input for collecting ratings. | +| Ranking | A ranking input for ordering items by preference. | +| Name | Collects someone's name. | +| Email | Collects an email address and ensures it's properly formatted. | +| Website | Collects a website address and ensures it's properly formatted. | +| Phone | A field for collecting phone numbers. | +| Number | Collects a number. You can set minimum and maximum values. | +| Currency | Collects a monetary amount. | +| Date Picker | Lets respondents pick a date. | +| Time Picker | Lets respondents pick a time of day. | +| Upload | Lets respondents upload one or more files. See [File uploads](#file-uploads). | + +:::tip +Click a fieldtype in the Form Builder to see an example of what it looks like. +::: + +### Customizing the front-end views + +Each form fieldtype renders using a publishable view. To customize the HTML, run `php artisan vendor:publish --tag=statamic-forms`, which exposes editable templates in your `resources/views/vendor/statamic/forms/fields` directory. + +:::tip +Publishable views are implemented in Antlers by default. Blade versions will be used instead if your `statamic.templates.language` config is set to `blade`. +::: + +### Controlling which fieldtypes are selectable + +Most form fieldtypes are selectable in the Form Builder by default. You can change this from a service provider by calling `makeSelectable()` or `makeUnselectable()` on the relevant fieldtype class: + +```php +use Statamic\Forms\Fieldtypes\Dictionary; + +public function boot() +{ + Dictionary::makeUnselectable(); +} +``` + +### Building a custom form fieldtype + +Each form fieldtype wraps a regular [fieldtype](/fieldtypes) and tailors it for use in forms. + +You can build your own by creating a class in the `app/FormFieldtypes` directory that extends `Statamic\Forms\Fields\FormFieldtype`. Statamic will discover it automatically. + +```php + ['display' => 'Minimum', 'type' => 'integer'], + 'max' => ['display' => 'Maximum', 'type' => 'integer'], + ]; + } + + public function toFieldArray(): array + { + return [ + 'type' => 'range', + 'min' => $this->config('min'), + 'max' => $this->config('max'), + ]; + } + + public function example(): ?array + { + return [ + 'config' => [ + 'display' => 'How confident are you about your answer?', + 'min' => 1, + 'max' => 5, + ], + 'value' => 3, + ]; + } +} +``` + +#### Properties + +| Property | Description | +| --- | --- | +| `$fieldtype` | The handle of the regular fieldtype this form fieldtype wraps. | +| `$description` | A short description shown in the Form Builder. | +| `$categories` | The categories the fieldtype is grouped under in the Form Builder. Should be an array. | +| `$icon` | The icon shown in the Form Builder. | +| `$order` | Controls the fieldtype's position within its category. | + +#### Methods + +| Method | Description | +| --- | --- | +| `configFieldItems()` | The configuration fields shown when editing the field in the Form Builder. | +| `toFieldArray()` | Translates the form fieldtype's config into a regular field definition. | +| `example()` | An example configuration used to preview the field in the Form Builder. Optional. | +| `view()` | The front-end view used to render the field. Optional. | + ## Viewing submissions -In the Forms area of the control panel you can explore the collected responses, configure dashboards and export the data to CSV or JSON formats. +In the Forms area of the Control Panel you can explore the collected responses and export the data to CSV or JSON formats.
List of form submissions in the control panel @@ -185,6 +279,17 @@ In the Forms area of the control panel you can explore the collected responses,
Forms. Submissions. Features.
+When running a [multi-site](/multi-site), you'll only see submissions submitted from the current site. You may remove the filter to see submissions from all sites you have access to. + +### Generating fake submissions + +To help test how your submissions display on the front-end with the [form submissions](/tags/form-submissions) tag, you can generate fake submissions populated with realistic data. From the submissions listing, use the **Generate Fake Submission** button: + +- **Generate Fake Submission** creates a submission directly, without firing events or sending [connections](#connections). +- **Generate Fake Submission + Run All Workflows** runs the submission through the full pipeline, firing events and sending any configured [connections](#connections) — handy for testing those too. + +Fake submissions are flagged so you can tell them apart and bulk-delete them when you're done. If you'd like to disable fake submission generation for a form, you can turn it off in the form's settings. + ## Displaying submission data You can display any or all of the submissions of your forms on the front-end of your site using the [form submissions][submissions] Tag. @@ -313,28 +418,35 @@ Then, to make the exporter available on your forms, simply add it to your forms ], ``` -## Emails +## Connections -Allowing your fans to send their comments is all well and good, but at this point you will only know about it when you head back into the Control Panel to view the submissions. Wouldn't it be better to get notified? Let's hook that up next. +Out of the box, you'll only know about a submission when you head into the Control Panel to view it. Most of the time you'll want something to happen when a form is submitted — like being notified by email. -You can add any number of emails to your formset. +**Connections** let you send submissions off to other places. Statamic ships with Email and Webhook connections out of the box. -```yaml -email: - - - to: hello@celebrity.com - from: website@celebrity.com - subject: You've got fan mail! - html: fan-mail - text: fan-mail-text - - - to: agent@celebrity.com - subject: Someone still likes your client -``` +You'll find them in the **Connect** area of your form in the Control Panel, along with how many of each are configured. + +_TODO: Screenshot of the Connect area_ + +### Email + +The Email connection sends emails whenever the form is submitted. You can add any number of emails to your form, each with their own settings. + +The Recipient, CC, BCC, Sender and Reply-To fields suggest your form's fields — so you can send the email to whatever address the visitor submitted — and you can type email addresses in directly (including the `Jack Black ` syntax) to send to fixed addresses. + +Each email can be sent for every submission, or only when the submission matches a set of conditions — like only sending the "sales" email when the visitor picked "Sales" from your enquiry type field. -Here we'll send two emails for every submission of this form. One will go to the celebrity, and one to the agent. The first one uses custom html and text views while the other doesn't, so it'll get an "automagic" email. The automagic email will be a simple text email with a list of all fields and values in the submission. +_TODO: Screenshot of configuring an email_ -### Email variables +#### The email body + +You can write the email body directly in the Control Panel — no need to create any views. Use `@` to insert your form's fields into the body. + +If you'd rather have full control over the email's design, you can specify custom HTML and Text views instead. To output the written body inside your view, use `{{ email_config:body }}`. When there's no body and no views, Statamic will send an "automagic" email — a simple text email with a list of all the fields and values in the submission. + +[Learn how to create your emails](/email) + +#### Email variables Inside your email view, you have a number of variables available: @@ -342,7 +454,7 @@ Inside your email view, you have a number of variables available: - `site_url` - The site home page. - `site`, `locale` - The handle of the site - `config` - Any app configuration values -- `email_config` - The email's config (the current item from your `email:` array) +- `email_config` - The email's config - `form_config` - Any extra config values appended to the form's blueprint (e.g. via addons using `Form::appendBlueprintTab()`) - Any data from [Global Sets](/globals#global-sets) - All of the submitted form values @@ -391,86 +503,37 @@ In each iteration of the `fields` array, you have access to: - `config` - The configuration of the blueprint field -### Setting the from and reply-to name +#### Setting the from and reply-to name -You can set a full "From" and "Reply-To" name in addition to the email address using the following syntax: +You can set a full "From" and "Reply-To" name in addition to the email address by typing it into the Sender or Reply-To fields using the following syntax: ``` -from: 'Jack Black ' -reply_to: 'Jack Black ' +Jack Black ``` +#### Dynamic recipients and subjects -### Setting the recipient dynamically +The address fields suggest your form's fields — select one and the address the visitor submitted will be used when the email is sent. For example, selecting your "Email Address" field as the Reply-To means you can hit reply in your inbox to respond directly to the visitor. -You can set the recipient to an address submitted in the form by using the variable in your config block. Assuming you have a form input with `name="email"`: +The Subject field supports Antlers, so you can reference submitted values there too using their field handles: -```yaml -email: - - - to: "{{ email }}" - # other settings here ``` - -### Setting the "Reply to" dynamically - -You can set the "reply to" to an address submitted in the form by using the variable in your config block. Assuming you have a form input with `name="email"`: - -```yaml -email: - - - reply_to: "{{ email }}" - # other settings here +{{ subject ?? "Email Form Submission" }} ``` -### Setting the "Subject" dynamically +#### Attachments -You can set the email "subject" to a value in your form by using the variable in your config block. Assuming you have a form input with `name="subject"`: +When using [file uploads](#file-uploads) in your form, you may choose to have those attached to the email by enabling the **Attachments** toggle. -```yaml -email: - - - subject: '{{ subject ?? "Email Form Submission" }}' - # other settings here -``` - -[Learn how to create your emails](/email) +If you don't want the attachments to be kept around on your server, configure your [Upload field](#file-uploads) so it doesn't store the files — they'll be attached to the email and then deleted. -### Attachments - -When using [file uploads](#file-uploads) in your form, you may choose to have those attached to the email. By adding `attachments: true` to the email config, any `assets` fields will be automatically attached. - -```yaml -email: - - - attachments: true - # other settings here -``` - -If you don't want the attachments to be kept around on your server, you should pick the `files` fieldtype option explained in the [File Uploads](#file-uploads) section. - -### Using Markdown Mailable Templates +#### Using Markdown Mailable Templates Laravel allows you to create email templates [using Markdown](https://laravel.com/docs/mail#markdown-mailables). It's pretty simple to wire these up with your form emails: -1. Enable Markdown parsing in your email config: - -```yaml -email: - - - # other settings here - markdown: true # [tl! add] -``` - -2. Next, create a **Blade** view for your email template and start using Laravel's Markdown Mailable components: +1. Enable the **Markdown** toggle when configuring the email. -```yaml -email: - - - # other settings here - markdown: true - html: 'contact-us' # [tl! add] -``` +2. Next, create a **Blade** view for your email template, select it as the **HTML view**, and start using Laravel's Markdown Mailable components: ```blade {{-- contact-us.blade.php --}} @@ -491,57 +554,178 @@ Someone has taken the time to fill out a form on your website. Here are the deta Make sure you don't use indentation in your Markdown view. Laravel's markdown parser will render it as code. ::: -You can customize the components further by reviewing the [Laravel documentation](https://laravel.com/docs/13.x/mail#customizing-the-components). +You can customize the components further by reviewing the [Laravel documentation](https://laravel.com/docs/mail#customizing-the-components). -## File uploads +### Webhooks -Sometimes your fans want to show you things they've created, like scissor-cut love letters and innocent selfies with cats. No problem! File input types to the rescue. Inform Statamic you intend to collect files, specify where you'd like the uploads to go, and whether you'd like them to simply be placed in a directory somewhere, or become reusable Assets. +The Webhook connection sends a POST request to a URL of your choice whenever the form is submitted. You can add any number of webhooks to your form and, like emails, each webhook can be sent for every submission or based on conditions. -First, add a file upload field to your blueprint: -- Add an `assets` field if you want the uploaded files to be stored in one of your asset containers. -- Add a `files` field if you're only wanting to attach the uploads to the email. Anything uploaded using this fieldtype will be attached and then deleted after the emails are sent. +The request contains the form's handle and the submission's data as JSON: -Then decide if you need single or multiple files to be uploaded. +```json +{ + "form": "contact_us", + "submission": { + "id": "1753264619.65652", + "date": "2026-07-23T10:00:00.000000Z", + "name": "Jack Black", + "email": "jack@jackblack.com" + } +} +``` -### Single files +Only `http` and `https` URLs are supported. If you're developing locally, or sending requests to internal services with self-signed certificates, you can disable SSL verification per webhook. -On your field, add a `max_files` setting of `1`: +_TODO: Screenshot of configuring a webhook_ -``` - -``` +### Building custom connections -```yaml -fields: - - - handle: cat_selfie - field: - type: assets - container: main - max_files: 1 +You can build your own connections to send submissions anywhere you like. + +Create a class in the `app/FormConnections` directory that extends `Statamic\Forms\Connections\Connection` and Statamic will discover it automatically. Addons can do the same in their `FormConnections` directory, or register classes explicitly using the `$formConnections` property on their service provider. + +```php +connections()->get('acme', [])); + } + + public function render(Form $form): VueComponent + { + return VueComponent::render('acme-connection', [ + 'action' => cp_route('forms.connect.acme.update', $form->handle()), + 'config' => $form->connections()->get('acme', []), + ]); + } + + public function routes($router): void + { + $router->patch('/', [AcmeConnectionController::class, 'update'])->name('update'); + } +} ``` -### Multiple files +#### Properties & Methods + +| Property/Method | Description | +| --- | --- | +| `$title` | The title shown on the Connect index. Defaults to a title generated from the class name. | +| `$description` | A short description shown on the Connect index. | +| `$icon` | The icon shown on the Connect index. | +| `count()` | The number shown in the "Connections" badge on the Connect index. Optional. | +| `render()` | The Vue component (and its props) rendered on the connection's page. | +| `routes()` | Routes for the connection, like the one your component saves to. They're registered under `/forms/{form}/connect/{handle}` and automatically wrapped in authorization. | +| `finalized()` | The job (or array of jobs) to be dispatched when a submission is finalized. | + +#### The frontend + +The `render()` method determines which Vue component gets rendered, along with its props. -You have two methods available to you: +Connections handle their own saving — your component should post back to a route you've registered in the `routes()` method. -First, you can create separate fields for each upload. This is useful if each has a separate purpose, like Resume, Cover Letter, and Headshot. You'll need to explicitly create each and every one in your formset. +If your connection supports multiple "rows" (eg. multiple emails per form), you can use the `` component to get a head start. -Or, you can enable multiple files on one field by dropping the `max_files` setting on your field, and using array syntax on your input by adding a set of square brackets to the `name` attribute: +Pass it your array of configs via `v-model`, a header slot and a body slot for each row, and it takes care of the collapsible row UI, along with the add/duplicate/remove actions. +```vue + + + ``` - + +#### Conditional logic + +If you want your connection to support conditional logic, the `` component renders the logic builder. Bind your conditions with `v-model:conditions` and put whatever the conditions control inside its `then` slot. + +```vue + ``` -```yaml -fields: - - - handle: selfies - field: - type: assets - container: main +On the PHP side, the `Statamic\Forms\Connections\ConnectionLogic` class handles the rest: + +- When saving, `ConnectionLogic::normalize($conditions)` strips out any incomplete conditions, and returns `null` when there's nothing to save. +- When a submission comes in, `ConnectionLogic::passes($config, $submission)` evaluates the conditions against the submission, so you can decide whether to send anything or not. + +#### Sending notifications + +When a submission is finalized, Statamic dispatches a single job chain: file uploads are converted to assets, then each of the connection jobs run and finally temporary file uploads are deleted. + +To hook into this process, return a job (or array of jobs) from the `finalized()` method: + +```php +public function finalized($submission) +{ + return new SendNotificationToThirdPartyService($submission); +} ``` +Because Statamic uses Laravel's [job chaining](https://laravel.com/docs/queues#job-chaining) feature, if you need to dispatch additional jobs within one of your jobs, call `$this->prependToChain($job)` (from Laravel's `Queueable` trait) so they stay part of the chain. + +## File uploads + +Maybe your fans want to send a photo, or you're collecting resumes and cover letters. Whatever the files, add an **Upload** field to your form and you're collecting them. + +When configuring the Upload field, you decide whether the uploaded files should be kept around: + +- **Store the files** and they'll be permanently saved as reusable [Assets](/assets) in the asset container you choose. +- **Don't store the files** and they'll only stick around long enough to be sent with your form's [connections](#connections) (like email attachments), then they'll be deleted shortly after the form is submitted. + +You can also set a maximum number of files to control whether respondents can upload a single file or several. + ### Configuring temporary file storage When using the `files` fieldtype, uploads are temporarily stored on your server before being attached to emails and then deleted. By default, these files are stored on the `local` disk at `storage/app/private/statamic/file-uploads`. @@ -564,6 +748,37 @@ Or in your `config/statamic/system.php` file: Since these are temporary files containing user uploads, you should use a private filesystem to prevent unauthorized access. ::: +## Restricting submissions + +Sometimes you don't want a form to accept submissions forever. From the form's settings, under **Access**, you can restrict submissions using any combination of: + +- **Close Date** — the form stops accepting submissions after this date. +- **Submission Limit** — the maximum number of submissions to accept, optionally scoped to a period (Total, Per Day, Per Week, or Per Month). The limit resets at the app timezone's midnight, start of the week, or start of the month. +- **Require Login** — only logged in visitors may submit the form. + +Statamic rejects restricted submissions server-side with a validation error, so nothing gets through even if you don't check for it in your template. You can also hide or replace the form's contents on the front-end using the `restricted` and `restriction_message` variables: + +```antlers +{{ form:contact }} + {{ if restricted }} +

{{ restriction_message }}

+ {{ else }} + {{ fields }} ... {{ /fields }} + {{ /if }} +{{ /form:contact }} +``` + +The message shown for a closed or limit-reached form can be customized with the **Closed Message** setting, and the message shown when login is required can be customized separately with **Require Login Message**. Leave either blank to use Statamic's default wording. If a form is both closed/limit-reached and requires login, the closed message takes precedence. + +## Localizing forms + +Form fields aren't yet localizable — a field's display label, instructions, and options are shared across every site. We're planning on adding support for this soon. + +In the meantime, you have a couple of options if you need a form's labels translated per site: + +- Use the [Translation Manager](https://statamic.com/addons/thoughtco/translation-manager) addon by Thought Collective, which lets you manage translations for content that isn't otherwise localizable. +- Duplicate the form once per site, and translate its labels, instructions, and options by hand. + ## Honeypot Simple and effective spam prevention. @@ -597,17 +812,9 @@ For example: ``` :::tip -In order to fool smarter spam bots, you should customize the name of the field by changing the `name=""` attribute to something common, but not used by your particular form. Like `username` or `address`. Then, add `honeypot: your_field_name` to your formset config. +In order to fool smarter spam bots, you should customize the name of the field by changing the `name=""` attribute to something common, but not used by your particular form. Like `username` or `address`. Then, add `honeypot: your_field_name` to your form's config. ::: -## Using AJAX - -To submit the form with AJAX, be sure to pass all the form inputs in with the submission, as Statamic sets `_token` and `_params`, both of which are required. - -You'll also need to set your ajax library's `X-Requested-With` header to `XMLHttpRequest`. - -The URL endpoint to send the request to is `/!/forms/{form-handle}`. You can configure the action route prefix which defaults to `!` in `config/statamic/routes.php`. - ## Rate limiting Form submissions are rate limited by IP address to help protect against abuse. By default, the `statamic.forms` limiter allows 10 submissions per minute across all forms. @@ -627,250 +834,532 @@ public function boot() } ``` -Consult the [Laravel documentation](https://laravel.com/docs/13.x/routing#rate-limiting) to learn more about defining rate limiters. +Consult the [Laravel documentation](https://laravel.com/docs/routing#rate-limiting) to learn more about defining rate limiters. -## Caching +## Submitting forms programmatically -If you are static caching the URL containing a form, return responses like 'success' and 'errors' will not be available after submitting unless you [exclude this page from caching](/static-caching#excluding-pages) or wrap the form in {{ nocache }} tags. +The [`form:create`](/tags/form-create) tag handles submissions for you, but you can also submit a form yourself using the `SubmitForm` action. This makes it easy to submit forms from a Livewire component, a custom controller, or your own API endpoint. -**Wrapping the form in {{ nocache }}** +```php +use Statamic\Facades\Form; +use Statamic\Forms\SubmitForm; +use Statamic\Exceptions\FormRestrictedException; +use Statamic\Exceptions\SilentFormFailureException; +use Illuminate\Validation\ValidationException; + +$form = Form::find('contact'); + +try { + $submission = app(SubmitForm::class) + ->form($form) + ->submit( + data: ['name' => 'John', 'email' => 'john@example.com'], + files: [], // Optional + ); +} catch (ValidationException $e) { + return back()->withErrors($e->errors()); +} catch (FormRestrictedException $e) { + // The form is closed, its submission limit has been reached, or + // it requires login and the visitor is logged out. + return back()->withErrors(['*' => $e->getMessage()]); +} catch (SilentFormFailureException $e) { + // The honeypot was triggered, or an event listener rejected the + // submission. To the user, it should appear as though it succeeded. + return back()->with('success', 'Form submitted successfully!'); +} -::tabs +return back()->with('success', 'Form submitted successfully!'); +``` -::tab antlers -```antlers -{{ nocache }} - {{ form:create formset="contact" }} - ... - {{ /form:create }} -{{ /nocache }} +The action provides the following methods: + +| Method | Description | +| --- | --- | +| `form` | Provide the `Form` you want to use. | +| `page` | Optional. (intended to be used alongside Forms Pro's multi-page forms feature) ID of the page you want to submit. | +| `resume` | `Submission` instance of the partial submission you wish to resume. | +| `submit` | Submit the form. Accepts an array of `$data` and an optional array of `$files`. Returns a `SubmissionResult` object containing the submission and the ID of the next page (in the case of a multi-page form). | +| `validate` | Validate the current page. Accepts an array of `$data` and an optional array of `$files`. Also accepts an array of field handles to limit which fields are validated. | + +The action also throws various exceptions: + +- `SilentFormFailureException` is thrown when the [honeypot](#honeypot) catches spam, or an event listener returns `false` from the [`FormSubmitted`](/events#formsubmitted) event — so spam bots still see a success response. The submission data is available via `$e->submission()`. +- `FormRestrictedException` is thrown when a form is [restricted](#restricting-submissions). Its message is the form's [restriction message](#restricting-submissions), and the restricted `Form` is available via `$e->form()`. + +## Forms Pro + +Need more from your forms? [Forms Pro](https://statamic.com/addons/statamic/forms-pro) is a paid addon that builds on Statamic's built-in forms with features like: + +- [Address fieldtype](#address-fieldtype) +- [Multi-page forms](#multi-page-forms) + - [Controlling page logic](#controlling-page-logic) + - [The page logic tree view](#tree-view) + - [Customizing page buttons](#customizing-page-buttons) +- [Form summaries](#form-summaries) +- [Form summary graphs](#form-summary-graphs) +- [Automagic Forms](#automagic-forms) +- Unique form instances per entry +- Additional fieldtypes & connections +- Enhanced spam prevention + +### Installation + +1. You can install the Forms Pro addon via Composer: + + ```bash + composer require statamic/forms-pro + ``` + +2. Next, publish the configuration file to `config/forms-pro.php`: + + ```bash + php artisan vendor:publish --tag=forms-pro-config + ``` + +3. You can now use Forms Pro's features when building and configuring forms in the Control Panel. + +### Address fieldtype + +Forms Pro ships an **Address** fieldtype with localized field labels and autocomplete powered by [Google Places](https://developers.google.com/maps/documentation/places/web-service/place-autocomplete) or [Geoapify](https://www.geoapify.com/). + +#### Localization + +Address formats differ by country, so the field adapts itself to country changes: + +- The "Region" label becomes "State", "Province", "County", "Prefecture", and so on. It's hidden entirely for countries that don't use one. +- The "Postcode" label becomes "ZIP Code", "Postal Code", "Eircode", and so on. It's likewise hidden where it doesn't apply. +- Some countries (the US, Canada, China, and Italy) require a region, so it's marked as required even when the field as a whole is optional. + +You can set a **Default Country** in the field's configuration to control which country — and therefore which labels — are shown by default. + +#### Autocomplete + +To enable autocomplete, specify a provider and publishable API key in your `.env`: + +```env +FORMS_PRO_ADDRESS_AUTOCOMPLETE_PROVIDER=google # or: geoapify +FORMS_PRO_ADDRESS_AUTOCOMPLETE_KEY=your-key ``` -::tab blade -```blade - - - ... - - + +Typing into the "Line 1" field suggests addresses; picking one fills in the rest of the fields. Forms Pro supports two autocomplete providers: + +- **Google Places** — the best data, and the only provider that returns unit numbers, handles UK postal towns, and per-country administrative levels. A billing account (payment method) is required to obtain an API key. +- **Geoapify** — free up to 3,000 requests per day. It doesn't have the concept of a "subpremise" — the sub-unit of a building, like a flat number in the UK — so it won't fill in Line 2. + +#### Customizing the front-end + +Like Statamic's own form fieldtypes, the Address fieldtype ships with basic stubs to get up and running. If you wish to customize how the field is rendered, you may publish the Antlers/Blade views into your project with: + +```bash +php artisan vendor:publish --tag=forms-pro-fields ``` -:: -### Axios example +The stubs will be published to `resources/views/vendor/forms-pro/forms`. -``` javascript -window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; -form = document.getElementById('form'); +You'll also need to publish Forms Pro's JS and add it to your layout. Without it, the field still renders and submits, but labels won't localize and autocomplete won't work: -// On submit... -axios.post(form.action, new FormData(form)) - .then(response => { - console.log(response.data) - }); +```bash +php artisan vendor:publish --tag=forms-pro-frontend ``` -## Precognition +```html + +``` -Statamic supports using [Laravel Precognition](https://laravel.com/docs/13.x/precognition) in forms. +Forms Pro binds what it needs to `data` attributes, so as long as you keep them, you're free to customize the markup: -Here is a basic example that uses Alpine.js for the Precognition validation, and a regular form submission. This is a starting point that you may customize as needed. For instance, you might prefer to use AJAX to submit the form. +- `data-forms-pro-address` — the root element. +- `data-address-field` — wraps a sub-field, so it can be hidden for countries that don't use it. +- `data-address-input` — the ``/`