TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

Vapor + Leaf templating cheat sheet

The least you need to know to render great templates

Paul Hudson       @twostraws

Leaf is Vapor’s very own template rendering framework, and provides a variety of tools to help us control layouts in a flexible, efficient way. If you’ve never used templates before, they are effectively the “V” in Vapor’s MVC – they contain HTML with a minimal amount of logic, meaning that we don’t try to write HTML inside our Swift code and instead leave that to Leaf.

In this article we’re going to look at the variety of tags that Leaf gives us to work with – as you'll see, there's a lot they can do!

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Setting up a sandbox

If you already have a Vapor project up and running you can mostly skip this step. If you just want to experiment, this is for you.

Before we can start with any code, we need to install the Vapor toolkit. This lets us build, test, and deploy Vapor projects easily, and it’s available through Homebrew.

So, first you need to install Homebrew. If you run “brew” from the command line and see “command not found” then it means you don’t have it installed and should run this command:

/usr/bin/ruby -e "$(curl -fsSL"

Note: that will ask for your password.

Once Homebrew is ready, you can install the Vapor toolkit using this command:

brew install vapor/tap/vapor

You can check that the toolkit is installed by running vapor --help – you should get some useful information back.

Now that you have the Vapor toolbox ready, we can create an empty Vapor project that we can use for experimenting. Run this command:

vapor new LeafTest --template=twostraws/vapor-clean
cd LeafTest

That creates a new Vapor project called LeafTest, asking it to clone my example project from GitHub. Don’t worry: that example project contains the absolute least amount of code required to set up a Vapor server.

Adding Leaf to your project

Adding Leaf takes two steps. First, open the Package.swift file for your project and modify its dependencies to this:

.package(url: "", .upToNextMinor(from: "3.1.0")),
.package(url: "", .upToNextMinor(from: "3.0.0")),

You’ll also need to modify its target dependencies to this:

.target(name: "App", dependencies: ["Vapor", "Leaf"]),

