Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
jekyll-plugin loop_directory
#{% loop_directory directory:images iterator:image filter:*.jpg sort:descending %}
# <img src="{{ image }}" />
#{% endloop_directory %}
module Jekyll
class LoopDirectoryTag < Liquid::Block
include Liquid::StandardFilters
Syntax = /(#{Liquid::QuotedFragment}+)?/
def initialize(tag_name, markup, tokens)
@attributes = {}
@attributes['directory'] = '';
@attributes['iterator'] = 'item';
@attributes['filter'] = 'item';
@attributes['sort'] = 'ascending';
# Parse parameters
if markup =~ Syntax
markup.scan(Liquid::TagAttributes) do |key, value|
@attributes[key] = value
raise"Bad options given to 'loop_directory' plugin.")
#if @attributes['directory'].nil?
# raise"You did not specify a directory for loop_directory.")
def render(context)
context.registers[:loop_directory] ||=
images = Dir.glob(File.join(@attributes['directory'], @attributes['filter']))
if @attributes['sort'].casecmp( "descending" ) == 0
# Find files and sort them reverse-lexically. This means
# that files whose names begin with YYYYMMDD are sorted newest first.
images.sort! {|x,y| y <=> x }
# sort normally in ascending order
result = []
context.stack do
# remove filename extension
images.each { |pathname|
context[@attributes['iterator']] = File.basename(pathname, @attributes['filter'].sub('*', ''))
result << render_all(@nodelist, context)
# return pathname
# images.each_with_index do |item, index|
# context[@attributes['iterator']] = item
# result << render_all(@nodelist, context)
# end
Liquid::Template.register_tag('loop_directory', Jekyll::LoopDirectoryTag)
Copy link

mikeritter commented Jun 1, 2015

Please add a with install instructions for noobs and maybe even build testing if you're feeling adventurous.

I am trying following Jekyll's plugin instructions but not getting anywhere.

Copy link

ac70 commented Jul 13, 2015

this is working well for me, THANKS!

I am trying to work out how to send the number of files in the directory as well (result.length ??). Can you point me in the right direction on what code to add? I'm a ruby noob.

mikeritter - basic instructions:
add the file above to the plug-ins folder
restart your jekyll server
add lines 2-4 to your html page

  • remove the # preceding the line
  • change the directory if needed
  • I changed filter:.jpg to filter: so it finds all files (not just .jpg, which is case sensitive as well)
  • try adding this line so you can see what the plug-in is outputting: {{ image }}

Copy link

lmmx commented Nov 8, 2015

Copy link

planemad commented Mar 13, 2016

@jgatjens this is an amazing plugin and should probably be probably be part of liquid!

I'm trying to pass a variable subdirectory to loop through but can't get it to work. Something like this:

{% loop_directory directory:{{page.url | remove_first: "/"}}thumbnail

I'm looking through stackoverflow questions on passing variables as arguments in liquid, but can't get any to work.

Copy link

adenta commented Mar 26, 2016

@planemad, were you able to get this working? This seems like a common use case. I am trying to solve the same problem as well.

Copy link

adenta commented Mar 26, 2016

For now I am using the solution presented here.

Copy link

skuzzymiglet commented Oct 31, 2019

I get a

> jekyll serve --trace 
Configuration file: /home/skuzzymiglet/ysgol/gwefan-jekyll/_config.yml 
            Source: /home/skuzzymiglet/ysgol/gwefan-jekyll 
       Destination: /home/skuzzymiglet/ysgol/gwefan-jekyll/_site 
 Incremental build: disabled. Enable with --incremental 
  Liquid Exception: undefined method `render_all' for #<Jekyll::LoopDirectoryTag:0x000056284e5f07e0> in 
Traceback (most recent call last): 
        41: from /home/skuzzymiglet/.gem/ruby/2.6.0/bin/jekyll:23:in `<main>' 
        40: from /home/skuzzymiglet/.gem/ruby/2.6.0/bin/jekyll:23:in `load' 
        39: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/exe/jekyll:15:in `<top (required)>' 
        38: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/mercenary-0.3.6/lib/mercenary.rb:19:in `program' 
        37: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/mercenary-0.3.6/lib/mercenary/program.rb:42:in `go' 
        36: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/mercenary-0.3.6/lib/mercenary/command.rb:220:in `execute' 
        35: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/mercenary-0.3.6/lib/mercenary/command.rb:220:in `each' 
        34: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/mercenary-0.3.6/lib/mercenary/command.rb:220:in `block in execute' 
        33: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/commands/serve.rb:86:in `block (2 levels) in init_with_program' 
        32: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/command.rb:89:in `process_with_graceful_fail' 
        31: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/command.rb:89:in `each' 
        30: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/command.rb:89:in `block in process_with_graceful_fail' 
        29: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/commands/build.rb:36:in `process' 
        28: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/commands/build.rb:65:in `build' 
        27: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/command.rb:28:in `process_site' 
        26: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/site.rb:76:in `process' 
        25: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/site.rb:202:in `render' 
        24: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/site.rb:515:in `render_pages' 
        23: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/site.rb:515:in `each' 
        22: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/site.rb:516:in `block in render_pages' 
        21: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/site.rb:523:in `render_regenerated' 
        20: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/renderer.rb:63:in `run' 
        19: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/renderer.rb:80:in `render_document' 
        18: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/renderer.rb:127:in `render_liquid' 
        17: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/liquid_renderer/file.rb:32:in `render!' 
        16: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/liquid_renderer/file.rb:60:in `measure_time' 
        15: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/liquid_renderer/file.rb:33:in `block in render!' 
        14: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/liquid_renderer/file.rb:53:in `measure_bytes' 
        13: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/liquid_renderer/file.rb:34:in `block (2 levels) in render!' 
        12: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/liquid_renderer/file.rb:49:in `measure_counts' 
        11: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/jekyll-4.0.0/lib/jekyll/liquid_renderer/file.rb:35:in `block (3 levels) in render!' 
        10: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/liquid-4.0.3/lib/liquid/template.rb:220:in `render!' 
         9: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/liquid-4.0.3/lib/liquid/template.rb:207:in `render' 
         8: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/liquid-4.0.3/lib/liquid/template.rb:242:in `with_profiling' 
         7: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/liquid-4.0.3/lib/liquid/template.rb:208:in `block in render' 
         6: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/liquid-4.0.3/lib/liquid/block_body.rb:82:in `render' 
         5: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/liquid-4.0.3/lib/liquid/block_body.rb:103:in `render_node_to_output' 
         4: from /home/skuzzymiglet/ysgol/gwefan-jekyll/_plugins/loop_dir.rb:52:in `render' 
         3: from /home/skuzzymiglet/.gem/ruby/2.6.0/gems/liquid-4.0.3/lib/liquid/context.rb:123:in `stack' 
         2: from /home/skuzzymiglet/ysgol/gwefan-jekyll/_plugins/loop_dir.rb:56:in `block in render' 
         1: from /home/skuzzymiglet/ysgol/gwefan-jekyll/_plugins/loop_dir.rb:56:in `each' 
/home/skuzzymiglet/ysgol/gwefan-jekyll/_plugins/loop_dir.rb:58:in `block (2 levels) in render': undefined method `render_all' for #<Jekyll::LoopDirectoryTag:0x000056284e5f07e0> (NoMethodError) 


layout: default
title: "Happy Jekylling!"

## You're ready to go!

{% loop_directory directory:images iterator:image filter:*.jpg sort:descending %}
   <img src="{{ image }}" />
{% endloop_directory %}

Clean, jekyll new --blank site

Copy link

jgatjens commented Nov 1, 2019

@skuzzymiglet do you have some repo with the code to see what's going on there?

Copy link

miko007 commented Feb 24, 2020

i have come across the same problem. seems like Liquid::Block does not provide the render_all method anymore. Even the regular Liquid::Block::render() method is marked "For backwards compatibility". Likewise, the whole API for liquid blocks has changed.

I will have a more in-depth look into the problem, but never having wrote a liquid/jekyll module myself, it will not be very successful, i fear.

Copy link

shak-mar commented May 14, 2021

For a simpler version that works well with the built-in for block and works with current versions of liquid, see It creates a Page object for each of the files that were found, so if you don't want that, remove the .map do ... end part (you can also remove the site = ... and source = ... lines as well as the comment in that case) – without the map call, you'll get a list of filenames instead of a list of pages. See for an example usage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment