The project is in a healthy, maintained state
A Jekyll plugin + theme that scans assets/documents/**, creates a collection item per file, adds metadata (title/date/category), outputs pages, generates a JSON index for Lunr, and integrates with jekyll-sitemap.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 4.4, < 5.0
 Project Readme

jekyll-documents

Status: Active Tests Codecov Gem Version Gem Downloads License: AGPL-3.0-only Ask DeepWiki CodeFactor Semgrep CE Quality Gate Status

Turn files in assets/documents/ into browsable document pages.

Requirements: Ruby 3.4+ • Jekyll 4.4+

Features: Auto-collection • File icons • Categories • Search

Quick Start

# Gemfile
gem "jekyll-documents"
# _config.yml
plugins:
  - jekyll-documents

documents:
  icon_set: "color"  # color, lines, minimal, ultra-minimal
# Add files
assets/documents/reports/2026-03-01_Annual_Report.pdf
assets/documents/minutes/2026-02-15_Board_Meeting.docx
<!-- Use in templates -->
{% include latest_documents.html %}
{% include documents_list.html %}
{% include category_list.html %}

File Naming

Format: YYYY-MM-DD_Title.ext

assets/documents/reports/2026-03-01_Annual_Report.pdf

Date → YYYY-MM-DD • Title → underscores to spaces • Category → folder name

Icons

4 icon sets: color (default), lines, minimal, ultra-minimal

documents:
  icon_set: "color"

Render a document icon in templates with the context-aware tag:

{% document_icon page %}

The tag applies the configured icon set and site baseurl automatically. The file_type_icon and file_type_icon_tag filters remain available for lower-level or Ruby-side use.

Usage

Includes

{% include latest_documents.html count=5 %}
{% include documents_list.html %}
{% include documents_search.html %}

Liquid Tags

Link to a document by title or slug (partial match, case-insensitive):

{% doc_link "Annual Report" %}
{% doc_link "annual" text:"Read the report" %}
{% doc_link "board-meeting" icon:false size:false %}
{% doc_link path:"Reports/2026-03-01_Annual_Report.pdf" %}

Renders an <a> tag with the file type icon, title, and human-readable file size. Use text:"..." to override the link text, icon:false to hide the icon, or size:false to hide the file size. A path: lookup is exact and case-sensitive after normalizing / and \ separators; use it when a title or slug matches multiple documents.

Link to or list a category:

{% doc_category "reports" %}
{% doc_category "reports" text:"All reports" %}
{% doc_category "reports" list:true limit:5 %}
{% doc_category path:"Departments/Europe/Reports" list:true %}
{% doc_category "reports" aggregate:true list:true %}

Link mode renders an <a> tag to the category page. List mode renders a <ul> sorted by date. Use path: for an exact, case-sensitive category path, limit:N to cap the list, and aggregate:true list:true to explicitly combine repeated short category names.

List the most recent documents:

{% latest_documents %}
{% latest_documents count:3 %}
{% latest_documents count:3 category:"minutes" %}

count: defaults to latest_default_count (5); category: filters to a single category. Unlike {% include latest_documents.html %}, the tag reads its defaults from site config.

Document template customization

The document layout provides two optional include hooks. Both are empty by default, and the LayoutRegistrar copies them only when the site does not already provide a file with that name:

  • _includes/document_metadata_extra.html renders inside the document metadata paragraph, after the date and category.
  • _includes/document_download_attributes.html renders inside the opening tag of the document download link. It should output zero or more valid HTML attributes, including any leading spaces.

For example, a site can put a downloads label or count display in the metadata include, and add analytics attributes to the download link without copying the full document layout. These hooks are provider-neutral; the site owns their content and any associated JavaScript.

Optional GoatCounter click events

If the site loads GoatCounter's count.js, it can record clicks on the document-page download link using the attributes include. Use a stable, unique event name for each document:

 data-goatcounter-click="download-{{ page.source_path | url_encode | escape }}"
 data-goatcounter-no-session="1"

data-goatcounter-no-session="1" counts repeat clicks by the same visitor. GoatCounter counts a click attempt, not a confirmed file transfer. This hook covers the download link on the document page only; direct file links in site-authored listings or posts need the same event attributes of their own. The gem does not load GoatCounter, fetch event totals, or combine historical baselines. Those remain optional site-level analytics behavior.

Configuration

documents:
  root: "assets/documents"
  icon_set: "color"
  include_extensions: [".pdf", ".docx", ".pptx", ".xlsx", ".odt", ".ods", ".odp"]
  strict_filename: true
  strict_extensions: true
  resolution_mode: "warn"

resolution_mode controls ambiguous or unknown path references. The default warn mode logs all matching candidates and renders nothing. Set it to strict to log the same diagnostic and abort the build.

Generated documents expose source_path, category_path, and category_slug. Category mappings check the complete category_path before falling back to its final directory component. Permalinks support :category, :category_path, :slug, :source_path, :date, :year, :month, and :day. For example:

documents:
  permalink: "/documents/:category_path/:date/:slug/"
  category_map:
    Departments/Europe/Research: "European Research"
    Research: "Research"

The default permalink remains backward compatible. Any duplicate final permalink is fatal and the build error lists every conflicting source_path.

See configuration.rb for all options.

Icon sizing

Icons default to 1em (line-height) so they scale with surrounding text. A framework-agnostic CSS file with fixed-size utility classes is included:

<link rel="stylesheet" href="{{ '/assets/css/documents.css' | relative_url }}">
Class Size
(default) 1em — scales with line height
icon-x1 16px
icon-x2 32px
icon-x3 48px
icon-x4 64px
icon-x5 96px
icon-x6 128px
icon-x7 150px
icon-x8 256px
icon-x9 512px

Usage with the document_icon tag:

{% document_icon page class:"document-file-icon icon-x2" %}
{% document_icon doc alt:"Board minutes" %}

Or with any <img>:

<img src="..." class="document-file-icon icon-x3" />

The CSS uses display: inline-block and vertical-align: middle — no dependency on Bulma, Bootstrap, Tailwind, or any other framework.

Search Integration

Documents are compatible with jekyll-client-search for client-side search. Each document has categories (plural array) and searchable content baked in at generation time, so search plugins can index uploaded files alongside posts.

To include documents in the search index, add documents to the collections list in _config.yml:

client_search:
  collections:
    - posts
    - documents

Icons and file-type metadata in search results

jekyll-documents bakes file_type, icon_url, and icon_set into each document's data at generation time. When jekyll-client-search is installed and documents is in the search collections, these fields are auto-injected into the search index — no extra configuration needed:

client_search:
  collections:
    - posts
    - documents

This makes jekyll-client-search render:

  • data-file-type, data-icon-set attributes on each result <article> (for CSS-based badges and theme-aware styling)
  • An <img class="client-search-result-icon"> before the title, using the icon from the configured icon_set (color, lines, minimal, or ultra-minimal)

The icon automatically matches the icon_set configured in your documents section — no extra configuration needed for theme consistency.

Field renaming (for integration with other search conventions):

client_search:
  passthrough_fields:
    - file_type: doctype      # rename in the search index
    - icon_url: thumbnail
  icon_field: thumbnail

CSS examples:

/* File-type badge */
.client-search-result[data-file-type="pdf"]::before {
  content: "PDF";
  background: #e74c3c; color: white;
  padding: 0 0.3em; font-size: 0.7em; margin-right: 0.3em;
}

