Home Blog Jekyll _config.yml: The Complete Configuration Guide
Tutorial

Jekyll _config.yml: The Complete Configuration Guide

Everything you need to know about Jekyll's _config.yml — global settings, build options, plugins, collections, front matter defaults, and real-world examples.

Jekyll _config.yml: The Complete Configuration Guide

_config.yml is the single most important file in a Jekyll project. It controls everything: your site’s URL, which plugins run, how collections are defined, what files are excluded from builds, and any custom data you want available across every template. Understanding it thoroughly is essential for anyone building or customising a Jekyll site.

File location and format

_config.yml lives at the root of your Jekyll project:

my-jekyll-site/
├── _config.yml     ← here
├── _posts/
├── _layouts/
├── _includes/
└── index.html

It uses YAML format — indented key-value pairs. Two spaces per indent level, no tabs.

Important: Jekyll reads _config.yml only at startup. If you change it while running jekyll serve, you must restart the server to see the changes take effect. Content changes in posts and pages hot-reload automatically; config changes do not.

Essential site settings

# Site identity
title: "JekyllHub"
description: "A marketplace for premium and free Jekyll themes."

# URLs — critical to get right
url: "https://jekyllhub.com"       # your production domain with scheme
baseurl: ""                         # subdirectory, if any (e.g. "/blog")

# Contact
email: hello@jekyllhub.com

url vs baseurl

This distinction trips up many Jekyll users.

url is your site’s root domain: https://jekyllhub.com. It is used to construct absolute URLs (for RSS feeds, sitemaps, canonical tags).

baseurl is a subdirectory path if your site does not live at the root of the domain. For a site at https://username.github.io/my-project/, set baseurl: "/my-project". For a site at the root, leave it empty: baseurl: "".

In templates, always use the relative_url or absolute_url filters instead of hardcoding paths — they automatically prepend baseurl:


<a href="{{ '/about/' | relative_url }}">About</a>
<link rel="canonical" href="{{ page.url | absolute_url }}">

Build settings

# Build
source: .              # where Jekyll reads files (default: current dir)
destination: _site     # where Jekyll writes output (default: _site)

# Markdown
markdown: kramdown
highlighter: rouge     # syntax highlighting engine

# Kramdown options
kramdown:
  input: GFM                        # GitHub Flavoured Markdown
  hard_wrap: false
  syntax_highlighter: rouge
  syntax_highlighter_opts:
    block:
      line_numbers: true

# Liquid
liquid:
  error_mode: warn      # warn | strict | lax
  strict_filters: false
  strict_variables: false

Plugins

List all plugins in the plugins key:

plugins:
  - jekyll-feed          # generates /feed.xml RSS feed
  - jekyll-seo-tag       # adds meta tags, OG tags, JSON-LD
  - jekyll-sitemap       # generates /sitemap.xml
  - jekyll-paginate-v2   # pagination for posts
  - jekyll-redirect-from # add redirects via front matter
  - jekyll-archives      # generates category and tag archive pages

Plugins must also be in your Gemfile:

group :jekyll_plugins do
  gem "jekyll-feed"
  gem "jekyll-seo-tag"
  gem "jekyll-sitemap"
  gem "jekyll-paginate-v2"
end

GitHub Pages note: GitHub Pages only supports a specific list of whitelisted plugins. If deploying to GitHub Pages, check the whitelist. For full plugin support, use Cloudflare Pages, Netlify, or Vercel with a build step.

Collections

Collections let you create custom content types beyond posts and pages:

collections:
  themes:
    output: true                        # generate individual pages for each item
    permalink: /themes/:name/           # URL pattern
  authors:
    output: true
    permalink: /authors/:name/
  showcase:
    output: false                       # data only, no individual pages

With this configuration, files in _themes/ generate pages at /themes/minimal-mistakes/, etc. Files in _showcase/ are available as site.showcase but do not generate pages.

Front matter defaults

Avoid repeating the same front matter on every post with defaults:

defaults:
  # All posts get layout: post and author: Marcus Webb
  - scope:
      path: ""
      type: posts
    values:
      layout: post
      author: Marcus Webb
      toc: true
      featured: false
      comments: true

  # All pages get layout: page
  - scope:
      path: ""
      type: pages
    values:
      layout: page

  # Theme collection items get layout: theme
  - scope:
      path: ""
      type: themes
    values:
      layout: theme

  # Files in a specific directory
  - scope:
      path: "guides"
    values:
      layout: guide
      sidebar: true

  # A specific file
  - scope:
      path: "index.html"
    values:
      layout: home

