Dancer2::Plugin(3)

πŸ’– Dancer2::Plugin(3) πŸ’–

okay so bestie, this is the manual page for Dancer2::Plugin, filed under User Contributed Perl Documentation, and it is literally the base class for Dancer2 plugins. It's giving foundation, it's giving blueprint, it's giving main character energy for every plugin that comes after. 🫢✨


🌸 NAME 🌸

Dancer2::Plugin β€” base class for Dancer2 plugins. That's the whole name moment, no cap. πŸ’…

πŸ’… VERSION πŸ’…

version 2.2.1 β€” yes, that exact number. Periodt. ✨

πŸŽ€ SYNOPSIS πŸŽ€

okay so first, the plugin itself. This is how Dancer2::Plugin::Polite does its thing:

package Dancer2::Plugin::Polite;

use strict;
use warnings;

use Dancer2::Plugin;

has smiley => (
    is => 'ro',
    default => sub {
        $_[0]->config->{smiley} || ':-)'
    }
);

plugin_keywords 'add_smileys';

sub BUILD {
    my $plugin = shift;

    $plugin->app->add_hook( Dancer2::Core::Hook->new(
        name => 'after',
        code => sub { $_[0]->content( $_[0]->content . " ... please?" ) }
    ));

    $plugin->app->add_route(
        method => 'get',
        regexp => '/goodbye',
        code   => sub {
            my $app = shift;
            'farewell, ' . $app->request->params->{name};
        },
    );

}

sub add_smileys {
    my( $plugin, $text ) = @_;

    $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;

    return $text;
}

1;

then to load it into the app, you do this:

package MyApp;

use strict;
use warnings;

use Dancer2;

BEGIN { # would usually be in config.yml
    set plugins => {
        Polite => {
            smiley => '8-D',
        },
    };
}

use Dancer2::Plugin::Polite;

get '/' => sub {
    add_smileys( 'make me a sandwich.' );
};

1;

DESCRIPTION πŸ’–

Okay so real talk, bestie β€” this is where the magic happens. ✨

Writing the plugin πŸ› οΈ

use Dancer2::Plugin 🌸

Your plugin must begin with:

use Dancer2::Plugin;

This turns the package into a Moo class that inherits from Dancer2::Plugin. The base class provides the plugin with two attributes: app, which is populated with the Dancer2 app object for which the plugin is being initialized for, and config, which holds the plugin section of the application configuration. Iconic.

Modifying the app at building time πŸ—οΈ

If the plugin needs to tinker with the application β€” add routes or hooks, for example β€” it can do so within its BUILD() function.

sub BUILD {
    my $plugin = shift;

    $plugin->app->add_route( ... );
}

Adding keywords πŸ”‘

Via plugin_keywords

Keywords that the plugin wishes to export to the Dancer2 app can be defined via the plugin_keywords keyword:

plugin_keywords qw/
    add_smileys
    add_sad_kitten
/;

Each of the keyword will resolve to the class method of the same name. When invoked as keyword, it'll be passed the plugin object as its first argument.

sub add_smileys {
    my( $plugin, $text ) = @_;

    return join ' ', $text, $plugin->smiley;
}

# and then in the app

get '/' => sub {
    add_smileys( "Hi there!" );
};

You can also pass the functions directly to plugin_keywords.

plugin_keywords
    add_smileys => sub {
        my( $plugin, $text ) = @_;

        $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;

        return $text;
    },
    add_sad_kitten => sub { ... };

Or a mix of both styles. We're easy that way:

plugin_keywords
    add_smileys => sub {
        my( $plugin, $text ) = @_;

        $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;

        return $text;
    },
    'add_sad_kitten';

sub add_sad_kitten {
    ...;
}

If you want several keywords to be synonyms calling the same function, you can list them in an arrayref. The first function of the list is taken to be the "real" method to link to the keywords.

plugin_keywords [qw/ add_smileys add_happy_face /];

sub add_smileys { ... }

Calls to plugin_keywords are cumulative.

Via the :PluginKeyword function attribute

For perl 5.12 and higher, keywords can also be defined by adding the :PluginKeyword attribute to the function you wish to export.

For Perl 5.10, the export triggered by the sub attribute comes too late in the game, and the keywords won't be exported in the application namespace.

sub foo :PluginKeyword { ... }

sub bar :PluginKeyword( baz quux ) { ... }

# equivalent to

sub foo { ... }
sub bar { ... }

plugin_keywords 'foo', [ qw/ baz quux / ] => \&bar;

For an attribute

You can also turn an attribute of the plugin into a keyword.

has foo => (
    is => 'ro',
    plugin_keyword => 1,  # keyword will be 'foo'
);

has bar => (
    is => 'ro',
    plugin_keyword => 'quux',  # keyword will be 'quux'
);

has baz => (
    is => 'ro',
    plugin_keyword => [ 'baz', 'bazz' ],  # keywords will be 'baz' and 'bazz'
);

Accessing the plugin configuration βš™οΈ

The plugin configuration is available via the config() method.

sub BUILD {
    my $plugin = shift;

    if ( $plugin->config->{feeling_polite} ) {
        $plugin->app->add_hook( Dancer2::Core::Hook->new(
            name => 'after',
            code => sub { $_[0]->content( $_[0]->content . " ... please?" ) }
        ));
    }
}

Getting default values from config file πŸŽ€

Since initializing a plugin with either a default or a value passed via the configuration file, like

has smiley => (
    is => 'ro',
    default => sub {
        $_[0]->config->{smiley} || ':-)'
    }
);

Dancer2::Plugin allows for a from_config key in the attribute definition. Its value is the plugin configuration key that will be used to initialize the attribute.

If it's given the value 1, the name of the attribute will be taken as the configuration key.

Nested hash keys can also be referred to using a dot notation.

If the plugin configuration has no value for the given key, the attribute default, if specified, will be honored.

If the key is given a coderef as value, it's considered to be a "default" value combo:

has foo => (
    is => 'ro',
    from_config => sub { 'my default' },
);

# equivalent to
has foo => (
    is => 'ro',
    from_config => 'foo',
    default => sub { 'my default' },
);

For example:

# in config.yml

plugins:
    Polite:
        smiley: ':-)'
        greeting:
            casual: Hi!
            formal: How do you do?

# in the plugin

has smiley => (             # will be ':-)'
    is          => 'ro',
    from_config => 1,
    default     => sub { ':-(' },
);

has casual_greeting => (    # will be 'Hi!'
    is          => 'ro',
    from_config => 'greeting.casual',
);

has apology => (            # will be 'sorry'
    is          => 'ro',
    from_config => 'apology',
    default     => sub { 'sorry' },
)

has closing => (            # will be 'See ya!'
    is => 'ro',
    from_config => sub { 'See ya!' },
);

Config becomes immutable 🧊

The plugin's config attribute is loaded lazily on the first call to config. After this first call config becomes immutable so you cannot do the following in a test:

use Dancer2;
use Dancer2::Plugin::FooBar;

set plugins => {
    FooBar => {
        wibble => 1,  # this is OK
    },
};

flibble(45);          # plugin keyword called which causes config read

set plugins => {
    FooBar => {
        wibble => 0,  # this will NOT change plugin config
    },
};

Accessing the parent Dancer app πŸ‘‘

If the plugin is instantiated within a Dancer app, it'll be accessible via the method app().

sub BUILD {
    my $plugin = shift;

    $plugin->app->add_route( ... );
}

To use Dancer's DSL in your plugin:

$self->dsl->debug( β€œHi! I’m logging from your plugin!” );

See "DSL KEYWORDS" in Dancer2::Manual for a full list of Dancer2 DSL.


Using the plugin within the app 🌈

A plugin is loaded via

use Dancer2::Plugin::Polite;

The plugin will assume that it's loading within a Dancer module and will automatically register itself against its app() and export its keywords to the local namespace. If you don't want this to happen, specify that you don't want anything imported via empty parentheses when useing the module:

use Dancer2::Plugin::Polite ();

Plugins using plugins 🧚

It's easy to use plugins from within a plugin:

package Dancer2::Plugin::SourPuss;

use Dancer2::Plugin;
use Dancer2::Plugin::Polite;

sub my_keyword { my $smiley = smiley(); }

1;

This does not export smiley() into your application - it is only available from within your plugin. However, from the example above, you can wrap DSL from other plugins and make it available from your plugin.


Utilizing other plugins πŸ”

You can use the find_plugin to locate other plugins loaded by the user, in order to use them, or their information, directly:

# MyApp.pm
use Dancer2;
use Dancer2::Plugin::Foo;
use Dancer2::Plugin::Bar;

# Dancer2::Plugin::Bar;
...

sub my_keyword {
    my $self = shift;
    my $foo  = $self->find_plugin('Dancer2::Plugin::Foo')
        or $self->dsl->send_error('Could not find Foo');

    return $foo->foo_keyword(...);
}

Hooks πŸͺ

New plugin hooks are declared via plugin_hooks.

plugin_hooks 'my_hook', 'my_other_hook';

Hooks are prefixed with plugin.plugin_name. So the plugin my_hook coming from the plugin Dancer2::Plugin::MyPlugin will have the hook name plugin.myplugin.my_hook.

Hooks are executed within the plugin by calling them via the associated app.

$plugin->execute_plugin_hook( 'my_hook' );

You can also call any other hook if you provide the full name using the execute_hook method:

$plugin->app->execute_hook( 'core.app.route_exception' );

Or using their alias:

$plugin->app->execute_hook( 'on_route_exception' );
Note: If your plugin consumes a plugin that declares any hooks, those hooks are added to your application, even though DSL is not.

Writing Test Gotchas 😭

Constructor for Dancer2::Plugin::Foo has been inlined and cannot be updated πŸ’€

You'll usually get this one because you are defining both the plugin and app in your test file, and the runtime creation of Moo's attributes happens after the compile-time import voodoo dance.

To get around this nightmare, wrap your plugin definition in a BEGIN block.

BEGIN {
    package Dancer2::Plugin::Foo;

    use Dancer2::Plugin;

        has bar => (
            is => 'ro',
            from_config => 1,
        );

        plugin_keywords qw/ bar /;

}

{
    package MyApp;

    use Dancer2;
    use Dancer2::Plugin::Foo;

    bar();
}

You cannot overwrite a locally defined method (bar) with a reader 🚫

If you set an object attribute of your plugin to be a keyword as well, you need to call plugin_keywords after the attribute definition.

package Dancer2::Plugin::Foo;

use Dancer2::Plugin;

has bar => (
    is => 'ro',
);

plugin_keywords 'bar';

Coverage for Dancer2::Plugin::<Name> is 0.0%, with 15 naked subroutines πŸ‘€

To avoid errors caused by missing pod for private plugin methods, write your pod coverage test like so:

pod_coverage_ok(
    "Dancer2::Plugin::MyAwesomePlugin", {
        also_private => [
            qw/
              BUILDARGS BUILD ClassHooks PluginKeyword dancer_app
              execute_plugin_hook hook keywords on_plugin_import plugin_args
              plugin_setting realms realm realm_providers register register_hook
              register_plugin request var
              /
        ]
    });

✨ AUTHOR ✨

Written by the literal Dancer Core Developers πŸ’– β€” an entire squad of absolute legends maintaining this thing, no cap. Say thank you to them bestie, they did that 🫢


okay so legal bit, but make it cute:

  • This software is copyright (c) 2026 by Alexis Sukrieh πŸ‘‘
  • It's free software πŸ’… you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Yes, literally the same terms as Perl 5. iykyk.
fr fr, read the license, it's giving generous and iconic 🌸

aaaand that's the whole page bestie, we made it πŸ¦‹ this one shipped as perl v5.42.3 and the docs are dated 2026-09-20 β€” go forth and slay your plugins πŸ’–βœ¨πŸŽ€

πŸ“¦ where this page comes from