/* Theme-aware icon sizing */
.client-search-result[data-icon-set="color"] .client-search-result-icon { width: 2em; }
.client-search-result[data-icon-set="ultra-minimal"] .client-search-result-icon { width: 1em; }

See the jekyll-client-search README for all CSS customization options.

Text Extraction

Extract text from PDF/DOCX/XLSX/PPTX/ODT/ODS/ODP files so search engines can index document contents, not just metadata.

Setup

Add the optional plaintext gem to your Gemfile:

# Gemfile
gem "plaintext", group: :jekyll_plugins

Enable extraction in _config.yml:

documents:
  extract_text: true

How it works

  • Extracted text is stored in doc.content, which jekyll-client-search indexes automatically — no extra configuration needed
  • Text is cached in .cache/jekyll-documents/ (in your site source, not .jekyll-cache/), so it survives jekyll clean
  • Cache invalidation uses SHA-256 file digests — only changed files are re-extracted
  • Stale cache entries for deleted files are cleaned up automatically at the end of each build
  • Falls back to metadata-only content (title, category, file type, date) if the plaintext gem is missing or extraction fails

Configuration

documents:
  extract_text: true                    # Enable text extraction
  text_max_bytes: 500000                # Truncate extracted text (default 500KB)
  text_cache_dir: ".cache/jekyll-documents"  # Cache directory in site source

Add the cache directory to .gitignore:

.cache/jekyll-documents/

CLI tool dependencies

The plaintext gem uses the rubyzip Ruby gem for Office formats (no CLI tools needed). PDF extraction shells out to a system command:

Format Tool
PDF pdftotext (poppler-utils)
DOCX/PPTX/XLSX rubyzip (Ruby gem, no CLI needed)
ODT/ODS/ODP rubyzip (Ruby gem, no CLI needed)

Install pdftotext for PDF extraction:

# macOS
brew install poppler

# Debian/Ubuntu
sudo apt-get install poppler-utils

On macOS and other non-standard installations, the executable is discovered from PATH. You can also configure a custom pdftotext command through the plaintext gem configuration.

Development

rake              # Ruby quality checks
rake test         # Rebuild/install gem + Ruby and browser tests
rake browser_test # Browser search tests (Node.js + Chromium required)
rake quick        # Fast check (style + markdown + tests)
rake help         # Show commands

Quality: ≥98% coverage • no unallowlisted vulnerabilities • 0 offenses

See README.Development.md for details.

DeepWiki: https://deepwiki.com/gundestrup/jekyll-documents

Release

bundle exec rake "version:bump[patch]"
# Edit CHANGELOG.md
git add lib/jekyll/documents/version.rb CHANGELOG.md
git commit -m "Release X.Y.Z: summary"
git tag -a vX.Y.Z -m "Release X.Y.Z"
git push origin main
git push origin vX.Y.Z

License

AGPL-3.0-only • Icons by SVG Repo