Specificity rules: more specific scopes override less specific ones. A file-level front matter value always wins over any default.

Excluding and including files

By default, Jekyll excludes dotfiles, Gemfile, Gemfile.lock, node_modules, and a few others. Customise with:

# Exclude from build output
exclude:
  - .sass-cache/
  - .jekyll-cache/
  - Gemfile
  - Gemfile.lock
  - node_modules/
  - vendor/
  - "*.sh"
  - README.md
  - package.json
  - package-lock.json
  - tools/
  - CHANGELOG.md

# Include files that would otherwise be excluded
include:
  - _redirects      # Netlify/Cloudflare redirects file
  - _headers        # Cloudflare/Netlify headers file
  - .htaccess       # Apache config (dotfile, excluded by default)

Pagination

Using jekyll-paginate-v2:

pagination:
  enabled: true
  per_page: 12
  permalink: "/page/:num/"
  title: ":title - Page :num"
  sort_field: "date"
  sort_reverse: true

# Enable autopages for categories and tags
autopages:
  enabled: true
  categories:
    enabled: true
    permalink: "/category/:cat/"
    layouts:
      - "category.html"
  tags:
    enabled: false

SEO and analytics settings

Using jekyll-seo-tag:

# Used by jekyll-seo-tag
title: "JekyllHub"
tagline: "Find Your Perfect Jekyll Theme"
description: "Browse free and premium Jekyll themes for blogs, portfolios, and business sites."
url: "https://jekyllhub.com"
logo: /assets/images/logo.png
author:
  name: Marcus Webb
  email: marcus@jekyllhub.com
  twitter: marcuswebb

twitter:
  username: jekyllhub
  card: summary_large_image

social:
  name: JekyllHub
  links:
    - https://twitter.com/jekyllhub
    - https://github.com/jekyllhub

# Analytics (custom keys — not built-in Jekyll)
google_analytics: "G-XXXXXXXXXX"
plausible_domain: "jekyllhub.com"

# Newsletter (custom)
sendy_url: "https://sendpress.org/s/subscribe"
sendy_list_id: "YOUR_LIST_ID"

Jekyll Feed settings

feed:
  posts_limit: 20
  excerpt_only: false
  collections:
    - posts

Sass/SCSS settings

sass:
  sass_dir: _sass          # where .scss partials live
  style: compressed        # compressed | expanded | nested | compact
  load_paths:
    - _sass
    - node_modules         # if using npm packages

Custom data available in all templates

Any key in _config.yml is available as site.keyname throughout all templates. Use this to store site-wide settings:

# Custom site settings
nav_links:
  - title: Browse Themes
    url: /themes/
  - title: Blog
    url: /blog/
  - title: Showcase
    url: /showcase/

social_links:
  github: https://github.com/jekyllhub
  twitter: https://twitter.com/jekyllhub

support_email: support@jekyllhub.com
theme_submission_url: /submit/

Access in templates:


{% for link in site.nav_links %}
  <a href="{{ link.url }}">{{ link.title }}</a>
{% endfor %}

<a href="{{ site.social_links.github }}">GitHub</a>

Environment-specific configuration

Use multiple config files for different environments:

# _config.yml (base, committed to repo)
title: "JekyllHub"
url: "https://jekyllhub.com"
google_analytics: ""

# _config.development.yml (local overrides, not committed)
url: "http://localhost:4000"
google_analytics: ""

Run Jekyll with multiple configs — later files override earlier ones:

# Development
bundle exec jekyll serve --config _config.yml,_config.development.yml

# Production
JEKYLL_ENV=production bundle exec jekyll build

Serving options

Settings for jekyll serve:

# Local server
port: 4000
host: "127.0.0.1"
livereload: true          # auto-refresh browser on changes
open_url: true            # open browser automatically

# Show drafts during development
show_drafts: false        # set to true or use --drafts flag
future: false             # show posts with future dates
unpublished: false        # show unpublished posts

Timezone

timezone: "Europe/London"    # IANA timezone name

This affects how page.date is interpreted and how dates are formatted. Set it to your local timezone to avoid date-off-by-one issues.

# For posts
permalink: /:categories/:year/:month/:day/:title/

# Common permalink styles:
permalink: pretty          # /year/month/day/title/
permalink: date            # /year/month/day/title.html
permalink: ordinal         # /year/ordinal/title.html
permalink: weekdate        # /year/week/short_day/title/
permalink: none            # /title.html

A common setup for blogs:

permalink: /blog/:title/

This gives clean, category-free URLs like /blog/jekyll-front-matter-guide/.

Full example _config.yml

