Skip to content

[6.x] Form Fields Refactor#14496

Draft
duncanmcclean wants to merge 30 commits intoforms-2from
form-fields
Draft

[6.x] Form Fields Refactor#14496
duncanmcclean wants to merge 30 commits intoforms-2from
form-fields

Conversation

@duncanmcclean
Copy link
Copy Markdown
Member

@duncanmcclean duncanmcclean commented Apr 15, 2026

This pull request refactors how form fields work under the hood, ahead of some changes we're making to forms.

TLDR: Form fields aren't stored in blueprints anymore, they're stored under a fields key in the form data. Fields will be migrated the next time the form is saved.

Problem

We want to offer different fieldtypes when editing forms than the ones available elsewhere.

A couple of examples:

  • We want to rename the Text/Textarea fieldtypes to "Short Answer" and "Long Answer", but just for forms.
  • We want to offer "Select" and "Multi-Select" fieldtypes, both of which use the Select fieldtype under the hood.
  • We want to provide separate fieldtypes for Name/Email/Phone, but they should all use the Text fieldtype under the hood.

Solution

Because they aren't exactly 1:1 mappings with existing fieldtypes, we need to create a layer in between the form builder and the underlying blueprint fields.

This layer (which we're referring to as "form fieldtypes") is responsible for wrapping normal fieldtypes. They can expose their own names, keywords, icons, config options, etc.

<?php

class ShortAnswer extends FormFieldtype
{
  protected static $handle = 'short_answer';
  protected static $icon = 'text';
  protected $categories = ['Generic Fields'];
  protected $keywords = ['text'];

  public function toFieldArray(): array
  {
    return [
      'type' => 'text',
    ];
  }
}

Then, instead of storing fields in a blueprint, like you would with normal fieldtypes, fields are stored in the form's data and map to one of the "form fieldtypes".

# resources/forms/contact_us.yaml
title: Contact us
fields:
  sections:
    -
      fields:
        -
          handle: name
          field: 
	        type: short_answer
	        validate: required
	    -
	      handle: email
	      field:
	        type: email
	        validate: required # already validates "email"
	    -
	      handle: message
	      field:
	        type: long_answer
	        validate: required

Behind the scenes, there are new FormFields, FormField and FormFieldtype classes powering everything.

When rendering form fields — in the CP, on the frontend or in emails — the form fields will be converted to traditional blueprint fields.

Migrating blueprints

When the fields key is missing, Statamic will read from the form blueprint and convert fields to the new "form fieldtypes" on-the-fly.

For example: text fields will become short_answer fields, textarea fields will become long_answer fields, etc.

When there's no matching "form fieldtype" for an existing field, the Fallback form fieldtype will be used which literally returns the field config from the fieldtype's toFieldArray().

The fields key will be written to the form data array and the blueprint will be deleted the next time the form is saved.

Custom Form Fieldtypes

To build a custom form fieldtype, create a class that extends Statamic's FormFieldtype and add a toFieldArray() method.

<?php

class App\FormFieldtypes;

class Email extends FormFieldtype
{
  protected static $handle = 'email';
  protected static $icon = 'form-email';
  protected $categories = ['Contact Info'];
  protected $keywords = ['mailto', 'website'];

  public function configFieldItems(): array
  {
    return [
      'placeholder' => ['type' => 'text', 'display' => 'Placeholder'],
    ];
  }

  public function toFieldArray(): array
  {
    return [
      'type' => 'text',
      'validate' => 'email',  
      'placeholder' => $this->config('placeholder'),
    ];
  }
}

The toFieldArray() method is responsible for building the underlying blueprint field used when rendering the field.

You may also add a configFieldItems() method to define any config options which will be editable in the UI.

In an app, simply put your class in the app/FormFieldtypes directory and it'll be registered automatically.

In an addon, put your class in the src/FormFieldtypes directory or register it manually in your ServiceProvider.php:

protected $formFieldtypes = [
	FormFieldtypes\Custom::class,
];

Fieldtype::makeSelectableInForms() & Fieldtype::makeUnselectableInForms()

The makeSelectableInForms() and makeUnselectableInForms() methods on the Fieldtype class have been deprecated and will be removed in v7.

You should instead call makeSelectable() or makeUnselectable() on the form fieldtype class directly:

LongAnswer::makeSelectable();
LongAnswer::makeUnselectable();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant