Customize Frontmatter Fields in NetlifyCMS
Tags: cms, ssg, bridgetown, netlifycms
Published: 2020-09-09 12:39:13

Recently, I was setting up a [landing page](https://www.crowsnestapp.com) for a project in [Bridgetown](https://www.bridgetownrb.com/) (highly recommended!). Later on, I began adding a blog section, and since I envisioned myself creating more content, played around with NetlifyCMS as a lightweight CMS alternative.

I followed @andrewmcodes excellent tutorial here: {% link https://dev.to/andrewmcodes/creating-a-blog-with-bridgetown-and-netlify-cms-1d1a %}

The rest of this post builds up on his groundwork, so if you haven't, go read it first!

## Customize Frontmatter Metadata

Every blog has its own set of metadata for its posts, but most often they include a subset of

- title
- description
- categories
- tags
- featured image
- post author
- publish date

A quick review of the [NetlifyCMS docs](https://www.netlifycms.org/docs/configuration-options/#fields) revealed that they are managed by a YAML key called `fields` that is part of a `collection` definition.

Every field at the least needs to possess

- a `label` (that's how NetlifyCMS will label the field)
- a `name` (that's how your metadata will end up in your frontmatter), and
- a `widget` (which translates roughly to the data type and determines how your field is displayed by NetlifyCMS)
- and, depending on the chosen `widget`, a set of other sub-keys (consult the [NetlifyCMS docs](https://www.netlifycms.org/docs/widgets/) for more precise instructions)

So here's how that might look in your NetlifyCMS `config.yml` file:

```yaml
# ...

collections:
  - name: blog
    # ...
    fields:
      - { label: Layout, name: layout, widget: hidden, default: post }
      - { label: Title, name: title, widget: string }
      - { label: Description, name: description, widget: text }
      - { label: Featured Image, name: image, widget: image }
      - { label: Categories, name: categories, widget: list }
      - label: Author
        name: author
        widget: object
        fields:
          - {label: Name, name: name, widget: string}
          - {label: Email, name: email, widget: string}
      - { label: Publish Date, name: date, widget: datetime }
      - { label: Body, name: body, widget: markdown }
```

Observe that the `author` field uses a widget of type `object`, which necessitates to define which `fields` that object (the YAML key/value pairs that make up this field) includes. After committing these configuration changes, your post editor might look like this:

![Metadata in NetlifyCMS](https://dev-to-uploads.s3.amazonaws.com/i/yftot6e81hr7iv0k62l4.png)

## Referencing Authors

We can do better though, than to manually add an author's name and email every time. Imagine your blog has a couple dozen authors, you're quickly going to mistype something and sabotage any backlinks to the author. Luckily, NetlifyCMS knows a [relation](https://www.netlifycms.org/docs/widgets/#relation) widget, allowing to reference a different collection.

So, let's set up an `authors` collection. In Bridgetown, everything you need to do is [add it to your config](https://www.bridgetownrb.com/docs/collections):

```yaml
collections:
  - authors
```

If you want, you can even have it [output author pages automatically](https://www.bridgetownrb.com/docs/collections#output). Now we have to reference this collection in our NetlifyCMS `config.yml`, too:

```yaml
collections:
  - name: authors
    label: Authors
    folder: src/_authors/
    extension: .md
    format: frontmatter
    create: true
    fields:
      - {label: Name, name: name, widget: string}
      - {label: Email, name: email, widget: string}

  # ...

  - name: blog
    # ...
    fields:
      # ...
      - label: Author
        name: author
        widget: relation
        collection: authors
        valueField: email
        displayFields: ["name"]
        searchFields: ["name", "email"]
```

The alert reader will have noticed that our collection definition resembles exactly our `object` definition from above. In our `blog` collection (shown below) we now reference the author as a `relation` widget, which requires a few more keys:

- the `collection`
- a `valueField` (which will be stored as the frontmatter value)
- a `displayFields` array (the fields displayed in the UI widget), and
- a `searchFields` array to allow for interactive lookup of authors.

Now for starters, let's add an author in `src/_authors/julian-rubisch.md`:

```yaml
---
name: Julian Rubisch
email: ...
---
```

After a refresh, our widget now looks like this:

![Author Relation Widget](https://dev-to-uploads.s3.amazonaws.com/i/ds2emv5ahs5hn550758z.png)

If you save this post in NetlifyCMS, you will find your frontmatter filled out comfortably for you:

```yaml
title: ...
description: ...
image: /images/uploads/....jpeg
categories:
  - Blog
author: .....
date: 2020-08-30T08:08:46.681Z
```

## Render it in Bridgetown

The only remaining task is to actually render these metadata in your liquid tags, and for the new `authors` collection, we need a little tweak. To find the corresponding post author object out of your collection, you need to do something like

```liquid
{% assign author = site.authors | where: "email", post.author | first %}
...
{{ author.name }} {{ author.email }}
```

in your post layout.

## Bonus: A Custom Liquid Filter

To my eye, this reads clunky enough to warrant its own Liquid filter. Praise the Ruby gods, Bridgetown comes with a handy DSL to accomplish this. In `plugins/builders/filter.rb`, key this in:

```ruby
class Filters < SiteBuilder
  def build
    liquid_filter "find_author_by_email", :find_author_by_email
  end

  def find_author_by_email(email)
    site.collections["authors"]
      .select { |author| author.email == email }
      .first
  end
end
```

This will allow your liquid to boil down to:

```liquid
{% assign author = post.author | find_author_by_email %}
...
{{ author.name }} {{ author.email }}
```

Notabene: a forthcoming feature in Bridgetown will make this even easier:
{% github https://github.com/bridgetownrb/bridgetown/pull/101 %}