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.