Plugins

A plugin packages behaviour an application opts into with one line:

plugin 'RequestId';
plugin '+My::Plugin' => { opt => 1 };

A name resolves against Punk::Plugin::; the + prefix takes the class literally. The plugin's register receives the application registrar and its options, and wires whatever it needs - routes, hooks, helpers, keywords.

Helpers

The lightest extension is a context helper - a new method every handler can call:

helper uid => sub { my ($c) = @_; $c->stash->{uid} };

# later, anywhere
my $uid = $c->uid;

Plugins usually install these from register.

Hooks

hook before_dispatch => sub { my ($c) = @_; ...; return };
hook after_dispatch  => sub { my ($c, $resp) = @_; ... };

before_dispatch runs after routing, before guards; a reference return short-circuits the request. after_dispatch sees the finalized PSGI triplet and may mutate or replace it. A request-id plugin is ten lines: stamp the context in before_dispatch, stamp the header in after_dispatch.

Middleware

For behaviour that must wrap the whole PSGI app - compression, raw timing - there is the outer layer:

middleware sub {
    my ($app) = @_;
    sub { my ($env) = @_; ...; $app->($env) };
};

Keywords of your own

A plugin can install declaration keywords into the application class, so its surface reads like the core DSL:

$app->install_kw(queue => sub { ... });

Punk installs the keyword from C beside its own: installing over a core keyword croaks, two owners claiming one name croak naming both, and the same owner installing twice is a no-op. The keyword forwards its arguments and returns in the caller's context - my $q = queue(); is a keyword call like any other.

This is how Punk::Queue gives an application queue, task and cron that look and behave exactly like get and under:

use Punk::Plugin::Queue;            # compile time: the keywords exist

plugin 'Queue' => { dsn => ... };   # runtime: the configuration

task 'mail.send' => 'Job::Mail#send';

What a plugin sees

register receives the Punk::App registrar - the same surface the keywords write to - so a plugin can declare routes, guards, mounts, helpers, hooks and keywords with no privileged API. If the application class could write it, the plugin can.

What ships

Punk itself carries nine plugins, each documented where its subject lives:

And five ship as their own distributions, because they carry an engine or a client the core does not need: Punk::Plugin::Queue, Punk::Plugin::Mailer, Punk::Plugin::TOTP, Punk::Plugin::ClamAV and Punk::Plugin::OpenTelemetry.