Adding Data to Your 11ty Website
In the last chapter, we set up the basics of an 11ty website. In this chapter, we’ll give it superpowers by exploring the many ways that 11ty allows us to add static and dynamic data to our templates, layouts, and pages.
At the heart of any website is data. Whether that’s content, structured data, or third-party information, getting data onto a page is the most important thing any site generator can do. By adding a data layer to the simple website we built in Chapter 1, we’ll see how the flexibility of 11ty extends to data via its Data Cascade feature, and how this provides us with great power for customizing our various layouts, includes, and pages.
In this chapter, we’ll be covering the following topics:
- Understanding the 11ty Data Cascade
- Adding data for each page
- Adding data to external files
- Adding global data
Technical requirements
The code in this chapter builds on the site that we built in Chapter 1. If you didn’t follow along with that chapter, you can find the complete code in the companion GitHub repository: https://github.com/PacktPublishing/Eleventy-by-Example/tree/main/project-1/chapter-2/start.
Understanding the 11ty Data Cascade
Before we can dive into the code, we need to discuss how 11ty handles data.
Much like 11ty’s template options, the way 11ty handles data is also very flexible. With that flexibility comes some extra complexity. 11ty allows you to add data at every layer of your project, in page files, layout files, directories, and globally. Based on that, there’s a specific order and precedence in which it ingests the data along the way. This is called the 11ty Data Cascade. Let’s take a look at data sources in order of highest priority to lowest priority. At each step along the way, data can either be merged or overridden.
Lowest-priority, most generic data—such as a global data file—is the first to be computed. This gives us access to that data to be used or mutated by higher-priority, more specific data—such as computed data.
Computed data
While we won’t cover computed data in this chapter, it is the data with the highest priority and the last to run. 11ty computed data is run at the page or template level and has access to various data variables that are already available. It can be used for things such as generating page permalinks, working with the navigational structure, or anything else that requires additional data to be already compiled.
Page frontmatter
We covered the frontmatter a little in Chapter 1 as a way of declaring what layout our pages will use. The frontmatter can also be used to add page-specific data to each page template. Examples include titles, publish dates, descriptions, and data required for the page, such as banner content and promotional space content.
Template data files
Template data files are specific files of JSON or JavaScript data that are paired by name with specific pages. This can make for a better developer experience than just using frontmatter data on an individual page. These data files need filenames that match the template name.
Directory data files
Directory data is shared between multiple pages in a specific directory in your project. This can be used to share things such as layouts and parent IDs between various pages within a section of your site. For deeply nested directories, the data from parent directories is also available within the deeply nested template. This data file needs to match the directory it lives within.
Layout frontmatter
Layouts in 11ty are just chained templates. So, any page or layout can have frontmatter. In the context of a layout, the frontmatter is the same as a page, but a page can override its layout. The layout frontmatter can be helpful for creating template inheritance features to show different data on each section of your site based on the layout.
Configuration API global data
You used the 11ty configuration API to set up your 11ty instance in Chapter 1, but it can also be used to store global data. The addGlobalData
method on the configuration object can be used to create regular global data but is best used to create data source plugins for various APIs and add data programmatically to the 11ty data stack.
Global data files
The lowest-priority data is global data. These files are typically JavaScript or JSON files and are stored by default in the src
directory of your project. This data is accessible to any template, layout, include, or page in your project. These files are great for data that should be fetched or created once and used in multiple places.
With the basics in hand, let’s begin by making our templates and includes more dynamic. To start, we’ll add individual page data that can be used in our base template, as well as in includes.
Adding data for each page
The home page of our website has a large banner at the top. It would be great to be able to reuse the HTML from that banner on the About page as well. If we move the HTML from the home page into an include and use it in both places, the headline and banner text will be identical. That’s where page data comes in.
Adding variable data to the home page
In the index.html
file, we already have the YAML frontmatter that we used when setting up the layout in Chapter 1. This is where the page data lives.
To add additional variables, we can follow the same format as we use for the layout
variable and add a new line to the frontmatter. This time, add a title
and bannerContent
variable. Each of these will contain a string that will be used by the templates:
--- layout: "layouts/base.html" title: "This is my homepage" bannerContent: "This is my banner content" ---
These two new variables are accessible inside of the page, the layout controlling the page, and the includes that are included on the page.
To start, replace the static HTML in index.html
with Liquid variable expressions, as we discussed in Chapter 1 for the {{ content }}
variable. Any variable entered into the frontmatter of a page is accessible by the key given to it:
<section class="banner"> <h1>{{ title }}</h1> <p>{{ bannerContent }}</p> </section>
Now that we have the content as data in our page, we can move the HTML from the page into an include placed in the base layout.
Copy the markup from the home page and move it into a new file named banner.html
in the src/_templates/includes
directory. Your directory structure should now be updated.

