Mojolicious::Plugin::Loco - launch a web browser; easy local GUI


version 0.008


  use Mojolicious::Lite;
  plugin 'Loco';

  get '/' => "index";

  post '/kick-me' => sub {
    my $c = shift;
    return if $c->loco->csrf_fail;
    # system q(cat /dev/urandom | fdisk);
    my $bcount = ++$c->session->{bruises};
    $c->flash(msg => "Ok, that hurt. ($bcount)");

  post '/quit' => sub { shift->loco->quit; };

  app->secrets(['I am the very model of a modern major general'.rand()]);

  @@ index.html.ep
  % layout 'default';
  %= csrf_button_to "Kick Me", '/kick-me', method => 'POST';
  %= csrf_button_to "Exit",    '/quit',    method => 'POST';
  <p><%= $c->flash('msg')%></p>

  @@ layouts/default.html.ep
  <!DOCTYPE html><html><head>
  %= $c->loco->jsload;    # needs to be on every page
  %= content_for 'head';
  %= content

Save to, and then

  perl daemon


This plugin allows writing low-effort desktop applications using Mojolicious (even cross-platform ones if your code is sufficiently portable).

When run as a daemon listening on an explicit port, the plugin launches an internet browser, passing an initial URL and seed so that subsequent requests from that browser can be distinguished. Client-side javascript then pings the server regularly until either the server shuts down or the various browser windows are closed.

For applications making changes to the filesystem / local machine state, or handling sensitive data or anything else that matters, please see "SECURITY CONSIDERATIONS"

This module is currently experimental; the API may change without notice.



Key of the configuration hash (app->config) containing plugin option values. Default is 'loco'. If false, then app->config is not consulted.


How many seconds for the server to wait for the initial browser window to finish loading before giving up and shutting down. Zero means wait forever. Default is 15.


How many seconds for the server to wait after the last browser window ceases communicating before shutting down (we do not rely on window.unload). Default is 3.

Since javascript timer events from backgrounded/hidden tabs/windows are typically throttled in current browsers, reducing this below 2 will most likely make the application terminate prematurely if all windows are hidden or minimized.


URI path for the entry point of your application (i.e., what to display in the initial brower window). Default is /.


URI path prefix reserved for use by this plugin. Endpoints needed by this module live here; this is also where the required javascript file(s) are served from. Default is /hb/; it can be pretty much anything as long as it's distinct from what the rest of your application uses.


Which browser (executable file) to run. If false (0 or '') then no browser will be invoked. If a subroutine reference, then it is assumed that calling it with a URI as first argument will launch something useful. Default is for Browser::Open to launch whatever the default browser is for your system.


If false (default), $c->validation->csrf_protect fails on any session other than the one started from the initial browser launched. Requests doing this check are then blocked from all other sessions.

If true, $c->validation->csrf_protect will behave in the default manner, and you will need to check "loco->id" explicitly.

Read "SECURITY CONSIDERATIONS" if you think you might want to set this.



Get or set plugin configuration options. Note that most values are only meaningful prior to server start so changing them thereafter won't affect anything.

  $c->loco->config                         # -> full hash of settings
  $c->loco->config('final_wait')           # -> 3
  $c->loco->config(final_wait => 200)      # -> $c, sets final_wait
  $c->loco->config({final_wait => 200})    # -> same
  app->config('loco')->{final_wait} = 200  # same effect if config_key not changed


Get or set the session id (for the purposes of this plugin). By default, this is 1 for the launched browser's session and undefined for all others.


Renders an HTTP 400 reply and returns true unless csrf_token validation succeeds. If "allow_other_sessions" is false, this implicitly also checks "loco->id".

  return if $c->loco->csrf_fail;


Renders an HTTP 400 reply and returns true unless there is a nonzero session id.

  return if $c->loco->id_fail;


Stop the server and render a final page if necessary. This implicitly calls "loco->csrf_fail" and checks "loco->id".


Loads whatever javascript needs to be in the <head> section of every page to be displayed in the browser window. You most likely want this in your default layout.

  %= $c->loco->jsload;

Or you can be more elaborate

  %= $c->loco->jsload( jquery => '', begin
        .on_hb(function(h) {
          // do something on every heartbeat
  % end );

Options for "loco->jsload" include


URL to be loading jquery from. Default is to use the jquery version included in the Mojolicious distribution. Specifying an empty string '' suppresses jquery loading entirely, if you have already loaded it as part of some other package.


Suppress the default on_finish handler.


Final begin block, if provided, will be assumed to be javascript code to further configure the heartbeat object (code is preceded by $().heartbeat()), typically to add on_hb or on_finish handlers


In a typical desktop application, traffic between the user interface and the application code will be invisible to remote attackers and code running in different processes. Structuring your application as a Mojolicious server with UI provided by an internet browser using this plugin will, in some cases, expose this traffic and make additional interferences possible.

What follows is a (necessarily incomplete) listing of vulnerabilities you may need to address.

Listening on Network-accessible Interfaces/Ports

Using a listening point that is network accessible means the server will (at least initially) accept incoming connection attempts from untrusted remote sites unless you have a reliable firewall to block these. If, in addition, "allow_other_sessions" is also set, then these remote clients will be able to obtain working sessions.

By default, Mojolicious servers listen at http://*:3000, where * means all available interfaces, so you need to change this (localhost,, or [::1]) using either $ENV{MOJO_LISTEN} or the -l command line argument that the daemon allows.

Cross-Site Scripting

Style-sheets or script pages served by your application should not reveal application secrets (e.g., the current csrf_token).

Pages/templates should not incorporate scripts from untrusted sources.

Use a browser that enforces domain restrictions so that HTML page content and cookies are not observable from scripts running in other domains (most current browsers do this correctly).

Cross-Site Request Forgery

Since any webpage may potentially contain links, buttons, or forms targeting your application, you should use and check csrf_token on any requests that change application state, write to the local filesystem, or otherwise make use of privileges beyond those typically available in a browser scripting context.

Access From Other Local Processes

With this plugin loaded, $c->validation->csrf_protect will fail on sessions other from the one created by the browser launched.

If, however you choose to "allow_other_sessions" then it is assumed you have some independent means of authenticating them. Your application will be receiving requests from other local processes (including other browsers you may have installed). Special care will be needed if, e.g.,

  • You are concurrently running instances of alternative/out-dated browsers with cross-site scripting vulnerabiliities visiting untrusted sites that can exploit them.

  • There are other local users on your machine.

  • Your application runs setuid or otherwise invokes elevated privileges (which could then be used by other processes to bypass various protections).


Mojolicious::Plugin::Loco inherits all methods from Mojolicious::Plugin and implements the following new ones.


  $plugin->register($app, entry => '/', initial_wait => 15);

Register plugin in Mojolicious application. Key-value pairs give default option values for this application. Everything other than "config_key" may be overridden in the application configuration.


Mojolicious, Mojolicious::Guides,


Roger Crew <>


This software is copyright (c) 2018 by Roger Crew.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.