Skip to content

Custom Tags

You can extend Leaf to provide your own tags that add custom functionality. To demonstrate this, let's look at a basic example by recreating #uppercase together. This tag will take one argument, which is the string to uppercase.

When working with custom tags, there are four important things to know:

  1. You should call requireParameterCount() with the number of parameters you expect to receive. This will throw an error if your tag is used incorrectly.
  2. If you do or do not require a body, you should use either requireBody() or requireNoBody(). Again, this will throw an error if your tag is used incorrectly.
  3. You can read individual parameters using the parameters array. Each parameter will be of type LeafData, which you can convert to concrete data types using properties such as .string, .dictionary, and so on.
  4. You must return a Future<LeafData?> containing what should be rendered. In the example below we wrap the resulting uppercase string in a LeafData string, then send that back wrapped in a future.

Here’s example code for a CustomUppercase Leaf tag:

import Async
import Leaf

public final class CustomUppercase: Leaf.LeafTag {
    public init() {}
    public func render(parsed: ParsedTag, context: LeafContext, renderer: LeafRenderer) throws -> Future<LeafData?> {
        // ensure we receive precisely one parameter
        try parsed.requireParameterCount(1)

        // pull out our lone parameter as a string then uppercase it, or use an empty string
        let string = parsed.parameters[0].string?.uppercased() ?? ""

        // send it back wrapped in a LeafData
        return Future(.string(string))
    }
}

We can now register this Tag in our configure.swift file with:

services.register { container -> LeafConfig in
    // take a copy of Leaf's default tags
    var tags = defaultTags

    // add our custom tag
    tags["customuppercase"] = CustomUppercase()

    // find the location of our Resources/Views directory
    let directoryConfig = try container.make(DirectoryConfig.self, for: LeafRenderer.self)
    let viewsDirectory = directoryConfig.workDir + "Resources/Views"

    // put all that into a new Leaf configuration and return it
    return LeafConfig(tags: tags, viewsDir: viewsDirectory)
}

Once that is complete, you can use #customuppercase(some_variable) to run your custom code.

Note: Use of non-alphanumeric characters in tag names is strongly discouraged and may be disallowed in future versions of Leaf.