Play’s Liaison with sbt

Have you ever wondered why you no longer play on the command line1, but execute sbt or activator? And why is Play only ever referred to in plugins.sbt and not your build.sbt’s libraryDependencies, but still, you can use its API in your code? And how does Play - run?

The sbt Plugin

The Playframework is separated into multiple modules, and with each new version, more parts of it are factored out into their own modules. The best known is simply called play - it contains the API that you as a developer interact with when developing a Play application.

The part that is running your application is actually an sbt plugin, and you can find it in the aptly named module sbt-plugin.

All plugins that your build uses must be specified in project/plugins.sbt, and are then in scope in each sub-project. To be more precise, Play is an auto-plugin2. As the name suggests, auto plugins can enable themselves automatically. Play does not use this feature however - because you want to specifically enable either the Scala or the Java plugin.

// Use Play with Scala
lazy val root = (project in file(".")).enablePlugins(PlayScala)
// Use Play with Java
lazy val root = (project in file(".")).enablePlugins(PlayJava)

This also means that the play command from previous versions was a mere wrapper around sbt, and if you want to restore it, you can simply alias sbt to play in your shell.

Auto plugins have a couple of other cool features that Play makes use of.

For one, the Play plugin has dependencies on a couple of other sbt plugins that are also enabled when using Play:

  • sbt-twirl for compiling templates to Scala code,
  • sbt-js-task to include sbt-web and execute JavaScript,
  • the routes compiler which turns the routes file into code,
  • and sbt Native Packager to create an executable package of your application.

Auto-plugins have the ability to import values into the build. Play uses this to import some shortcuts for dependencies on Play modules which is why you can write libraryDependencies ++= Seq(ws, filters).

Here is the full list from play.sbt.PlayImport:

  • evolutions
  • jdbc
  • javaCore (added automatically when you depend on PlayJava)
  • javaJdbc
  • javaJpa
  • filters
  • cache
  • json
  • ws
  • javaWs
  • specs2

Lastly, as a gimmick you can add emojiLogs to your build to have emojis show up in your test and build results.


Auto-plugins are able to add settings to a project that enables them. Settings in sbt can always be overridden in the build.sbt, so these are only defaults.

It is here that Play adds its API to your libraryDependencies via "" %% "play-server" % <current version> which in turn depends on the play module.

The Java and the Scala APIs are imported by both plugins equally. The distinction between the Java and Scala plugin mostly applies to handling templates, routing, dependency injection, and form validation.


There are two more auto-plugins that come with play, PlayNettyServer, which enables itself automatically when Play is enabled on a project, and the alternative to it, PlayAkkaHttpServer.

In order to use the experimental Akka HTTP, you have to disable Netty first:

lazy val root = (project in file("."))

sbt already has the notion of running a main class, which is normally an object containing a main method, or an object extending App. Here, Play uses sbt’s scoping in a clever way. When you check the value of the mainClass setting in your Play project, you might be surprised at first.

$ show mainClass
[info] Some(play.core.server.ProdServerStart)

This mainClass setting, scoped to the Compile configuration, is used by Native Packager, so the binary executable will start it in production mode.

The development server is scoped to the run task, and so is effective when you execute sbt run.

$ show run::mainClass
[info] Some(play.core.server.DevServerStart)

You might notice that DevServerStart does not contain a main method. When we inspect the run task we can see its description still stems from the original sbt setting, however it is defined in Play’s sbt settings, where it is overridden by Play’s own playRunTask which in turn lives in PlayRun.

$ inspect run
[info] Input task: Unit
[info] Description:
[info] 	Runs a main class, passing along arguments provided on the command line.
[info] Defined at:
[info] 	(play.sbt.PlaySettings) PlaySettings.scala:93

PlayRun then uses the run:::mainClass setting to find DevServerStart and has it start the development server (either Netty or Akka), and identifies itself by printing the famous --- (Running the application, auto-reloading is enabled) ---.

