Dancer2...gration(3)
β¨ NAME β¨
Dancer2::Manual::Migration β the whole entire guide for migrating from Dancer (the old one) to Dancer2 (the glow-up). Bestie, this is the manual page Dancer2::Manual::Migration(3), folded in right up top like a little name tag π
π VERSION π
version 2.2.1
π¦ Migration from Dancer 1 to Dancer2 π¦
okay so this doc is literally just "here are the things you're gonna notice when you upgrade from Dancer (version 1) to Dancer2." No surprises, no jump scares, just a list. Let's get into it π
πΈ Launcher script πΈ
The default launcher script in Dancer was called bin/app.pl and it looked exactly like this:
#!/usr/bin/env perl
use Dancer;
use MyApp;
dance;
In Dancer2, that same thing lives at bin/app.psgi and looks like this:
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use MyApp;
MyApp->to_app;
So the moves are: delete the use Dancer; line, swap the dance; command out for MyApp->to_app; (where MyApp is the name of your application, obviously), and add these lines:
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
There's a Dancer Advent Calendar article over at http://advent.perldancer.org that covers the to_app keyword and how to use it β that specific one is at http://advent.perldancer.org/2014/9. Go read it, it's giving tutorial energy β
π Configuration π
If you wanna point the static (public) content directory somewhere else, you set the public_dir option. But heads up bestie β when you do that, you also have to set the static_handler option. Both. Together. No skipping π
π Apps π
- In Dancer2, every module is its own separate application with its own namespace and its own variables. You can set the application name inside each of your Dancer2 application modules. And if you want different modules to be tied into the same app, you just set the application name to the same value in all of them. Easy.
For example, to set the appname directive explicitly:
MyApp:
package MyApp;
use Dancer2;
use MyApp::Admin
hook before => sub {
var db => 'Users';
};
get '/' => sub {...};
1;
MyApp::Admin:
package MyApp::Admin;
use Dancer2 appname => 'MyApp';
# use a lexical prefix so we don't override it globally
prefix '/admin' => sub {
get '/' => sub {...};
};
1;
Without the appname directive, MyApp::Admin would not have access to the variable db. And not just that β when you accessed /admin, the before hook would not even run. Rude, honestly.
See Dancer2::Cookbook for the details, specifically the bit about using the prefix feature to split your application: https://metacpan.org/pod/Dancer2::Cookbook#Using-the-prefix-feature-to-split-your-application
- To speed up an app in Dancer2, install the recommended modules listed in the "Performance Improvements" section of
Dancer2::Manual::Deployment. Free speed, no cap π₯
π«Ά Request π«Ά
The request object (Dancer2::Core::Request) now defers a whole bunch of its code to Plack::Request, so it stays consistent with the known interface to PSGI requests. Iconic behaviour, honestly.
Currently, these attributes pass directly through to Plack::Request:
address, remote_host, protocol, port, method, user, request_uri, script_name, content_length, content_type, content_encoding, referer, and user_agent.
Here's the thing though β if those attributes used to return undef when there was no value, they'll now return whatever Plack::Request defines instead, which just might be an empty list. So likeβ¦ guard your stuff π
For example:
my %data = (
referer => request->referer,
user_agent => request->user_agent,
);
should be replaced by:
my %data = (
referer => request->referer || '',
user_agent => request->user_agent || '',
);
π Plugins: plugin_setting π
plugin_setting returns the configuration of the plugin. The catch: it can only be called inside register or on_plugin_import. Nowhere else, bestie. Them's the rules.
π§ Routes π§
Dancer2 requires every route you define via a string to start with a leading slash /. Every single one. No exceptions.
For example:
get '0' => sub {
return "not gonna fly";
};
that would return an error. The correct way to write it is with get '/0' β see how the slash is right there, doing its job? Slay.
π Route parameters π
The params keyword β the one that gives you merged parameters β used to let body parameters override route parameters. Not anymore. Now route parameters take precedence over query parameters and body parameters. Order has been restored, periodt.
We've also introduced route_parameters for retrieving parameter values from the route matching. Please refer to Dancer2::Manual for more info, iykyk.
π Tests π
Dancer2 recommends using Plack::Test.
For example:
use strict;
use warnings;
use Test::More tests => 2;
use Plack::Test;
use HTTP::Request::Common;
{
package App::Test; # or whatever you want to call it
get '/' => sub { template 'index' };
}
my $test = Plack::Test->create( App::Test->to_app );
my $res = $test->request( GET '/' );
ok( $res->is_success, '[GET /] Successful' );
like( $res->content, qr{<title>Test2</title>}, 'Correct title' );
Other modules that could be used for testing are:
Test::TCPTest::WWW::Mechanize::PSGI
π Logs π
The logger_format attribute in the Logger role (Dancer2::Core::Role::Logger) is now called log_format. Renamed. That's it, that's the tea.
Also, read_logs can no longer be used, same situation with Dancer2::Test. Instead, Dancer2::Logger::Capture could be used for testing, so you can capture all your logs into an object. Cute, right?
For example:
use strict;
use warnings;
use Test::More import => ['!pass'];
use Plack::Test;
use HTTP::Request::Common;
use Ref::Util qw<is_coderef>;
{
package App;
use Dancer2;
set log => 'debug';
set logger => 'capture';
get '/' => sub {
debug 'this is my debug message';
return 1;
};
}
my $app = Dancer2->psgi_app;
ok( is_coderef($app), 'Got app' );
test_psgi $app, sub {
my $cb = shift;
my $res = $cb->( GET '/' );
is $res->code, 200;
my $trap = App->dancer_app->logger_engine->trapper;
is_deeply $trap->read, [
{ level => 'debug', message => 'this is my debug message' }
];
};
π Exports: Tags π
The following tags are not needed in Dancer2:
use Dancer2 qw(:syntax);
use Dancer2 qw(:tests);
use Dancer2 qw(:script);
Instead, you should be using the plackup command. It gives you a development server and it reads the configuration options in your command line utilities. Two birds, one slay β¨
π Engines π
- Engines receive a logging callback
Engines now receive a logging callback named log_cb. Engines can use it to log literally anything at run-time, without having to worry about which logging engine is being used. Stress-free logging, we love to see it.
Why is it a callback, you ask? Because the logger might get changed at run-time, and we want engines to always be able to reach the current one without holding a reference back to the core application object. Main character design decision, honestly.
The logger engine doesn't have this attribute, since it is the logger itself. It contains multitudes. It doesn't need a callback to itself.
- Engines handle encoding consistently
All engines are now expected to handle encoding on their own. User code is expected to be in internal Perl representation.
So, therefore: all serializers, for example, should deserialize to the Perl representation. Templates, on the other hand, encode to UTF-8 if the user requests it β or by default.
One side-effect of this is that from_yaml will call YAML's Load function with decoded input. Noted π
π¦ Templating engine changes π¦
If you're migrating from Dancer1 and your application is based on Dancer::Template::Simple β heads up β the Dancer2::Template::Simple engine is no longer part of the core Dancer2 distribution. It's still available to install as a separate distribution from CPAN. It's not gone, it just moved out π
In Dancer1, these two were equivalent for Template::Toolkit:
template 'foo/bar'
template '/foo/bar'
In Dancer2, when you're using Dancer2::Template::TemplateToolkit, the version with the leading slash will try to locate /foo/bar relative to your filesystem root β not relative to your Dancer application directory. Different vibe entirely, so watch out.
The Dancer2::Template::Simple engine is unchanged in this respect. It behaves like it always did.
Whereas in Dancer1, template engines have these methods:
$template_engine->view('foo.tt')
$template_engine->view_exists('foo.tt')
In Dancer2, you should instead write:
$template_engine->view_pathname('foo.tt')
$template_engine->pathname_exists($full_path)
You may not need these at all, unless you're writing a templating engine. In which case, go off π
πΈ Serializers πΈ
You no longer need to implement the loaded method. It's simply unnecessary. Delete it, it's not doing anything, it never was π«Ά
π Sessions π
Now the Simple session engine is turned on by default β unless you go out of your way to specify a different one. Default: slay. Custom: also valid.
π Configuration π
β public_dir β
You cannot set the public directory with setting anymore. Instead you'll need to call config:
# before
setting( 'public_dir', 'new_path/' );
# after
config->{'public_dir'} = 'new_path';
π confdir π
You cannot set the configuration directory (confdir) with setting anymore either. You can use an environment variable, though:
export DANCER_CONFDIR=$PROJECT_ROOT/etc
π warnings π
The warnings configuration option, along with the environment variable DANCER_WARNINGS, have been removed and have no effect whatsoever. Gone. Zero. Nothing.
They were added back when someone requested being able to load Dancer without the warnings pragma β which it adds, just like Moose, Moo, and other modules provide.
If you want that to happen now (which, honestly, you probably shouldn't be doing), you can always control it lexically:
use Dancer2;
no warnings;
You can also use Dancer2 within a narrower scope:
{ use Dancer2 }
use strict;
# warnings are not turned on
However β and I mean this with my whole chest β having warnings turned on is very recommended. Please. For me π
π§ server_tokens π§
The configuration server_tokens has been introduced in the reverse form β but a more sensible, Plack-compatible one β as no_server_tokens.
And DANCER_SERVER_TOKENS changed to DANCER_NO_SERVER_TOKENS. Flip it and reverse it.
π₯ engines π₯
If you want to use Template::Toolkit instead of the built-in simple templating engine, you used to enable the following line in the config.yml file:
template: "template_toolkit"
That was enough to get you started. The start_tag and end_tag it used were the same as in the simple template: <% and %> respectively.
If you wanted to further customize Template::Toolkit, you could also enable or add the following:
engines:
template_toolkit:
encoding: 'utf8'
start_tag: '[%'
end_tag: '%]'
In Dancer 2, you can also enable Template::Toolkit with the same configuration option:
template: "template_toolkit"
But the default start_tag and end_tag are now [% and %], so if you used the default in Dancer 1 now you will have to explicitly change the start_tag and end_tag values. No skipping this one bestie, they literally swapped the vibes on you. The configuration also got an extra level of depth. Under the engine key there is a template key and the template_toolkit key comes below that. As in this example:
engines:
template:
template_toolkit:
start_tag: '<%'
end_tag: '%>'
In a nutshell, if you used to have
template: "template_toolkit"
You need to replace it with
template: "template_toolkit"
engines:
template:
template_toolkit:
start_tag: '<%'
end_tag: '%>'
Session engine π
The session engine is configured in the engine section. She got a whole glow-up, here's the rename receipts:
session_namechanged tocookie_name.session_domainchanged tocookie_domain.session_expireschanged tocookie_duration.session_securechanged tois_secure.session_is_http_onlychanged tois_http_only.
Dancer2 also adds two attributes for session:
cookie_pathβ The path of the cookie to create for storing the session key. Defaults to/. iconic default tbhsession_durationβ Duration in seconds before sessions should expire, regardless of cookie expiration. If set, then SessionFactories should use this to enforce a limit on session validity.
See Dancer2::Core::Role::SessionFactory for more detailed documentation for these options, or the particular session engine for other supported options. iykyk, go read the deep lore if you need it
session: Simple πΈ
session: Simple
engines:
session:
Simple:
cookie_name: dance.set
cookie_duration: '24 hours'
is_secure: 1
is_http_only: 1
Plack Middleware π
In Dancer1 you could set up Plack Middleware using a plack_middlewares key in your config.yml file. Under Dancer2 you will instead need to invoke middleware using Plack::Builder, as demonstrated in Dancer2::Manual::Deployment. different era, different method, same slay
Keywords π
Calling Keywords Explicitly
In Dancer1, keywords could be imported individually into a package:
package MyApp;
use Dancer qw< get post params session >;
get '/foo' { ... };
Any keywords you didn't export could be called explicitly:
package MyApp;
use Dancer qw< get post params session >;
use List::Util qw< any >;
Dancer::any sub { ... };
Dancer2's DSL is implemented differently. Keywords only exist in the namespace of the package which uses Dancer2, i.e. there is no Dancer2::any, only e.g. MyApp::any. so if you go looking for the global one, she's not there, that's not a bug it's a whole design choice
If you only want individual keywords, you can write a shim as follows:
package MyApp::DSL;
use Dancer2 appname => 'MyApp';
use Exporter qw< import >;
our @EXPORT = qw< get post ... >
Then in other packages:
package MyApp;
use MyApp::DSL qw< get post >;
MyApp::DSL::any sub { ... };
appdir
This keyword does not exist in Dancer2. However, the same information can be found in config->{'appdir'}. she moved, but she left a forwarding address π
load
This keyword is no longer required. Dancer2 loads the environment automatically and will not reload any other environment when called with load. (It's a good thing.) genuinely, this is a win
param_array
This keyword doesn't exist in Dancer2. rip, she's gone, we don't talk about her
session
In Dancer a session was created and a cookie was sent just by rendering a page using the template function. In Dancer2 one needs to actually set a value in a session object using the session function in order to create the session and send the cookie. yes this trips literally everyone, you're not alone π
The session keyword has multiple states:
- No arguments β Without any arguments, the session keyword returns a Dancer2::Core::Session object, which has methods for read, write, and delete.
my $session = session;
$session->read($key);
$session->write( $key => $value );
$session->delete($key);
- Single argument (key) β If a single argument is provided, it is treated as the key, and it will retrieve the value for it.
my $value = session $key;
- Two arguments (key, value) β If two arguments are provided, they are treated as a key and a value, in which case the session will assign the value to the key.
session $key => $value;
- Two arguments (key, undef) β If two arguments are provided, but the second is undef, the key will be deleted from the session.
session $key => undef;
In Dancer 1 it wasn't possible to delete a key, but in Dancer2 we can finally delete: we ate and left no crumbs
# these two are equivalent
session $key => undef;
my $session = session;
$session->delete($key);
You can retrieve the whole session hash with the data method:
$session->data;
To destroy a session, instead of writing:
session->destroy
In Dancer2, we write:
app->destroy_session if app->has_session
Heads up bestie: if you make changes to the session in anafterhook, your changes will not be written to storage, because writing sessions to storage also takes place in an (earlier)afterhook. it's giving "I did the work and nobody saved it" π
Hooks πͺ
The following Dancer 1 hooks have been renamed in Dancer2:
before_error_initchanged toinit_error.before_error_renderchanged tobefore_error.after_error_renderchanged toafter_error.
The original names are still supported, but you should change these to new versions during your migration as the aliases may be removed in a newer version of Dancer2. fr fr, do the rename now before future-you gets jumpscared
AUTHOR π
Dancer Core Developers absolute legends, no notes π
COPYRIGHT AND LICENSE π
This software is copyright (c) 2026 by Alexis Sukrieh.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. free as in freedom, free as in slay π¦
And that's a wrap on Dancer2...gration(3), babe! β¨ This lil' page was cooked up by the Perl toolchain running perl v5.42.3, dated 2026-09-20, and it's the user-contributed Perl documentation for Dancer2...gration(3). Go forth and migrate, you got this π
π«Ά
π¦ where this page comes from
- fedora 44: perl-Dancer2 2.2.1-1.fc44 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms