Content filters in Feather CMS

In this article I'll tell you all about content filters and show you how to build your own one using hooks functions and Vapor.


The anatomy of a content filter

When you create a blog post or a static page in Feather you can use the markdown filter to render the final representation of the stored content. It is also possible to highlight Swift code snippets though another filter. These content filters are altering the underlying data in dynamic way at runtime, Feather only saves the original data in the persistent srorage using Fluent. 💪

This approach allows us to transform various text values using manually selected filters for each individual frontend related content. For example you can enable the markdown filter for post A, but if you prefer to use HTML for post B you can disable the markdown filter and write your article using the good old HyperText Markup Language.

Content filters can be used to create your own shortcodes. In Wordpress shortcode is a small piece of code, indicated by brackets, that can perform a dedicated function. By using Feather you don't have to put shortcodes into brackets, but you can replace anything you want based on your own rules. Curse words? No problem. Print something using Swift? Why not.

The only thing that you have to be aware that content filters are running in a synchronous way. Be as fast as possible, since they will block the execution thread. In most of the cases this is not a big deal, but I just wanted to let you know that you won't be able to return a future this time. 🚀

How to create a content filter for Feather?

Content filters are provided by modules, this means that you have to write a Feather CMS module first. Don't worry too much, a module can be quite simple, in our case it's just going to be one Swift file. We are going to write a filter that's going to replace the fuck word with a duck emoji. 🦆

import Vapor
import Fluent
import ViperKit

final class DuckFilterModule: ViperModule {

    static var name: String = "duck-filter"

    func invokeSync(name: String, req: Request, params: [String: Any]) -> Any? {
        switch name {
        case "content-filter":
            return [DuckFilter()]
            return nil

Just place the code from above into a new file called DuckFilterModule.swift inside the Modules directory. We have to give the module a name, that's going to be duck-filter of course, and we have to implement the invokeSync hook function that should return a filter type for the content-filter key. You can even return multiple filters per module if needed.

Hook functions are quite essential in Feather CMS, modules can work together through dynamic hooks without forming a dependency. This approach is very flexible and powerful, you can build and invoke your own hooks through the ViperKit framework. If you want to learn more about this modular architecture, you can also grab a copy of my Practical Server Side Swift book, it's written for Vapor 4 and you'll learn how to write a modular blog engine using Swift (similiar to Feather).

Anyway, back to the topic, we just have to implement the DuckFilter object:

import Vapor
import ViperKit

struct DuckFilter: ContentFilter {
    var key: String { "duck-filter" }
    var label: String { "Duck" }

    func filter(_ input: String) -> String {
        input.replacingOccurrences(of: "fuck", with: "🦆")

Basically that's it. You just have to alter the configure.swift and append a DuckFilterModule() instance to the module list. Now if you run Feather with this module you can enable the Duck filter for your contents under the content editor admin interface. Oh by the way you can save this filter under a ContentFilters/DuckFilter.swift directory next to your module file.

How to invoke available filters?

Let's try out our newly created filter. Go to the admin and create a new static page with the "I don't give a fuck" content, well... it can be anything that contains the f word, just be creative. 🤔

Save the page and preview it using the eye icon. You should see your original text. Now if you click on the feather icon you can select which filters should be enabled for this particular content. Check the Duck, save the content details and reload the preview page. You should see the duckling. 🐥

These content filters are programmatically available for you. By calling the filter method on any FrontendContentModel type you can invoke the enabled filters on that content, this will allow you to transform an associated model value using filters.

let filteredValue = frontendContentModel.filter(myModel.contentToBeFiltered, req: req)

The only catch is that your Fluent model needs to have an associated content type since filters are tied to FrontendContentModel objects. In my previous article I mentioned how to create a content relation, but maybe next time I'll create a longer post about the existence of this special object in Feather CMS. If you have issues, feedbacks or ideas, please use GitHub or Twitter.

Share this article on Twitter.
Thank you. 🙏

Picture of Tibor Bödecs

Tibor Bödecs

Creator of (weekly Swift articles), server side Swift enthusiast, full-time dad. -- Follow me & feel free to say hi. 🤘🏻 -- #iOSDev #SwiftLang

Twitter · GitHub


100% Swift news, delivered right into your mailbox

Subscribe to my monthly newsletter. On the first Monday of every month, you'll get an update about the most important Swift community news, including my articles.