Library / SDK
playframework/twirl avatar
playframework/twirl

Twirl compiles Play templates into Scala before your build runs

Twirl is Play's default template engine

561 stars118 forksScalaApache-2.0

At a glance

What is it?
playframework/twirl is Play's default template engine, and it is not a runtime renderer: templates are turned into Scala source files by a source generator during compile, so template errors surface at build time. The same compiler is available outside Play through sbt-twirl, maven-twirl and gradle-twirl.
Who is it for?
Twirl fits teams that already write Scala and want templates checked by the compiler, and it is the only sensible choice inside a Play project since it ships as the default engine. It is a poor fit if you need a template language that does not leak Scala into the markup, or if you cannot accept the two-line version dance in the 1.6 and 2.0 lines.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Scala, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Play's template engine, usable on its own

Twirl is described simply as Play's default template engine, and the README is quick to add that it is automatically available in Play projects and can also be used stand-alone without any dependency on Play. That second sentence is the interesting one for adoption, because it means the template compiler is not locked to a Play application lifecycle. An sbt plugin is provided for integration with Scala or Java projects, and the repository also carries maven-twirl and gradle-twirl directories alongside the sbt one, with api/, compiler/ and parser/ directories holding the template API, the code generator and the parser respectively. The intended audience is a JVM team that wants HTML, JavaScript, XML or text templates that are checked by the Scala compiler rather than interpreted at request time. It is not a general purpose template language in the scripting sense, and the whole design follows from that. Understanding the compiler step is what tells you whether Twirl fits the way your team writes code.

Templates become Scala source during compile, not strings at runtime

The mechanism explains most of the project's behaviour. Twirl template files are expected under src/main/twirl or src/test/twirl, in the same style as scala or java sources, and the source locations can be configured. Files must be named {name}.scala.{ext}, where ext can be html, js, xml or txt. The Twirl template compiler is automatically added as a source generator for both the main/compile and the test configurations, so when you run compile or Test/compile the Twirl compiler generates Scala source files from the templates, and those generated sources are then compiled along with the rest of the project. The consequence is the important part. A template is not parsed and rendered while a request is in flight, it is translated into Scala code that the compiler type checks. A mistake in a template is a compile error, not a stack trace at runtime. The cost of that guarantee is that the template language is Scala, and whatever that implies about how expressions, imports and control flow inside your markup will look is not something the design tries to hide.

Adding sbt-twirl to a project and enabling it

The sbt plugin requires sbt 1.3.0 or higher. You add the plugin dependency in project/plugins.sbt, and the README gives the coordinates for the three lines that exist, which is worth reading carefully because the organisation changed over time:

scala
// twirl 2.0 and newer:
addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % "LATEST_VERSION")
// twirl 1.6:
addSbtPlugin("com.typesafe.play" % "sbt-twirl" % "1.6.1")
// twirl 1.5.1 and before:
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")

The plugin is then enabled on the projects that need it:

scala
someProject.enablePlugins(SbtTwirl)

If you have a single project and use a build.sbt file, the README shows how to create a root project and enable the plugin there instead:

scala
lazy val root = (project in file(".")).enablePlugins(SbtTwirl)

The LATEST_VERSION placeholder is meant to be replaced with the latest published version, which the README points to the Scala index page for. Publishing under three different organisation names across three version lines is a small thing that costs a newcomer ten minutes, and it is the kind of detail that a lock-in-free dependency deserves.

The Maven path uses a plugin and a separate API artifact

Maven is handled in a different shape from sbt, and the difference is worth noticing. Instead of enabling a plugin on a project, you add both the plugin and the Twirl API as dependencies in pom.xml, with the Scala version baked into the artifact names:

xml
<dependencies>
    <dependency>
        <groupId>org.playframework.twirl</groupId>
        <artifactId>twirl-api_${SCALA_VERSION}</artifactId>
        <version>${TWIRL_VERSION}</version>
    </dependency>
</dependencies>

The plugin itself binds a compile goal so the source generator runs during the Maven build:

xml
<plugin>
    <groupId>org.playframework.twirl</groupId>
    <artifactId>twirl-maven-plugin_${SCALA_VERSION}</artifactId>
    <version>${TWIRL_VERSION}</version>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

The TWIRL_VERSION placeholder is again replaced with the latest published version from the Scala index. The ${SCALA_VERSION} and ${TWIRL_VERSION} placeholders are properties you define, and because they appear in artifact identifiers rather than in values, a mismatch there produces a dependency resolution error rather than a compile error in your templates. Gradle is covered by the gradle-twirl directory in the repository, though the README excerpt here stops before that section.