To avoid confusion, here’s how the full file should look:

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "LeafTest",
    dependencies: [
        // A server-side Swift web framework.
        .package(url: "", .upToNextMinor(from: "3.1.0")),
        .package(url: "", .upToNextMinor(from: "3.0.0")),
    targets: [
        .target(name: "App", dependencies: ["Vapor", "Leaf"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"]),

Now run vapor xcode from the command line, to have Vapor fetch all the dependencies and generate an Xcode project. When it finishes, open the project in Xcode. In order to run the project, you need to activate the Run scheme, so go to the Product menu and choose Scheme > Run.

The second step is to prepare Vapor to render Leaf templates. This is done by registering a LeafProvider instance with Vapor’s services system, so that we can create Leaf templates in any of our routes.

So, open configure.swift, add import Leaf to the top, then place a LeafProvider service inside the configure() function:

try services.register(LeafProvider())

We can now tell Vapor to use Leaf for rendering its views, so add this line below the previous one:

config.prefer(LeafRenderer.self, for: ViewRenderer.self)

That’s all our configuration complete, so let’s dive into some code examples. If you’re using the LeafTest project, you can add these directly to the “hello” route inside routes.swift – just replace the code there with each example.

Rendering simple templates

Rendering a template is made up of two parts: some Leaf code that contains your HTML, plus some Swift code to select the template file and pass in any customization.

To start with, let’s just render a template without any customization. Open routes.swift, add import Leaf there too, and replace the existing “hello” route with this:

router.get("hello") { req -> Future<View> in
    return try req.view().render("home")

That asks Vapor to find and render the file “home.leaf”. We haven’t created that yet, so let’s do that now.

If you’re using my vapor-clean template you’ll find a directory called Resources alongside your Xcode project, and inside that you’ll find one called Views – that’s where we need to place our Leaf templates. If you’re working in your own project, please create Resources/Views now, making sure to use a capital R in Resources and a capital V in Views.

Inside the Views directory, create a file called home.leaf and give it this content:

<p>Hello, Vapor!</p>    

That just prints out some flat HTML, which is fine for now – press Cmd+R to build and run your Vapor server, then visit http://localhost:8080/hello. If everything has worked correctly, you should see “Hello, Vapor!” in your browser.

Sending in values from Swift

You can fetch and format as much data as you want in your Swift code, then pass the finished data to Leaf to be rendered however you want. For example, we might have a dictionary of data like this:

let values = [
    "name": "Taylor Swift",
    "city": "Nashville",
    "vaporSkill": "11/10",

In order to pass that to Leaf for rendering, we just need to send it as a second parameter to render(). This is called the template’s context, and should contain any variables you want Leaf to user. For example:

return try req.view().render("home", values)

Over in our Leaf template, we can read any of those values by using #(variableName), like this:

<p>Name: #(name)</p>
<p>City: #(city)</p>
<p>Vapor skill: #(vaporSkill)</p>

Adding conditions

Leaf has an #if tag that lets us check conditions, such as whether a variable has a specific value. For example, we could send in a score value like this:

return try req.view().render("home", ["score": 80])

And in the Leaf template we can check the value of score like this:

#if(score > 70) {
    <p>Great score!</p>
} else {
    <p>Try again.</p>

If you use a variable name without a value to check against, it will be true if the variable has any value at all. For example:

#if(feedback) {
    <p>Here's the feedback on your test: #(feedback).</p>
} else {
    <p>There was no feedback given.</p>

We didn’t provide a feedback variable in our context, so that will print “There was no feedback given”.

Working with arrays

You can send arrays into Leaf, but you do need to wrap them in a dictionary so they have a variable name attached.

For example:

let band = ["John", "Paul", "George", "Ringo"]
return try req.view().render("home", ["band": band])

That will make the band variable available inside Leaf, and we can use the #for tag to loop over the values and print them out, similar to in Swift:

#for(member in band) {

You can read the size of an array by using the #count tag, like this:

<p>There are #count(band) members in the band.</p>

There’s also a #contains tag, which lets you check whether an array contains a specific value. For example:

#if(contains(band, "Taylor")) {
    <p>Taylor is in the band!</p>
} else {
    <p>This band is Taylor-free.</p>

Loop variables

Inside a for loop you can read information about the loop’s progress using three special variables: isFirst is true when the current iteration is the first one, isLast is true when it's the last iteration, and index will be set to the number of the current iteration, counting from 0.

For example, if you pass in the following context:

let pythons = ["John", "Michael", "Eric", "Graham", "Terry", "Terry"]
return try req.view().render("home", ["pythons": pythons])

We could then print that out using the following Leaf code:

#if(pythons) {
        #for(python in pythons) {
            #if(isFirst) {
                <li>The first member is #(python)</li>
            } else if (isLast) {
                <li>The last member is #(python)</li>
            } else {
                <li>Member number #(index + 1) is #(python)</li>

Notice that I use #(index + 1) to make a more natural display.

Text formatting

Leaf has three tags that let you adjust the letter case of strings: #capitalize to capitalize the first letter, #uppercase to make the whole string uppercase, and #lowercase to make the whole string lowercase.

For example, given the following context:

return try req.view().render("home", ["name": "justin"])

…we can format name in various ways:

<p>#capitalize(name) will print Justin</p>
<p>#uppercase(name) will print JUSTIN</p>
<p>#lowercase(name) will print justin</p>

Formatting dates

If you’re passing a Date instance into a Leaf template, you can format it dynamically by using the #date tag. This takes two parameters: the first is the date variable you want to format, and the second is the date format string you want to use.

To try this out, first send a date into the template, like this:

return try req.view().render("home", ["now": Date()])

You can then format that however you want in Leaf. For example, if you wanted to print out a date like 2018-12-21, you might use this:

<p>#date(now, "y-MM-dd")</p>

Printing raw HTML

If you’ve generated some HTML in Swift and want to pass that straight to Leaf, you’ll hit a problem: Leaf automatically escapes HTML so you can’t introduce problems by accident. If you specifically want the value of a variable to be printed, even when it includes HTML, you should use #get.

First, pass in the HTML as your context:

return try req.view().render("home", ["html": "<h1>Hello</h1>"])

Now use #get with your variable name to make the <h1> be treated as a top-level heading:



You can add comments to your Leaf templates in two ways. Multi-line comments look like this:

    This is a comment and will be ignored.

And single-line comments look like this: #// comment here.

Embedding pages

You can embed one template inside another using the #embed tag. The template you embed gets the same variable context as its parent, so you can configure them both using Swift. Even better, you can define various parts of your child template that should be slotted into various places in the parent template.

As an example, we could put this into a template file called master.leaf: it has a header and footer, but leaves space in the middle for child templates to define their own page body:


The #get line means “get the value of the body block,” so we need to make a child template set that value somewhere before it embeds the parent. For example, we might write this:

#set("body") {
    <p>This is the child template.</p>


That will print the master header, the child body, then the master footer – it’s the best way to make sure all your pages share the same basic page structure.

Custom context objects

So far we’ve been sending in Swift dictionaries for page context, but for larger sites you might find it better to define a custom struct and send that in instead. This gives you all the bonus safety of structs, but works the same as far as Leaf is concerned.

When you’re just starting out, you’ll likely find it easier to define your structs inside your routes to keep your code more organized, but as you expand you’ll start sharing your struct definitions across requests and that’s fine too.

To send a custom struct for template context, just make sure you mark it as Codable, like this:

router.get("hello") { req -> Future<View> in
    struct User: Codable {
        let username = "Taylor Swift"
        let age = 26
        let city = "Nashville"

    return try req.view().render("home", User())

The properties inside the struct will immediately be made available as Leaf variables, like this:

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.5/5

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.