---
title: "Custom finisher"
manual: "Form"
version: "14.3"
permalink: "https://docs.typo3.org/permalink/typo3/cms-form:concepts-finishers-customfinisherimplementations@14.3"
source: "I/Concepts/Finishers/CustomFinisherImplementations/Index.rst"
rendered: "2026-09-19T11:59:02+00:00"
---

# Custom finisher {#concepts-finishers-customfinisherimplementations}

> [!IMPORTANT]
> Finishers are executed in the order defined in your form definition.
>
> See [Finisher execution order](https://docs.typo3.org/permalink/typo3/cms-form:concepts-finishers-execution-order@14.3).

**Table of contents**

-   [Write a custom finisher](https://docs.typo3.org/permalink/typo3/cms-form:write-a-custom-finisher@14.3)
-   [Accessing finisher options](https://docs.typo3.org/permalink/typo3/cms-form:accessing-finisher-options@14.3)
-   [Accessing form runtime values](https://docs.typo3.org/permalink/typo3/cms-form:accessing-form-runtime-values@14.3)
-   [Finisher Context](https://docs.typo3.org/permalink/typo3/cms-form:finisher-context@14.3)
-   [Share data between finishers](https://docs.typo3.org/permalink/typo3/cms-form:share-data-between-finishers@14.3)
-   [Add finisher to backend UI](https://docs.typo3.org/permalink/typo3/cms-form:add-finisher-to-backend-ui@14.3)

## Write a custom finisher {#concepts-finishers-custom-howtowrite}

To make your finisher configurable by users in the backend form editor, see
[here](https://docs.typo3.org/permalink/typo3/cms-form:concepts-finishers-customfinisherimplementations-extend-gui@14.3).

Add a new finisher to the form configuration prototype by defining a
`finishersDefinition`. Set the `implementationClassName` property to your new implementation class.

**EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml**

```yaml
prototypes:
  standard:
    finishersDefinition:
      CustomFinisher:
        implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'

```

[Register](https://docs.typo3.org/permalink/typo3/cms-form:concepts-finishers-custom-extend-gui-configuration@14.3)
your custom form definition.

Add options to your finisher with the `options` property. Options
are default values which can be overridden in the `form definition`.

### Define default values {#concepts-finishers-custom-default-value}

**EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml**

```yaml
prototypes:
  standard:
    finishersDefinition:
      CustomFinisher:
        implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'
        options:
          yourCustomOption: 'Ralf'

```

### Override options using the `form definition` {#concepts-finishers-custom-option-override}

**public/fileadmin/forms/my_form.yaml**

```yaml
identifier: sample-form
label: 'Simple Contact Form'
prototype: standard
type: Form

finishers:
  -
    identifier: CustomFinisher
    options:
      yourCustomOption: 'Björn'

renderables:
  # ...

```

A finisher must implement `FinisherInterface`
and should extend `AbstractFinisher`.
In doing so, in the logic of the
finisher the method `executeInternal()` will be called first.

## Accessing finisher options {#concepts-finishers-customfinisherimplementations-accessingoptions}

If your finisher class extends `AbstractFinisher`,
you can access the option values in the finisher using method `parseOption()`:

```php
$yourCustomOption = $this->parseOption('yourCustomOption');
```

`parseOption()` looks for 'yourCustomOption' in your
`form definition`.

**EXT:my_site_package/Classes/Domain/Finishers/CustomFinisher.yaml**

```yaml
prototypes:
  standard:
    finishersDefinition:
      CustomFinisher:
        implementationClassName: 'MyVendor\MySitePackage\Domain\Finishers\CustomFinisher'
        options:
          yourCustomOption: 'Ralf'

```

If it can't find it, `parseOption()` checks

1.  for a default value in the `prototype` configuration,
1.  for `$defaultOptions` inside your finisher class:

If it doesn't find anything, `parseOption()` returns `null`.

If it finds the option, the process checks whether the option value will
access [FormRuntime values](https://docs.typo3.org/permalink/typo3/cms-form:concepts-finishers-customfinisherimplementations-accessingoptions-formruntimeaccessor@14.3).
If the `FormRuntime` returns a positive result, it is checked whether the
option value [can access values of preceding finishers](https://docs.typo3.org/permalink/typo3/cms-form:concepts-finishers-customfinisherimplementations-finishercontext-sharedatabetweenfinishers@14.3).
At the end, it [translates the finisher options](https://docs.typo3.org/permalink/typo3/cms-form:concepts-frontendrendering-translation-finishers@14.3).

## Accessing form runtime values {#concepts-finishers-customfinisherimplementations-accessingoptions-formruntimeaccessor}

You can populate finisher options with
submitted form values using the `parseOption()` method.
You can access values of the `FormRuntime` and therefore values in every
form element by encapsulating option values with `{}`. Below, if there is a
form element with the `identifier` 'subject', you can access the value
in the finisher configuration:

**public/fileadmin/forms/my_form.yaml**

```yaml
identifier: simple-contact-form
label: 'Simple Contact Form'
prototype: standard
type: Form

finishers:
  -
    identifier: Custom
    options:
      yourCustomOption: '{subject}'

renderables:
  -
    identifier: subject
    label: 'Subject'
    type: Text

```

```php
// $yourCustomOption contains the value of the form element with the
// identifier 'subject'
$yourCustomOption = $this->parseOption('yourCustomOption');
```

You can use `{__currentTimestamp}` as an option value to return the
current UNIX timestamp.

## Finisher Context {#concepts-finishers-customfinisherimplementations-finishercontext}

The `FinisherContext` class takes care of
transferring a finisher context to each finisher. If your finisher class extends
`AbstractFinisher` the
finisher context will be available via:

```php
$this->finisherContext
```

The  `cancel` method prevents the execution of successive finishers:

```php
$this->finisherContext->cancel();
```

The method `getFormValues` returns the submitted form values.

```php
$this->finisherContext->getFormValues();
```

The method `getFormRuntime` returns the `FormRuntime`:

```php
$this->finisherContext->getFormRuntime();
```

## Share data between finishers {#concepts-finishers-customfinisherimplementations-finishercontext-sharedatabetweenfinishers}

The method `getFinisherVariableProvider` returns an
object (`FinisherVariableProvider`) which allows you
to store data and transfer it to other finishers. The data
can be easily accessed programmatically or inside your configuration:

```php
$this->finisherContext->getFinisherVariableProvider();
```

The data is stored in `FinisherVariableProvider` and is accessed
by a user-defined 'finisher identifier' and a custom option value path. The
name of the 'finisher identifier' should consist of the name of the finisher
without the 'Finisher' appendix. If your finisher class extends
`AbstractFinisher`, the finisher
identifier name is stored in the following variable:

```php
$this->shortFinisherIdentifier
```

For example, if the name of your finisher class is 'CustomFinisher', this
variable will contain 'Custom'.

There are 4 methods to access and manage data in the `FinisherVariableProvider`:

-   Add data:

    ```php
    $this->finisherContext->getFinisherVariableProvider()->add(
        $this->shortFinisherIdentifier,
        'unique.value.identifier',
        $value
    );
    ```
-   Get data:

    ```php
    $this->finisherContext->getFinisherVariableProvider()->get(
        $this->shortFinisherIdentifier,
        'unique.value.identifier',
        'default value'
    );
    ```
-   Check the existence of data:

    ```php
    $this->finisherContext->getFinisherVariableProvider()->exists(
        $this->shortFinisherIdentifier,
        'unique.value.identifier'
    );
    ```
-   Delete data:

    ```php
    $this->finisherContext->getFinisherVariableProvider()->remove(
        $this->shortFinisherIdentifier,
        'unique.value.identifier'
    );
    ```

In this way, finishers can access `FinisherVariableProvider` data programmatically.
However, it is also possible to access `FinisherVariableProvider` data using form configuration.

Assuming that a finisher called 'Custom' adds data to a `FinisherVariableProvider`:

```php
$this->finisherContext->getFinisherVariableProvider()->add(
    $this->shortFinisherIdentifier,
    'unique.value.identifier',
    'Wouter'
);
```

other finishers can access the value 'Wouter' by setting
`{Custom.unique.value.identifier}` in the form definition file.

**public/fileadmin/forms/my_form.yaml**

```yaml
identifier: sample-form
label: 'Simple Contact Form'
prototype: standard
type: Form

finishers:
  -
    identifier: Custom
    options:
      yourCustomOption: 'Frans'

  -
    identifier: SomeOtherStuff
    options:
      someOtherCustomOption: '{Custom.unique.value.identifier}'

```

## Add finisher to backend UI {#concepts-finishers-customfinisherimplementations-extend-gui}

After registering  a new finisher in the yaml form definition file, you can also
add it to the backend form editor for your backend users ( `formEditor:`
section below) to work with in the GUI:

**EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml**

```yaml
prototypes:
  standard:
    formElementsDefinition:
      Form:
        formEditor:
          editors:
            900:
              # Extend finisher drop down
              selectOptions:
                35:
                  value: 'CustomFinisher'
                  label: 'Custom Finisher'
          propertyCollections:
            finishers:
              # add finisher fields
              25:
                identifier: 'CustomFinisher'
                editors:
                  100:
                    identifier: header
                    templateName: Inspector-CollectionElementHeaderEditor
                    label: "Custom Finisher"
                  # custom field (input, required)
                  110:
                    identifier: 'customField'
                    templateName: 'Inspector-TextEditor'
                    label: 'Custom Field'
                    propertyPath: 'options.customField'
                    propertyValidators:
                      10: 'NotEmpty'
                  # email field
                  120:
                    identifier: 'email'
                    templateName: 'Inspector-TextEditor'
                    label: 'Subscribers email'
                    propertyPath: 'options.email'
                    enableFormelementSelectionButton: true
                    propertyValidators:
                      10: 'NotEmpty'
                      20: 'FormElementIdentifierWithinCurlyBracesInclusive'
                  9999:
                    identifier: removeButton
                    templateName: Inspector-RemoveElementEditor
    finishersDefinition:
      CustomFinisher:
        formEditor:
          iconIdentifier: 'form-finisher'
          label: 'Custom Finisher'
          predefinedDefaults:
            options:
              customField: ''
              email: ''
        # displayed when overriding finisher settings
        FormEngine:
          label: 'Custom Finisher'
          elements:
            customField:
              label: 'Custom Field'
              config:
                type: 'text'
            email:
              label: 'Subscribers email'
              config:
                type: 'text'

```

> [!IMPORTANT]
> Make sure to define an  `iconIdentifier` in the `finishersDefinition` of your
> finisher, otherwise the button to remove the finisher from the
> form will not be visible.

### Configuration registration {#concepts-finishers-custom-extend-gui-configuration}

Place your YAML files in a form set directory — no PHP registration needed:

```none
EXT:my_extension/
  Configuration/
    Form/
      MyFinisher/
        config.yaml
```

> [!NOTE]
> **See also**
>
> [Auto-discovery directory convention](https://docs.typo3.org/permalink/typo3/cms-form:concepts-configuration-yaml-autodiscovery@14.3)