templateImports and sourceDirectories are the two knobs

Once a template is in place, two settings do most of the customisation work. The first is templateImports, which adds imports to the Scala code inside your templates. In sbt it is set with the templateImports key, for example TwirlKeys.templateImports += "org.example._", and in Maven the same effect is achieved through the templateImports parameter in the plugin configuration, where an import element carries the package. The second is where the compiler looks for templates. The source directories for template files are configured with the compileTemplates / sourceDirectories key, and the README shows how to place template sources alongside Scala or Java source files:

scala
Compile / TwirlKeys.compileTemplates / sourceDirectories := (Compile / unmanagedSourceDirectories).value

The default locations, src/main/twirl and src/test/twirl, are what most projects never need to change, and the setting exists for teams with a non-standard layout. The templateImports key is the one you are more likely to need, because without it a template cannot see your domain model or helper types, and the symptom is a compile error in generated code rather than anything in your own source.

The limitations: a Scala-flavoured markup and a versioning dance

Twirl's constraints are visible in the documentation rather than hidden. The first is that the template language is Scala. Every expression in a template is a Scala expression, and the way to add your own types is to add imports, which means a template file is coupled to the Scala build rather than to a separate template runtime. Teams who want a template that a designer or a front-end engineer can edit safely, without knowing Scala, will find that separation hard to maintain. The second is the version matrix. The sbt plugin lives under org.playframework.twirl for Twirl 2.0 and newer, under com.typesafe.play for 1.6, and under com.typesafe.sbt for 1.5.1 and before, so an upgrade can mean changing the organisation as well as the number, and the README asks you to look the latest version up on the Scala index rather than shipping a fixed value. A third practical limit is the one the README is silent on. It does not document a rollback procedure for templates, nor how to run a template in isolation while developing it, so a team that wants fast visual feedback on a single page will find the compile-first workflow slower than a runtime renderer. There is also no mention of template caching, error pages or partial rendering, which is consistent with a design where templates are compiled code rather than a request-time subsystem.

Compile-time code generation versus a runtime renderer

The genuine alternative is a template engine that renders at request time, whether that is a string-based engine in another language, a runtime template implementation for Scala, or plain string interpolation in the application code. The difference in approach is the whole trade-off. A runtime renderer evaluates markup on each request, so you can change a template and see the result without a rebuild, but a mistake in the template surfaces in production rather than in your build, and the template's expressions are checked by nobody. Twirl inverts that, giving you compile-time errors and type checked expressions at the cost of a build step on every template edit and a markup file that contains Scala. The second difference is reach. Twirl is not tied to Play, and the presence of sbt-twirl, maven-twirl and gradle-twirl integrations means the same compiler can sit inside an existing JVM build with no web framework at all. If your team already lives in an sbt or Maven build and values compiler feedback, the cost of the compile step is usually worth paying. If the people editing templates are not Scala developers, it usually is not.

Editorial conclusion

Twirl fits teams that already write Scala and want templates checked by the compiler, and it is the only sensible choice inside a Play project since it ships as the default engine. It is a poor fit if you need a template language that does not leak Scala into the markup, or if you cannot accept the two-line version dance in the 1.6 and 2.0 lines. Before wiring it into a non-Play build, pin an explicit version instead of the LATEST_VERSION placeholder, since the sbt and Maven coordinates differ between Twirl 1.6 and Twirl 2.0.

Frequently asked questions

Where do Twirl template files go and how must they be named?

Template files are expected under src/main/twirl or src/test/twirl, and must be named {name}.scala.{ext} where ext can be html, js, xml or txt. The source locations can be configured through the compileTemplates / sourceDirectories key.

How do I add the Twirl sbt plugin to a non-Play project?

Add the plugin dependency in project/plugins.sbt, using org.playframework.twirl for Twirl 2.0 and newer, com.typesafe.play for 1.6 or com.typesafe.sbt for 1.5.1 and before, then enable it with someProject.enablePlugins(SbtTwirl). The plugin requires sbt 1.3.0 or higher.

Does Twirl render templates at request time?

No. The Twirl compiler is added as a source generator, so running compile or Test/compile generates Scala source files from the templates and compiles them with the rest of the project. Template problems therefore surface during the build.

How do I add my own imports to Twirl templates?

Use the templateImports key, for example TwirlKeys.templateImports += "org.example._" in sbt. In Maven the same thing is done through the templateImports parameter in the plugin configuration, with an import element naming the package.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. playframework/twirl on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/playframework-twirl.svg)](https://hysenlabs.com/projects/playframework-twirl)

Was this useful?