# ─── Site Identity ───────────────────────────────────────────────────────
title: "JekyllHub"
tagline: "Find Your Perfect Jekyll Theme"
description: "A marketplace for premium and free Jekyll themes  for blogs, portfolios, documentation, and business sites."
url: "https://jekyllhub.com"
baseurl: ""
email: hello@jekyllhub.com
author: "Marcus Webb"
logo: /assets/images/logo.png

# ─── Build ───────────────────────────────────────────────────────────────
timezone: "Europe/London"
markdown: kramdown
highlighter: rouge
permalink: /blog/:title/

kramdown:
  input: GFM
  syntax_highlighter: rouge

# ─── Plugins ─────────────────────────────────────────────────────────────
plugins:
  - jekyll-feed
  - jekyll-seo-tag
  - jekyll-sitemap
  - jekyll-paginate-v2
  - jekyll-redirect-from

# ─── Collections ─────────────────────────────────────────────────────────
collections:
  themes:
    output: true
    permalink: /themes/:name/
  authors:
    output: true
    permalink: /authors/:name/

# ─── Defaults ────────────────────────────────────────────────────────────
defaults:
  - scope:
      path: ""
      type: posts
    values:
      layout: post
      toc: true
      featured: false
  - scope:
      path: ""
      type: pages
    values:
      layout: page
  - scope:
      path: ""
      type: themes
    values:
      layout: theme
  - scope:
      path: ""
      type: authors
    values:
      layout: author

# ─── Pagination ──────────────────────────────────────────────────────────
pagination:
  enabled: true
  per_page: 12
  permalink: "/page/:num/"
  sort_field: date
  sort_reverse: true

# ─── Analytics ───────────────────────────────────────────────────────────
google_analytics: "G-XXXXXXXXXX"

# ─── Newsletter ──────────────────────────────────────────────────────────
sendy_url: "https://sendpress.org/s/subscribe"
sendy_list_id: ""
sendy_waitlist_id: ""

# ─── Exclude ─────────────────────────────────────────────────────────────
exclude:
  - Gemfile
  - Gemfile.lock
  - node_modules/
  - vendor/
  - tools/
  - package.json
  - README.md

include:
  - _redirects
  - _headers

The _config.yml file is the control panel of your Jekyll site. Mastering it means being able to change almost any aspect of how Jekyll builds and serves your content without touching a single template file.


Validating your _config.yml

YAML is sensitive to syntax errors — a misplaced tab or incorrect indentation produces cryptic Jekyll errors. Two tools help:

yamllint — a command-line YAML linter:

pip install yamllint --break-system-packages
yamllint _config.yml

Online YAML validators — paste your config into yaml.org/start.html or any online YAML parser to check for syntax errors before running Jekyll.

The most common YAML mistakes in _config.yml:

Mixing tabs and spaces — YAML only allows spaces. If your editor inserts tabs when you press Tab, configure it to use spaces instead.

Forgetting quotes around values with special characters — a value containing :, #, {, }, or [ must be quoted:

description: "A marketplace for Jekyll themes: free & premium"  # quotes required
tagline: Building with Jekyll                                    # no special chars, quotes optional

Incorrect list syntax — list items need a dash and a space:

# Wrong
plugins:
  -jekyll-feed
  -jekyll-seo-tag

# Correct
plugins:
  - jekyll-feed
  - jekyll-seo-tag

Reading config values in JavaScript

Your _config.yml values are available in Liquid templates but not directly in JavaScript. To make config values available to client-side scripts, expose them via data attributes or a generated JSON file.

Data attribute approach — add to your layout’s <body> tag:


<body
  data-baseurl="{{ site.baseurl }}"
  data-title="{{ site.title | escape }}"
  data-search-enabled="{{ site.data.settings.features.search }}">

In JavaScript:

const baseurl = document.body.dataset.baseurl;
const searchEnabled = document.body.dataset.searchEnabled === 'true';

Generated JSON approach — create site-config.json at your project root:


---
layout: null
---
{
  "baseurl": {{ site.baseurl | jsonify }},
  "title": {{ site.title | jsonify }},
  "postsPerPage": {{ site.pagination.per_page | jsonify }}
}

Fetch this in JavaScript and use the values as needed. This approach works well for build-time config that complex JavaScript modules need to reference.


_config.yml for team sites

On sites with multiple contributors, _config.yml often becomes a source of merge conflicts. A few organisational practices reduce this:

Use _data/ files for frequently-changing values — navigation links, team member lists, feature flags. These files are separate from _config.yml and easier to update without touching the main config.

Keep _config.yml for build settings only — URL, plugins, collections, permalink structure. Things that rarely change. Custom display data lives in _data/.

Comment every non-obvious setting — future contributors (and future you) should understand why a particular setting exists:

# permalink uses :title only (not date) to keep URLs short and stable.
# Changing this would break all existing inbound links — do not change without setting up redirects.
permalink: /blog/:title/

Use a _config.defaults.yml for team documentation — a commented, example configuration file that explains every available setting. New contributors read this file to understand what can be configured and how.

The _config.yml file is the single most important piece of configuration in a Jekyll project. A well-organised, well-commented config file is a sign of a mature, maintainable site — and makes onboarding new contributors significantly easier.


Securing your _config.yml

_config.yml is committed to your Git repository and typically publicly visible on GitHub. This makes it the wrong place for sensitive values. The distinction is important:

Safe to commit: site URL, title, description, plugin names, permalink structure, navigation data, analytics property IDs (these are public by design — they are in your HTML anyway), any value that appears in your built site’s source.

Never commit: API keys with write access, webhook secrets, payment processor secrets, database credentials, SMTP passwords, admin tokens.

For values that need to be available during the build but should not be in your repository, use environment variables and access them in a Jekyll plugin or in your build script. Your CI/CD platform (GitHub Actions, Netlify, Cloudflare Pages) has a secrets/environment-variable system for exactly this purpose.

The test: if a value appears anywhere in _site/ after a build, it is effectively public regardless of whether it is in _config.yml or an environment variable. Keep truly secret values server-side and never embed them in static HTML.


_config.yml performance impact

A few config settings have meaningful build-time performance impact:

limit_posts — during development, set this to a small number (20-30) to skip building all posts. Your local server starts in seconds instead of minutes:

# In _config.development.yml — not in main _config.yml
limit_posts: 20

incremental: true — enables Jekyll’s incremental build feature. Only pages that have changed (or that include/extend changed files) are rebuilt. Can speed up builds significantly for large sites, but occasionally misses changes to layouts and includes. Use during active writing sessions, not for production builds:

incremental: true

profile: true — outputs a build profiling table showing which files take the most time to render. Useful for diagnosing slow builds:

bundle exec jekyll build --profile

strict_front_matter: true — makes Jekyll error on YAML front matter parsing failures rather than silently ignoring them. Recommended for production builds to catch typos in post front matter:

strict_front_matter: true

Understanding these options turns _config.yml from a configuration file you set up once and forget into an active tool for managing your development workflow and build quality. Every setting in the file is documented on jekyllrb.com/docs/configuration/ — worth reading end-to-end when you set up a new project.

Evolving your config as your site grows

A new Jekyll site might need thirty lines in _config.yml. A mature site with pagination, archives, SEO metadata, analytics, and multiple collections might need two hundred. That growth is natural and manageable — the key is keeping the file organised as it expands.

Group related settings with comments that explain intent, not just what the setting is. # Pagination is a useful section header. # Show 12 posts per page — increase if posts are short, decrease for image-heavy posts is a comment that saves future-you from having to re-derive the reasoning. Good config comments are a form of documentation, and the config file is often the first place a new collaborator looks when trying to understand how a Jekyll site is structured.

Use _config.yml as a single source of truth wherever possible. If your site name appears in the config, reference it in templates with site.title rather than hardcoding it in a layout. If your author name appears in the config, use site.author.name in your bio sections. Every piece of data in the config that you can reference rather than repeat is one less place to update when that data changes. Over time, this habit keeps your codebase clean and maintainable — a sign of a well-architected Jekyll site.

Validating your config

YAML is unforgiving of formatting errors — a misplaced tab or inconsistent indentation will cause Jekyll to fail silently or throw a confusing error. Before pushing a config change to production, validate your YAML using an online YAML validator or the ruby -e "require 'yaml'; YAML.load_file('_config.yml')" command in your terminal. This runs in under a second and saves the frustration of a failed production build caused by a subtle YAML syntax error.

When adding complex nested settings — like jekyll-paginate-v2 options or collections configurations with multiple keys — write them with consistent two-space indentation throughout. Jekyll’s YAML parser is strict about consistency, and mixing tabs and spaces is the single most common cause of _config.yml parse errors. Many editors can be configured to automatically convert tabs to spaces in YAML files, eliminating this class of error entirely.

Keep a local development config at _config_dev.yml for settings that differ between development and production — a development url of http://localhost:4000, disabled analytics, and verbose logging. Load it alongside your main config with bundle exec jekyll serve --config _config.yml,_config_dev.yml. The development config values override the main config for the same keys, so you never accidentally commit a localhost URL to your production config.

Share LinkedIn