Skip Navigation

Creating your own actions

Extending DreamForm by creating custom actions is easy. In this guide, we're creating a custom action for sending messages to Discord using their webhook feature. If you don't know what an action is yet, check out "What are actions?".

When to choose an action

  • You want to send submitted data somewhere after submission
  • You want to cancel a submission if certain conditions are met
  • It should be customizable when your action runs
  • You need panel controls for configuring the action
  • You don't need to add something to the template, your code runs entirely in the back

If any of those is a deal-breaker for you, you might be better off with a field or guard.

Creating the plugin

In DreamForm, every action is a class. In theory, you don't have to create a Kirby plugin as long as the action class is loaded correctly. But since Kirby autoloads plugins and they can also be used to share fields between projects, we're using a plugin here.

Create a new folder in site/plugins called discord-action and add an index.php file. In this file, define your plugin as you would normally do:

// site/plugins/discord-action/index.php

use Kirby\Cms\App as Kirby;

Kirby::plugin('tobimori/discord-action', []);

The action class

Let's create a new file for our class, DiscordAction.php. A basic action class only needs the blueprint(), type() and run() methods, but the following are available as well:

  • public static function blueprint(): array
    Returns an array with the blueprint definition used in the panel
  • public static function type(): string
    Returns the type of the field used internally, also used for accessing the snippet
  • public static function group(): string
    Customize the group of the action in the panel
  • public function run(): void
    The logic that's executed when the action is running
  • public static function isAvailable(FormPage|null $form = null): bool
    Whether the action should be available, used for checking global configuration details, like API keys

Okay, that was a lot, let's get back to the basics. We can start with the following, defining the blueprint() and the type() of the action.


use tobimori\DreamForm\Actions\Action;

class DiscordAction extends Action
  public static function blueprint(): array
    return [
      'title' => 'Discord Message',
      'preview' => 'fields',
      'wysiwyg' => true,
      'icon' => 'discord',
      'fields' => [
        'webhookUrl' => [
          'label' => 'Webhook URL',
          'type' => 'url',
          'placeholder' => '',
          'width' => '1/3',
          'required' => true

  public function run(): void
    // send the request

  public static function type(): string
    return 'discord';

Registering the action

Go back to your index.php file. We now have to tell DreamForm that our Discord action exists, and we can do so using the register function.

// [...]

@include_once __DIR__ . '/DiscordAction.php'; // Tell PHP to load our class

DreamForm::register(DiscordAction::class); // Register the class with DreamForm

Kirby::plugin('tobimori/discord-action', []);

DreamForm automatically uses the type returned by type(). You should now be able to see the action available in your panel.

Sending the webhook request

Discord has an API endpoint for sending messages. Using the Kirby Remote class, we're going to send a POST request to that endpoint.


class DiscordAction extends Action

  public function run(): void
    try {
      $request = Remote::post($this->block()->webhookUrl()->value(), [
        'headers' => [
          'Content-Type' => 'application/json'
        'data' => Json::encode([
          'content' => A::join(A::map($this->submission()->values()->toArray(), function ($value, $key) {
            return "**{$key}**: {$value}";
          }), "\n"),
          'embeds' => null,
          'attachments' => []

      if ($request->code() > 299) {
    } catch (Throwable $e) {

To turn the submitted values in a human-readable string, we're using $this->submission()->values() and mapping it to a line with the key and answer included.

In this code snippet, you might notice the method calls to $this->cancel() in case the request errors. In general, there are a bunch of methods available that you can call inside your action, namely:

  • $this->submission()
    Access the submission
  • $this->form()
    Access the form
  • $this->block()
    Access the block used to configure the action
  • $this->success()
    End the submission early, and show the success screen
  • $this->cancel(string $message = null, bool $public = false)
    Cancel the request with an error message - If $public is false, the error message is only shown when debug mode is enabled
  • $this->silentCancel(string $message = null)
    Silently cancel the form submission - Submission will be shown as successful to the user, except if debug mode is enabled

This way, you can easily build out actions the way you want. Let's add our action to the form and test it. If you need more inspiration, take a look at the source code of the built-in actions of DreamForm.