Skip to content

Custom Tags

You can create custom Leaf tags using the LeafTag protocol.

To demonstrate this, let's take a look at creating a custom tag #now that prints the current timestamp. The tag will also support a single, optional parameter for specifying the date format.

Tip

If your custom tag renders HTML you should conform your custom tag to UnsafeUnescapedLeafTag so the HTML is not escaped. Remember to check or sanitize any user input.

LeafTag

First create a class called NowTag and conform it to LeafTag.

struct NowTag: LeafTag {
    func render(_ ctx: LeafContext) throws -> LeafData {
        ...
    }
}

Now let's implement the render(_:) method. The LeafContext context passed to this method has everything we should need.

enum NowTagError: Error {
    case invalidFormatParameter
    case tooManyParameters
}

struct NowTag: LeafTag {
    func render(_ ctx: LeafContext) throws -> LeafData {
        let formatter = DateFormatter()
        switch ctx.parameters.count {
        case 0: formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        case 1:
            guard let string = ctx.parameters[0].string else {
                throw NowTagError.invalidFormatParameter
            }

            formatter.dateFormat = string
        default:
            throw NowTagError.tooManyParameters
        }

        let dateAsString = formatter.string(from: Date())
        return LeafData.string(dateAsString)
    }
}

Configure Tag

Now that we've implemented NowTag, we just need to tell Leaf about it. You can add any tag like this - even if they come from a separate package. You do this typically in configure.swift:

app.leaf.tags["now"] = NowTag()

And that's it! We can now use our custom tag in Leaf.

The time is #now()

Context Properties

The LeafContext contains two important properties. parameters and data that has everything we should need.

  • parameters: An array that contains the parameters of the tag.
  • data: A dictionary that contains the data of the view passed to render(_:_:) as the context.

Example Hello Tag

To do see how to use this, let's implement a simple hello tag using both properties.

Using Parameters

We can access the first parameter that would contain the name.

enum HelloTagError: Error {
    case missingNameParameter
}

struct HelloTag: UnsafeUnescapedLeafTag {
    func render(_ ctx: LeafContext) throws -> LeafData {
        guard let name = ctx.parameters[0].string else {
            throw HelloTagError.missingNameParameter
        }

        return LeafData.string("<p>Hello \(name)</p>")
    }
}
#hello("John")

Using Data

We can access the name value by using the "name" key inside the data property.

enum HelloTagError: Error {
    case nameNotFound
}

struct HelloTag: UnsafeUnescapedLeafTag {
    func render(_ ctx: LeafContext) throws -> LeafData {
        guard let name = ctx.data["name"]?.string else {
            throw HelloTagError.nameNotFound
        }

        return LeafData.string("<p>Hello \(name)</p>")
    }
}
#hello()

Controller:

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