Next up are run hooks (once before and once after the server has been started, see below), and handling hot reloading the server, meaning when you change code while developing and you refresh the browser, not only is the code recompiled but also any previous Application is stopped and a new Application is loaded (via the ApplicationLoader) with code changes applied.

In continuous mode (sbt ~run) sbt automatically recompiles the source code as soon as you change it which results in a faster reload when refreshing the browser. This also applies to templates, routes, and other files monitored by Play.

Finally, you can have sbt refresh the browser automatically by using James Ward’s Play Auto Refresh plugin. Browser plugins are available for Chrome and Safari.

Running in Production

In production, sbt is only used to package your project based on Play’s settings and your own build files. The produced run script is not specific to Play at all but uses the JavaAppPackaging layout provided by Native Packager. Play just overrides a couple of Native Packager’s default settings so the latter knows where to find resources, documentation, and, as shown above, the main class.

When considering production, the fact that Play is an sbt-plugin is only relevant at build time - at runtime, we just have a Java process with all dependencies and resources on the classpath.

Play Layout Plugin

Another plugin that enables itself when Play is loaded is the PlayLayoutPlugin. It is thanks to this plugin that Play uses the Rails-inspired app/ layout. If you want to use the standard Maven layout (which most non-Play projects do), you have to explicitly disable this plugin.

lazy val root = (project in file("."))

As for the views, you can put them into src/main/twirl and then add that folder to Play’s monitored files (also see below in Play Keys).

PlayKeys.playMonitoredFiles ++= (managedSourceDirectories in (Compile, TwirlKeys.compileTemplates)).value

Apparently this only works when running sbt in continuous mode, i.e. sbt ~compile or sbt ~run.

Also note that when you disable this plugin, sbt dist will put its produced zip file in Native Packager’s default folder target/universal, and no longer in dist.

Play Logback Plugin

Play uses Logback for logging by default. If you want to use log4j instead, you can disable the PlayLogback plugin and add the log4j dependencies and configuration files.

Will Sargent has a complete example on GitHub.

sbt Keys

Play defines its own set of sbt Keys.


playDefaultPort (default 9000) and playDefaultAddress ( should rather be overridden with environment vars -Dhttp.port and -Dhttp.address.

playRunHooks allows to you run tasks when Play’s run task is started or stopped. This is especially useful for plugins, but only applies to development mode.

val openInBrowser = Def.setting {
  PlayRunHook.makeRunHookFromOnStarted { address =>
    val uri = new URI("http://localhost:" + address.getPort)

PlayKeys.playRunHooks += openInBrowser.value

assetsPrefix - The folder where sbt-web assets will land, by default public/.

devSettings - Key-Value pairs of custom settings that are only read in development mode. You can use these to override the HTTP/s port (play.server.http.port / play.server.https.port) or address (play.server.http.address).


playGenerateSecret spits out a newly generated application secret to the sbt console, while playUpdateSecret puts a new secret directly in the application.conf.

playMonitoredFiles - determines which files are monitored by Play; a task because it filters subfolders. This setting is important when you are writing plugins for custom resource folders and want to trigger a reload in dev mode when one of those files has changed.

playPackageAssets - Packs all assets and put them in a JAR.

playDocsJar Generates the full Play documentation plus reference.confs, bundles them in a JAR and publishes them locally.

Play without sbt

Finally, using Play without sbt is also supported using Gradle or Maven. There is support planned for Play in cbt as well.

Playframework sbt Command Cheat Sheet

I’ve compiled a cheat sheet that lists all of Play’s commands, and also those from sbt, Native Packager, and sbt-web that are either used by Play, or useful when developing a Play application.

Sign up below to receive it.

Email Format
  1. From version 2.0 to 2.2, Play was started using play on the command-line. That seemed too obvious so it had to be changed ;)
  2. For completeness sake, all sbt plugins are now auto-plugins.

Share post


News posts directly in your newsreader.

Subscribe feed