Figure 2.1 – banner.html should now be in the includes directory
No noticeable changes should happen on the home page. Once the file is added, we can include banner.html
in the base.html
layout file with the following code:
{% include "includes/banner.html" %}
At this point, we can modify the About page to handle the same data.
This HTML is now on every page using the base.html
layout. That means we have access to it on the About page as well as the home page. Right now, there’s no data, so the h1
and p
elements will appear on the page but will be empty. We’ll update the About page with the proper data in a moment, but let’s first add protections against pages that don’t have this content.
Writing conditionals to display no markup if data doesn’t exist
To protect our HTML, we need to add conditionals to our include. We need to think about three different cases:
- When there is no
title
orbannerContent
, don’t display the entire section - When there is no
title
, don’t displayh1
, but display thebannerContent
paragraph - When there is no
bannerContent
, displayh1
, but not thebannerContent
paragraph
Most conditional operators you may be used to from other languages are also available in Liquid (and Nunjucks). For the first case, we need to check whether either title
or bannerContent
exists; for the second case, we need to check whether title
exists; and for the third case, we need to check whether bannerContent
exists:
{% if title or bannerContent %} <section class="banner"> {% if title %}<h1>{{ title }}</h1>{% endif %} {% if bannerContent %}<p>{{ bannerContent }}</p> {% endif %} </section> {% endif %}
This adds all the protections we need. Now, the About page no longer has a blank banner at the top. But we do need a banner, so let’s update the About page.
Adding About page data and content
When we created the About page in Chapter 1, we set it up to use base.html
like the home page. Because it’s using that layout, we now have access to the same banner include if we provide the same data structure to the page frontmatter. By adding the same data to the About page’s frontmatter, we can have a custom banner:
--- layout: "layouts/base.html" title: "About us" bannerContent: "This is a little paragraph about the company." ---
The page should now display a banner across the top, but let’s take this one step further. While it makes sense to keep the home page as an HTML document, authoring long-form content isn’t easy in HTML. While the frontmatter may be structured data, we can also use other types of content data in our pages. Let’s convert the page from HTML to Markdown—a more ergonomic way of authoring structured content in code.
To do this, change the file extension from .html
to .md
. By default, 11ty will read that as a Markdown document and use Markdown and Liquid to generate HTML from it. This means that all valid HTML and Liquid tags work in the page’s code, as well as standard Markdown syntax:
--- layout: "layouts/base.html" title: "About us" bannerContent: "This is a little paragraph about the company." --- ## The page content can go here It can use any markdown, since we're in a markdown page. Like [an anchor](https://packtpub.com) or **bold text**. * Or an unordered list * With some items 1. Or an ordered list 1. With some items (no need to have different numbers in a Markdown ordered list)
Now we have structured page data and our page content is transformed into data, but let’s take this a step further and create unique styling for the home page banner compared to the About page banner.
Typically, a home page banner will be styled with more padding and take up more space compared to an interior page, where getting users to the content faster is important.
To accomplish this, we need to dynamically add a class to the home page banner to modify its styles. Let’s add a pageId
variable to the frontmatter of the home and About pages. For the home page, set it to home
, and for About, set it to about
.
Then, we can modify the banner include to add a class when pageId
is home
. We can do this with another Liquid conditional, this time checking the value of pageId
rather than just whether it exists:
{% if title or bannerContent %} <section class="banner{% if pageId == "home" %} banner— home{% endif %}"> {% if title %}<h1>{{ title }}</h1>{% endif %} {% if bannerContent %}<p>{{ bannerContent }}</p> {% endif %} </section> {% endif %}
We add banner--home
as a class in the section when it matches home
; otherwise, it’s just banner
. This matches the class in the CSS file to set a min-height
on the banner. If you want to take this a step further, you could use the pageId
value itself and set styles for every page ID in your CSS.
Whitespace
Note the whitespace choices in the class list. There’s no space between banner
and the conditional and there’s a space preceding banner--home
. This is intentional and will render the HTML with no awkward whitespace. If you don’t mind extra spaces in your source code, you can choose to accommodate that space before the conditional. I care more about the rendered markup than perhaps I should.
We can also use pageId
to set an active state on our navigation to show users what section in the navigation the current page is in.
To do that, open the navigation.html
include we created in Chapter 1. For each navigation list item, we can create a conditional to check which pageId
is in use and display an active
class for the proper navigation item:
<nav> <ul> <li {% if pageId == "home" %}class="active" {% endif %}><a href="/">Home</a></li> <li {% if pageId == "about" %}class="active" {% endif %}><a href="/about">About</a></li> </ul> </nav>
Now that we have a working navigation and About section, let’s expand on the home page by adding data for a standard web design pattern—the triptych.
Adding an array to the frontmatter and looping through it in a page
We’ve drastically simplified the About page and applied reusable components between pages. The home page still has a section that has a lot of repeating HTML. The triptych area—three identically styled cards next to each other—has the same markup, but different content for each card.
We could put three sets of uniquely keyed data in the home page frontmatter and write the HTML around that, but it would be better to write the markup once and allow the data to be looped through and render the repeated HTML. To do this, we can use a YAML array and a Liquid for
loop.
Add the following to the frontmatter of index.html
:
triptychs: - headline: "Triptych 1" content: "Triptych 1 content" - headline: "Triptych 2" content: "Triptych 2 content" - headline: "Triptych 3" content: "Triptych 3 content"
Whitespace part 2
Note the whitespace again. YAML is whitespace sensitive, so the exact spacing is important.
If we add a dash before the start of each first property, YAML will interpret this as an array. The keys in the array should line up and the next dash will denote the next item in the array.
To use this data, we'll use another built-in Liquid template tag: {%
for %}
.
The for
tag is a paired shortcode that will loop through an array. It follows this syntax: {% for <variable-to-store-data> in <array-variable> %}
. This allows you to format your code in efficient ways:
<section class="triptych"> {% for triptych in triptychs %} <div class="triptych__item"> <h2>{{ triptych.headline }}</h2> <p>{{ triptych.content }}</p> </div> {% endfor %} </section>
Let’s make this even more reusable. Right now, this works in this space, but what if we want to have this style of item elsewhere? Let’s refactor it into an include and transform the data we pass into it so that it works with any data to make a new card. Make a new include
named card.html
. Bring the entire triptych__item
into the new include
and reference the include
from within the for
loop:
<section class="triptych"> {% for triptych in triptychs %} {% include "includes/card.html" %} {% endfor %} </section>
This works and might feel like we’re done, but it will only work with data under the triptych key. To fix this, we can pass specific data under specific keys to the include
.
We can extend the previous code snippet to rename each variable as we pass it into the include. We set the headline variable to triptych.headline
and the content variable to triptych.content
to give it the proper format for the new include. This way, anywhere we want to use the include
, we just pass the correct data to the correct key.
The new for
loop looks as follows:
<section class="triptych"> {% for triptych in triptychs %} {% include "includes/card.html", headline: triptych.headline, content: triptych.content %} {% endfor %} </section>
The new include
looks as follows:
<div class="triptych__item"> <h2>{{ headline }}</h2> <p>{{ content }}</p> </div>
The frontmatter is a great place to work with simple data, but as you may have noticed, it starts to get very complicated with just a little extra data. Let’s make the developer experience a little better by moving the data to an external file.
Adding data to external files
Data in the frontmatter can be helpful for writing content close to the page, but sometimes having discreet data files can keep the page files much cleaner. We can move data from the frontmatter to specific files along the 11ty Data Cascade.
Creating a template data file for the home page triptych
Let’s start by moving the triptych data from the frontmatter into a data file for the home page. To create a template data file, we create a file with a specific naming pattern. For JSON files, we use <template-name>.json
or <template-name>.11tydata.json
. For JavaScript data files—which we’ll dive into later in this chapter—the 11tydata
string is required for template or directory data.
For this example, we’ll use a JSON file to store the array we need for the home page. Create a file in the src
directory named index.json
. If you’re currently running 11ty, the terminal will show an error that the JSON is not formatted properly. That’s because 11ty already recognizes the data file, but it’s blank, and therefore incorrectly formatted for JSON.
For the triptych, insert the following JSON into the file you just created:
{ "triptychs": [ { "headline": "Triptych 1", "content": "Triptych 1 content!" }, { "headline": "Triptych 2", "content": "Triptych 2 content!" }, { "headline": "Triptych 3", "content": "Triptych 3 content!" } ] }
Once this is saved, the browser will refresh, and now we have six triptych items! When possible, 11ty will attempt to merge data from various sources instead of overriding it. So, for the triptych key, it knows that there are two arrays and adds them together. Note the template data file’s array content comes first. That’s because the template data is added first in the data cascade and then the frontmatter is added. If you insert a title
string into the template data file, you won’t see the title change. That’s because the frontmatter takes precedence in the cascade and strings won’t be merged like arrays.
The array merge is a nice feature, but we don’t want six items. For now, delete the frontmatter from the home page. The home page file is now much more readable and more data can be added to the data file and not overwhelm the page template. The same could be done for data for the About page as well. Follow the naming convention with about.json
and add any data you’d like.
If we had multiple pages with data files for each page, that would be difficult to manage. To fix this, we can use directory data files.
Moving the About page to its own directory
11ty allows for a deeper directory structure in the project to keep things neat but also adds additional power to pages that should be grouped together.
To start, create a new directory in the src
directory. Name this directory about
and move about.json
and about.md
into it. 11ty will recognize that the about.md
file is meant to be the root of that directory and automatically uses that file for the /about/
route on our site. It will also accept index.md
as the main file for this route. To keep things obvious, let’s change about.md
to the index.md
filename.
A note of caution on filenaming
While 11ty allows the use of the about.md
filename, it’s often better to go with index.md
for this file. By using about.json
, we’re telling 11ty to treat the data in about.json
as directory data. If we want template data for the About page, we need it to be index.md
with index.json
as the data file. For this example, this is unnecessary, but it can be important for bigger projects with a larger data structure.
The about.json
file can now provide data for any page within the about
directory. Let’s add a History page to our About section.
Create a new file in the about
directory and name it history.md
.
Add some Markdown to the file and save it. When 11ty rebuilds the site, there will now be a route at /about/history/
. This page will only display the Markdown you added to the file. In order to take advantage of the layout and other data, the layout path needs to be added. Instead of recreating reusable data, let’s move certain data to the directory data file. Both the layout
data and the pageId
data may need to be used by any page in the About section, so let’s add those two pieces of data to the about.json
file:
{ "layout": "layouts/base.html", "pageId": "about" }
Once that’s saved, the page will have the header and footer, as well as the correct navigation item selected. We’re still missing a banner. To add the banner, create the frontmatter in the History page. This ensures that the banner content is unique for each page:
--- title: "History" bannerContent: "This is a little paragraph to start talking about the history of the company." --- ## The page content can go here It can use any markdown, since we're in a markdown page. Like [an anchor](https://packtpub.com) or **bold text**.
We’ll dive deeper into directory data when creating a blog collection in Chapter 4.
While this is all good for a set of unique data, it’s often important to share data across the entire site—footers, metadata, and more are often powered by global data. Let’s add some data for all of our pages and templates.
Adding global data
Page and template data are great when adding unique data. Directory data is great for sharing data between specific pages. But what if you need to add data to every page? That’s where global data files come in.
To use global data files, we need a directory to store them. Inside the src
directory, create a new directory named _data
—11ty’s default data directory name. This can store multiple data files. Each file in this directory will give access to its data to any template using a key with the same name as its filename. For our site, let’s add some general site information and add the ability to access it from multiple files.
Create a new file in the _data
directory named site.json
. This file will have general information about the site, including the site name and the copyright date to be displayed in the footer:
{ "name": "My Awesome Site", "copyright": "2022" }
With this data in hand, let’s insert it into our templates.
In the site head—located in src/_templates/includes/header.html
—we’ll update the title. Currently, it’s just a hardcoded string. Let’s have it pull the page title and the site title:
<title>{{ title }} – {{ site.name }}</title>
In the footer—located in src/_templates/includes/footer.html
—let’s adjust the copyright information:
<footer class="footer"> <p>© {{ site.name }} {{ site.copyright }}</p> </footer>
Now the information is all changeable from one location whenever it needs to be updated.
Adding dynamic global data
Keen readers will have noticed something slightly off with that last section. The copyright date is already out of date. I’m writing this book at the end of 2022, but you’re not reading it then. To change this for the site, we would need to go into our footer each year and change the date. However, 11ty’s JavaScript data files can update this for us.
While JSON can be a handy format for simple data, it can be extended by writing it as JavaScript. Any code written in a JavaScript data file will be run each time the site builds. If we update the site’s copyright data with a script, it means the site will just need to be rebuilt each year, and no content change will be needed.
What do you mean “built”?
11ty has no client-side JavaScript code generated by default. Any JavaScript we write for data files will run when the HTML for the site is generated by 11ty—often referred to as “at build time.” This happens when the default eleventy
command is run in the terminal. This usually happens in your hosting provider, but can be run locally as well.
To start the process, let’s convert site.json
over to site.js
. This will immediately break the running of the terminal process. This is not a proper JavaScript module export.
Since the file is now a JavaScript file, it needs to be refactored to export the object instead of just containing the JSON object.
module.exports = { "name": "My Awesome Site", "copyright": "2022" }
When you run 11ty again, it should work as it did before.
Now that this is a JavaScript file, any Node.js JavaScript can be run from within the file. For our use, we can use the built-in Date
functionality in JavaScript to get the current date and save the year string as a globally accessible variable named copyright
:
module.exports = { "name": "My Awesome Site", "copyright": new Date().getFullYear() }
Now, the copyright date in the footer should display the current year instead of 2022. This is a simple example of using dynamic data in 11ty, but anything Node.js can do, 11ty’s JavaScript data files can also do. This includes things such as reading files from the filesystem, querying APIs, and transforming data.
Summary
In this chapter, we covered multiple ways of adding static and dynamic data to your 11ty site. After reviewing the structure of the 11ty Data Cascade, we added multiple types of data to our basic site project.
We started by adding page-specific data by using the frontmatter in each of our pages. We used this data to display different content for the banner on the home page and the About page with a singular include. We set up more differences in our layout by assigning a pageId
inside the frontmatter to display different banner styles and active states in the navigation. We created a reusable card component to be used with data from the home page and renamed it for the include so that it could be used in multiple locations.
Once we had that, we needed to clean up the display of the data in the templates. We used template data files to accomplish this, moving the triptych data to a separate data file with the same name as the page.
We created a new directory for the About section to use directory data files to share data between the About page and the new History page, which is a subpage of About.
Finally, we added global data to display certain information in multiple templates—the site name and copyright date. We started with a JSON file but converted it into a JavaScript data file to pull the current date for the copyright year in the footer.
In the next chapter, we’ll discuss the hosting needs of 11ty and walk through deploying a site through a modern static site host.