Child Fields
All objects can define children, by defining a field that has key value pairs. For instance, your archival_objects.toml file could define:
[post]
title = "string"
publish_date = "date"
template = "post"
[post.section]
video = "video"
image = "image"
content = "markdown"
Then, you could define a post with multiple sections:
title = "My Blog Post"
publish_date = "01/21/2024"
[[section]]
content = """
# Section Name
here is the content for the section
"""
[[section.video]]
name = "pete.mov"
filename = "pete.mov"
mime = "video/quicktime"
sha = "0f271906042978fdf19a7e1eae665b98d1e1f00169b69ae9cceb3b1c089490ed"
display_type = "video"
[[section]]
content = """
# Second Section
This section has a video
"""
[[section.image]]
sha = "9d18d8319c33ea2b10aeb6c833c1fe66304135e0e40aa6e509df5b6733fd3029"
mime = "image/png"
display_type = "image"
name = "creamy-meyer-lemon-pasta.png"
filename = "creamy-meyer-lemon-pasta.png"
When rendering this page, you can now iterate through the sections in our post template:
<div class="content">
<h1>post.title</h1>
<div class="post-info">
<h3>{{ post.date | date: "%B %d, %Y" }} —
</h3>
{% assign words = post.content | split: " " %}
<span>{{ words.size | divided_by: 233 | round | at_least: 1 }} Minute Read</span>
</div>
{% for section in post.section %}
<div>{{ section.content }}</div>
{% if section.video.url != "" %}
<video title="{{section.video.name}}" controls>
<source
src="{{section.video.url}}"
name="{{section.video.filename}}"
type="{{section.video.mime}}">
</video>
{% endif %}
{% if section.image.url != "" %}
<img
src="{{section.image.url}}"
alt="{{section.image.filename}}"/>
{% endif %}
{% endfor %}
</div>
These fields are supported in the archival editor but will display as tables, so depending on your use case you may want to use groups instead when creating lists.
A oneof inside a child
A child's own fields may be any type, including a oneof. That combination is how you define a repeating list whose entries are each one of several types — a post with several pieces of media, where every piece is either an image or a video.
Define the child's ordinary fields first, then the oneof:
[post]
title = "string"
template = "post"
[post.media]
alt = "string"
width = "number"
height = "number"
[[post.media.file]]
name = "image"
type = "image"
[[post.media.file]]
name = "video"
type = "video"
Every ordinary field has to come before the oneof. [[post.media.file]] opens a new toml table, so a key written after it belongs to that table. Move alt below the oneof and it becomes part of the last option rather than a field of the child — and archival does not complain, it simply isn't where you meant to put it. Run archival types if you want to see the shape archival actually read.
In an object file, each entry names the option in type and puts the file under value:
title = "A week in the shop"
[[media]]
alt = "The finished shelf"
width = 1600
height = 1200
[media.file]
type = "image"
[media.file.value]
sha = "9d18d8319c33ea2b10aeb6c833c1fe66304135e0e40aa6e509df5b6733fd3029"
mime = "image/png"
display_type = "image"
filename = "shelf.png"
name = "shelf.png"
[[media]]
alt = "Cutting the last board"
[media.file]
type = "video"
[media.file.value]
sha = "0f271906042978fdf19a7e1eae665b98d1e1f00169b69ae9cceb3b1c089490ed"
mime = "video/quicktime"
display_type = "video"
filename = "cut.mov"
name = "cut.mov"
Rendering iterates the child as usual and branches on the oneof's type:
{% for item in post.media %}
{% if item.file %}
{% if item.file.type == "video" %}
<video controls>
<source src="{{ item.file.value.url }}" type="{{ item.file.value.mime }}">
</video>
{% else %}
<img src="{{ item.file.value.url }}" alt="{{ item.alt }}">
{% endif %}
{% endif %}
{% endfor %}
The {% if item.file %} around the branch is not decoration. A row added in the editor but not yet filled in has no file at all, and reading item.file.type off nothing is a build error rather than an empty value — the same rule that makes unknown variables fail the build. The same guard is worth having on any optional child field.
Reach for this over two sibling fields — an image field next to a video field — whenever an entry should hold exactly one of them. Siblings let an entry hold both, or neither, and every template that reads them has to decide what that means. A oneof puts the choice in the type, and type tells your template which branch to take.