Jekyll Data Files: The Complete Guide to _data
Master Jekyll data files β store structured content in YAML, JSON, and CSV, loop through it in Liquid templates, and build navigation, team pages, and more.
Most Jekyll beginners learn about posts and pages before they discover data files β and that discovery usually comes with a βwhy didnβt I know about this earlier?β moment. The _data folder is where Jekyll lets you store structured content that is not quite a page and not quite a post: navigation menus, team members, pricing plans, FAQs, testimonials, social links, skills, sponsors, and anything else that belongs in a list or a structured record.
Instead of hard-coding this information into your HTML templates β which means editing multiple files every time anything changes β you put it in a clean YAML file. Templates read from it. Updates require changing one file. It is a separation of data and presentation that makes your site dramatically easier to maintain.
What data files are and how they work
Data files live inside the _data/ folder in your Jekyll project root. Jekyll reads them at build time and makes all their contents available globally through the site.data object. The filename (without extension) becomes the key you use to access the data.
A file at _data/social.yml is accessed as site.data.social. A file at _data/team.yml is site.data.team. A file in a subdirectory at _data/settings/theme.yml is site.data.settings.theme.
Jekyll supports four file formats in _data/:
YAML (.yml or .yaml) is the most common choice. It handles nested structures, lists, and mixed types naturally, and it reads cleanly compared to JSON. Use YAML for most data files.
JSON (.json) is useful when you are importing data from an API, a tool that exports JSON, or a system where JSON is the natural output format. Functionally identical to YAML β use whichever is more convenient for your workflow.
CSV (.csv) is ideal for tabular data that lives in a spreadsheet. Non-technical team members can manage the data in Google Sheets or Excel and export to CSV. Each row becomes an object with the column headers as keys.
TSV (.tsv) is tab-separated, otherwise identical to CSV.
Getting started: a simple example
Create _data/social.yml:
- name: Twitter
url: https://twitter.com/yourhandle
icon: twitter
- name: GitHub
url: https://github.com/yourusername
icon: github
- name: LinkedIn
url: https://linkedin.com/in/yourprofile
icon: linkedin
Access it in any template β a layout, an include, a page, or a post:
<ul class="social-links">
{% for link in site.data.social %}
<li>
<a href="{{ link.url }}"
aria-label="{{ link.name }}"
target="_blank"
rel="noopener noreferrer">
<span class="icon icon--{{ link.icon }}" aria-hidden="true"></span>
{{ link.name }}
</a>
</li>
{% endfor %}
</ul>
Add a new social network: add one entry to the YAML. Remove one: delete the entry. Reorder them: move the YAML blocks around. The template never changes.
Practical use cases
Navigation menus
Navigation is probably the most common use for data files. Storing nav items in YAML means you never need to touch layout HTML to add or rename a page.
# _data/navigation.yml
main:
- title: Themes
url: /themes/
- title: Blog
url: /blog/
- title: Showcase
url: /showcase/
- title: About
url: /about/
footer:
- title: Privacy Policy
url: /privacy/
- title: Terms of Use
url: /terms/
- title: FAQ
url: /faq/
- title: Contact
url: /contact/
<nav aria-label="Main navigation">
{% for item in site.data.navigation.main %}
<a href="{{ item.url | relative_url }}"
{% if page.url == item.url %}aria-current="page"{% endif %}>
{{ item.title }}
</a>
{% endfor %}
</nav>
<footer>
<nav aria-label="Footer navigation">
{% for item in site.data.navigation.footer %}
<a href="{{ item.url | relative_url }}">{{ item.title }}</a>
{% endfor %}
</nav>
</footer>
Notice the aria-current="page" attribute on the active link β this is the correct accessibility pattern for indicating the current page to screen reader users.
Team members
# _data/team.yml
- name: Sarah Jones
role: Lead Developer
bio: "10 years building Jekyll themes and static sites. Open source contributor."
avatar: /assets/images/team/sarah.jpg
github: sarahjones
twitter: sarahjones_dev
- name: James Park
role: Designer
bio: "UI/UX designer specialising in fast, minimal designs that convert."
avatar: /assets/images/team/james.jpg
github: jamespark
dribbble: jamespark_design
- name: Aria Fontaine
role: Content Strategist
bio: "Technical writer and SEO specialist helping developers build better docs."
avatar: /assets/images/team/aria.jpg
website: ariafontaine.dev
<div class="team-grid">
{% for member in site.data.team %}
<div class="team-card">
<img
src="{{ member.avatar | relative_url }}"
alt="{{ member.name }}"
loading="lazy"
width="120" height="120">
<h3>{{ member.name }}</h3>
<p class="team-card__role">{{ member.role }}</p>
<p class="team-card__bio">{{ member.bio }}</p>
<div class="team-card__social">
{% if member.github %}
<a href="https://github.com/{{ member.github }}"
aria-label="{{ member.name }} on GitHub">GitHub</a>
{% endif %}
{% if member.twitter %}
<a href="https://twitter.com/{{ member.twitter }}"
aria-label="{{ member.name }} on X">X</a>
{% endif %}
{% if member.dribbble %}
<a href="https://dribbble.com/{{ member.dribbble }}"
aria-label="{{ member.name }} on Dribbble">Dribbble</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
FAQ with categories
# _data/faq.yml
- question: "How do I install a Jekyll theme?"
answer: "Download or fork the theme repository, add the gem to your Gemfile if it's gem-based, run bundle install, and set theme: theme-name in _config.yml."
category: installation
- question: "Can I use Jekyll with GitHub Pages?"
answer: "Yes. Jekyll has native GitHub Pages support β push your site to a GitHub repository and it builds and deploys automatically. Free hosting for public repositories."
category: hosting
- question: "Are Jekyll themes mobile-responsive?"
answer: "Most modern Jekyll themes are fully responsive. Check the theme's demo on a mobile device before purchasing or installing."
category: themes
- question: "How do I add a custom domain to GitHub Pages?"
answer: "Add a CNAME file to your repository root containing your domain, then configure a CNAME or A record with your DNS provider pointing to GitHub Pages."
category: hosting
{% assign faq_by_category = site.data.faq | group_by: "category" | sort: "name" %}
{% for group in faq_by_category %}
<section class="faq-group">
<h2>{{ group.name | capitalize }}</h2>
{% for item in group.items %}
<details class="faq-item">
<summary class="faq-item__question">{{ item.question }}</summary>
<div class="faq-item__answer">{{ item.answer }}</div>
</details>
{% endfor %}
</section>
{% endfor %}
The group_by filter is one of Liquidβs most powerful tools for working with data files. It transforms a flat list into grouped sections automatically β no JavaScript, no backend.
Pricing table
# _data/pricing.yml
- name: Starter
price: 0
period: forever
highlight: "Get started for free"
features:
- Access to 60+ free themes
- GitHub Pages compatible
- Community forum support
- MIT licence
cta_text: Browse Free Themes
cta_url: /themes/?type=free
featured: false
- name: Premium
price: 49
period: one-time per theme
highlight: "Everything you need to launch"
features:
- Premium design quality
- Full source code included
- 6 months priority support
- Commercial licence
- Lifetime updates
- Installation guide
cta_text: Browse Premium Themes
cta_url: /themes/?type=premium
featured: true
<div class="pricing-grid">
{% for plan in site.data.pricing %}
<div class="pricing-card {% if plan.featured %}pricing-card--featured{% endif %}">
{% if plan.featured %}
<span class="pricing-card__badge">Most Popular</span>
{% endif %}
<h3 class="pricing-card__name">{{ plan.name }}</h3>
<p class="pricing-card__highlight">{{ plan.highlight }}</p>
<div class="pricing-card__price">
{% if plan.price == 0 %}
<span class="price-amount">Free</span>
{% else %}
<span class="price-amount">${{ plan.price }}</span>
<span class="price-period">{{ plan.period }}</span>
{% endif %}
</div>
<ul class="pricing-card__features">
{% for feature in plan.features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
<a href="{{ plan.cta_url }}"
class="btn {% if plan.featured %}btn--primary{% else %}btn--outline{% endif %}">
{{ plan.cta_text }}
</a>
</div>
{% endfor %}
</div>
Testimonials
# _data/testimonials.yml
- name: "Alex Chen"
role: "Freelance Developer"
quote: "JekyllHub saved me hours of searching. Found the perfect theme in ten minutes."
avatar: /assets/images/testimonials/alex.jpg
rating: 5
- name: "Maria Gonzalez"
role: "Blogger"
quote: "I had my new blog live in an afternoon. The quality of free themes here is remarkable."
avatar: /assets/images/testimonials/maria.jpg
rating: 5
<section class="testimonials">
{% for testimonial in site.data.testimonials %}
<blockquote class="testimonial-card">
<div class="testimonial-card__rating" aria-label="{{ testimonial.rating }} out of 5 stars">
{% for i in (1..testimonial.rating) %}β
{% endfor %}
</div>
<p class="testimonial-card__quote">"{{ testimonial.quote }}"</p>
<footer class="testimonial-card__author">
<img src="{{ testimonial.avatar | relative_url }}"
alt="{{ testimonial.name }}"
loading="lazy"
width="40" height="40">
<div>
<cite class="testimonial-card__name">{{ testimonial.name }}</cite>
<p class="testimonial-card__role">{{ testimonial.role }}</p>
</div>
</footer>
</blockquote>
{% endfor %}
</section>
Working with CSV data
CSV files work well for data maintained by non-developers in spreadsheets. Export from Google Sheets or Excel, drop in _data/, commit:
name,stars,category,licence,url
Minimal Mistakes,27000,Blog,MIT,/themes/minimal-mistakes/
Chirpy,7000,Blog,MIT,/themes/chirpy/
Just the Docs,8000,Documentation,MIT,/themes/just-the-docs/
Beautiful Jekyll,5000,Blog,MIT,/themes/beautiful-jekyll/
Jekyll reads this and treats each row as an object:
<table>
<thead>
<tr>
<th>Theme</th>
<th>Stars</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{% assign sorted_themes = site.data.themes | sort: "stars" | reverse %}
{% for theme in sorted_themes %}
<tr>
<td><a href="{{ theme.url }}">{{ theme.name }}</a></td>
<td>{{ theme.stars }}</td>
<td>{{ theme.category }}</td>
</tr>
{% endfor %}
</tbody>
</table>
Subdirectories for large data sets
Organise multiple data files into subdirectories for cleaner structure:
_data/
content/
homepage.yml
about.yml
settings/
navigation.yml
social.yml
marketplace/
categories.yml
featured.yml
Access with chained dot notation:
{{ site.data.content.homepage.hero_title }}
{{ site.data.settings.social.twitter_url }}
{% for category in site.data.marketplace.categories %}
{{ category.name }}
{% endfor %}
Filtering and sorting data
Liquid provides a full set of filters for working with data arrays:
{% comment %} Filter: exact match {% endcomment %}
{% assign free_themes = site.data.themes | where: "type", "free" %}
{% comment %} Filter: expression-based {% endcomment %}
{% assign popular = site.data.themes | where_exp: "item", "item.stars > 1000" %}
{% comment %} Sort ascending {% endcomment %}
{% assign alpha = site.data.team | sort: "name" %}
{% comment %} Sort descending {% endcomment %}
{% assign top = site.data.themes | sort: "stars" | reverse %}
{% comment %} Limit results {% endcomment %}
{% assign featured = top | limit: 3 %}
{% comment %} Find one item {% endcomment %}
{% assign chirpy = site.data.themes | find: "name", "Chirpy" %}
{% comment %} Map to extract a field {% endcomment %}
{% assign names = site.data.team | map: "name" | join: ", " %}
Data files vs other Jekyll features
Understanding when to use data files versus other Jekyll features saves confusion:
Use _config.yml for site-wide settings that affect the build: URL, title, description, plugin configuration, permalink structure, author defaults.
Use _data/ files for structured content that does not need its own URL: navigation, team members, testimonials, pricing, FAQs, skill lists, sponsors, social links.
Use collections (_themes/, _posts/, _docs/) for content that needs its own dedicated page: individual blog posts, theme listings, documentation pages, portfolio projects.
Use front matter for page-specific metadata: the title, description, layout, tags, and any custom fields that apply to a single page.
If data is used across multiple pages and does not need a URL, it belongs in _data/.
Automating data file updates
Data files are just text files in your repository. You can generate or update them programmatically:
# tools/update_stars.py
import json, urllib.request, yaml
repos = ['mmistakes/minimal-mistakes', 'cotes2020/jekyll-theme-chirpy']
data = []
for repo in repos:
url = f'https://api.github.com/repos/{repo}'
with urllib.request.urlopen(url) as r:
info = json.loads(r.read())
data.append({
'name': info['name'],
'stars': info['stargazers_count'],
'url': f'/themes/{info["name"].lower()}/'
})
with open('_data/stars.yml', 'w') as f:
yaml.dump(data, f, default_flow_style=False)
Run this in a GitHub Action on a schedule to keep star counts current without manual updates.
Data files are one of Jekyllβs most practical and underused features. Once you start using them, you will find them solving problems throughout your site β any time you have a list of structured items that appears in templates, _data/ is almost certainly the right home for it. Browse Jekyll themes on JekyllHub to see themes that use data files for navigation, settings, and structured content.
Advanced data file patterns
Once you understand the basics of _data/, several more sophisticated patterns become useful for complex Jekyll sites. These patterns are used by well-maintained production themes and can significantly improve the architecture of data-heavy sites.
Nested data files. Jekyll supports nested directories within _data/. A file at _data/team/engineering.yml is accessible as site.data.team.engineering. This organisational structure is useful when you have large amounts of related data β separating team members by department, products by category, or documentation settings by section. The nested structure keeps the data directory organised without merging all data into a single large file.
Iterating over multiple data files. If you have several data files in a subdirectory, Liquid can loop over them using site.data.folder_name where folder_name matches the directory. This is how theme galleries that automatically pick up every file in a directory work β add a new YAML file, and it appears in the listing without any template changes.
Data file validation at build time. Jekyll itself does not validate data files against a schema, but you can add a pre-build Ruby script that checks required fields and data types. This is overkill for personal sites but valuable for sites with multiple contributors who edit data files β a validation step catches missing required fields before they reach production.
Combining data files with collections. Data files and collections serve different purposes: data files hold structured configuration data; collections hold content with layouts, front matter, and body text. A theme marketplace site might use _data/theme-categories.yml for the filter labels and _themes/ as a collection for individual theme content. The two systems complement each other β data files for lookup tables and configuration, collections for content that needs its own pages.
Data files for site configuration
Beyond content, data files are an excellent place to store site-level configuration that does not belong in _config.yml. The distinction: _config.yml holds settings that affect how Jekyll processes files (URL, permalink format, plugins); data files hold settings that affect what templates render (navigation structure, feature flags, external link destinations).
A _data/nav.yml file defining your navigation structure is cleaner than hardcoding navigation links in a layout file. When you add a new page, you add one YAML entry; when you remove a page, you remove one entry. The layout template never needs to change. Navigation order is controlled by the order of items in the YAML file, without needing Liquid sorting logic.
A _data/settings.yml file for theme-level options β social media links, author bio, contact email β provides a single location for editors to update their details. Templates reference site.data.settings.twitter instead of repeating the Twitter handle in three different places. When the handle changes, it changes in one file.
Working with external data in Jekyll
One limitation of Jekyll data files is that they are static β you cannot fetch external API data at runtime. However, you can fetch external data at build time using a script that runs before Jekyll builds. A simple pattern: a Ruby or Python script that hits an API, writes the response to a _data/ file, then Jekyll builds with the fresh data.
For example, fetching your latest GitHub starred repositories and writing them to _data/github_stars.yml before each build lets you display them in a Jekyll template without any JavaScript API calls. The data is static in the built HTML but refreshes on every deployment. For data that changes infrequently (team headcount, product pricing, GitHub stats), this build-time fetch approach gives the freshness benefits of a dynamic site with the performance and simplicity of static files.
GitHub Actions makes this pattern clean to implement: run your data-fetching script in a step before the bundle exec jekyll build step. The fetched data is available to Jekyll during the build and baked into the output HTML. Visitors see current data; the site serves static files.
Data files are worth learning well early in your Jekyll journey. They solve a recurring problem β structured repeating content in templates β cleanly and without dependencies on plugins or external tools. Every non-trivial Jekyll site benefits from them, and the habit of reaching for a data file when you notice yourself hardcoding repeating values into templates is one of the marks of an experienced Jekyll developer.
Data files represent one of Jekyllβs most underused features. Many sites that reach for a plugin to generate dynamic content from a spreadsheet or database could achieve the same result with a well-structured YAML or JSON file in _data/. The habit of asking βcan I solve this with a data file?β before reaching for a plugin produces simpler, faster-building, and more maintainable Jekyll sites. Once you develop that instinct, you will find uses for _data/ files throughout your site β configuration values, navigation structures, team listings, pricing tables, FAQ items, testimonials β and your templates will be cleaner and more flexible for it.