Ir al contenido
Código en GitHub (se abre en una pestaña nueva)

Documentación · Referencia · El formato de un proyecto

6. Colecciones

Las colecciones y sus filtros, una página por cada valor y las listas partidas en páginas.

Versión 0.16

Una colección es el conjunto de páginas que comparten una etiqueta. Se declaran con etiquetas, normalmente desde el _datos.yml de la carpeta. Los fragmentos entran en las colecciones de sus etiquetas.

Toda página con URL está además en la colección implícita todo. Por eso todo no se puede usar como etiqueta: si aparece, se avisa y se quita.

En un sitio en varios idiomas, las colecciones están acotadas al idioma de la página que las consulta, y se puede pedir otro (§15.5). El nombre de una colección se compara exacto, con mayúsculas y sin recortar espacios: coleccion('Blog') no es coleccion('blog').

No entran en ninguna colección, tampoco en todo, los borradores, las páginas con publicar en el futuro y las de listada: no. Este último es el caso de los textos legales, la página de error o robots.txt: se publican, pero no salen en listados ni en el sitemap.

Desde una plantilla:

{% for articulo in coleccion('blog')|orden('fecha desc')|limite(6) %}

6.1 Orden de una colección

coleccion() devuelve las páginas por fecha, de la más antigua a la más reciente, y a igual fecha por ruta: la del fichero, con su extensión y su código de idioma, comparada byte a byte (blog/a-b.md va antes que blog/a.md, porque - es menor que .). Las que no tienen fecha van al final, también por ruta.

Es el mismo orden que usa Eleventy, salvo en las páginas sin fecha: Eleventy les da la fecha de creación del fichero, que cambia de una máquina a otra, y un proyecto tiene que dar el mismo resultado en cualquiera.

6.2 Filtros

  • orden(criterio) ordena por uno o varios campos separados por comas, cada uno seguido si acaso de asc o desc (asc si no se indica): orden('orden, titulo'). Sin criterio es orden('fecha desc'). Las páginas a las que les falta el campo van al final en las dos direcciones. Los números se comparan como números, las fechas como fechas y los textos sin distinguir mayúsculas ni tildes (con la tabla del slug, §5.2), con la ñ después de la n y sin contar los signos ¿ y ¡: «¿Cómo llegar?» va entre las «c», no antes de la «a». Es una comparación del valor de cada carácter, no la de un diccionario entero: un número escrito en un texto se compara como texto («Capítulo 10» va antes que «Capítulo 2») y las letras de otros alfabetos se ordenan por su código. Dos valores de distinto tipo se comparan como texto, y entre dos sí/no el no va antes. A igualdad, las páginas quedan en el orden que tenían. Un criterio que no vale (orden('fecha descendente')) detiene el build; paginar.orden (§6.4), en cambio, avisa y usa el de coleccion().
  • limite(n) deja las n primeras.
  • invertir da la vuelta a la lista.
  • sin(pagina) quita una página, normalmente la actual.
  • donde(campo, valor) deja las páginas en las que el campo vale eso. Si el campo es una lista, las que lo contienen: donde('etiquetas', 'novela'). Una fecha se compara con su día (donde('fecha', '2025-03-15')). Si los dos valores parecen números, se comparan como números (donde('codigo', '7') encuentra codigo: "007"); los demás textos se comparan exactos: con mayúsculas, tildes y espacios.
  • anterior(pagina) y siguiente(pagina) no filtran: devuelven la página que va antes o después de la indicada en la lista, o nada si es la primera, la última o no está. Con el orden de coleccion(), la anterior es la más antigua.

Los filtros aceptan también los alias ingleses de los campos (date, title).

orden y donde ven cada página como la ven las plantillas (§7.2): los campos del front matter, con la cascada, y además url, la URL ya resuelta (vacía en un fragmento), ruta, la del fichero dentro de contenido/, e idioma, el código del idioma de la página (§15.8). donde('url', '/contacto/') encuentra la página aunque su URL salga de la ruta y no esté escrita en el front matter. No cuentan contenido, markdown, encabezados ni traducciones (§7.2): para filtrar u ordenar por contenido habría que convertir el cuerpo de todas las páginas de la lista.

Como orden respeta el orden previo en los empates, orden('fecha desc') no es lo mismo que invertir cuando hay páginas con la misma fecha: la primera las deja por ruta y la segunda, al revés. Lo que en Eleventy es collections.blog | reverse | head(6) es aquí coleccion('blog')|invertir|limite(6).

6.3 Una página por cada valor

Un blog con etiquetas abiertas no puede tener una página escrita a mano por cada etiqueta: la siguiente entrada puede traer una nueva. Para eso, una página con agrupar no es una página sino una plantilla de páginas: el motor la repite una vez por cada valor distinto de un campo de las páginas de una colección.

---
plantilla: etiqueta
agrupar:
  coleccion: blog       # de dónde salen las páginas; obligatorio
  campo: etiquetas      # el campo por el que se agrupan; obligatorio
  excluir: [blog]       # valores que no generan página; opcional
url: "/etiqueta/{{ grupo|slug }}/"
etiquetas: [temas]
---

La página con agrupar no genera fichero, ni sale en colecciones ni en el sitemap. Cada página que genera lleva los campos de la que la define (con la cascada del §3, como cualquier otra) y además:

  • grupo: el nombre del grupo, en texto;
  • paginas: las páginas del grupo, con la forma de las que da coleccion() (§7.2) y en su mismo orden, así que los filtros del §6.2 valen: pagina.paginas|orden('fecha desc').

Como contenido (§7.2), las dos tapan un campo del front matter que se llame igual, solo en las páginas generadas.

Reglas:

  • Qué entra. Las páginas de la colección, tal como las da coleccion() (§6): sin borradores, sin las de publicar en el futuro y sin las de listada: no, y en el idioma de la página que define el grupo (§15.5). Por eso un valor que solo usan páginas que aún no se publican no tiene página hasta que sale la primera, y nunca hay un grupo vacío.
  • Los valores. campo es un campo de las páginas de la colección, tal como llega a la plantilla (§7.2), con la cascada. Una lista da un valor por elemento; un texto suelto, uno, como en etiquetas (§4). Un valor que no es texto ni número, o que está en blanco, se ignora y se avisa. Si el campo es etiquetas hay que acordarse de excluir: la carpeta pone a cada artículo el nombre de su colección (§3), y sin excluir: [blog] habría un grupo con todo el blog.
  • Variantes. Los valores se agrupan por su slug (§5.2): Cultura pop, cultura-pop y Cultura Pop son un grupo. Es la única regla coherente, porque la URL sale del slug y dos grupos con la misma saldrían al mismo fichero. grupo es la variante que más páginas usan y, a igualdad, la primera en el orden de orden (§6.2); si aun así empatan (Ideas e ideas), la de menor código de carácter, para que el nombre no dependa del orden en que llegan las páginas. excluir también compara por slug. Un valor cuyo slug sale vacío se ignora y se avisa.
  • La URL. Tiene que usar {{ grupo }}, casi siempre como {{ grupo|slug }} (§5.1), que es lo que da un tramo de URL válido: sin el filtro, el valor se escribe tal cual. Sin url, o con una que no lo use, todas las páginas irían al mismo fichero: se avisa y no se genera ninguna. El resto del §5 vale: el prefijo de idioma (§15.3) y el error si dos páginas van al mismo fichero.
  • El título. Si la página que define el grupo no tiene titulo, el de cada página generada es su grupo, y no avisa. Cualquier otro campo se pasa como está: lo que hay que componer con el grupo (Etiqueta: …) se escribe en la plantilla con pagina.grupo.
  • Sitemap y todo. Las páginas generadas salen en el sitemap y en todo, y en las colecciones que declaren sus etiquetas. Si la que las define lleva listada: no, lo heredan como cualquier otro campo (§3) y no salen en ninguno de los dos.
  • El orden. Las páginas generadas no tienen fecha salvo que la herede la que las define; en una colección van, como cualquier página sin fecha, al final, y entre ellas por slug de su grupo. Con etiquetas: [temas], el índice de todos los grupos es coleccion('temas') y su recuento, tema.paginas|length, sin repetir en un fichero de datos lo que ya dicen las páginas.
  • Idiomas. Cada idioma tiene su propia página con agrupar (etiqueta.md, etiqueta.eu.md) y agrupa las páginas de ese idioma. En una página generada, pagina.traducciones solo trae ella misma (§15.8): los grupos de un idioma no se corresponden con los de otro.
  • Los avisos y los errores. Un agrupar al que le falta coleccion o campo avisa, con su línea, y no genera nada. Una clave que no admite se avisa y se ignora. Si ni siquiera es un mapa (agrupar: sí), se avisa, se ignora y la página es una más. Una colección sin páginas no es un error: no hay grupos. excluir admite, como etiquetas, un texto suelto. agrupar no se hereda: en un _datos.yml se avisa y se ignora, porque todas las páginas de la carpeta serían plantillas de páginas y ninguna saldría.
  • Sin vueltas. Una página con agrupar no puede depender de las páginas que genera ella misma ni otra (coleccion: todo, o una colección a la que pertenecen las generadas): el build se detiene con un error que muestra la cadena, como en el §7.2.
  • No es paginación. Parte un conjunto en grupos; no parte una lista larga en trozos. Eso es paginar (§6.4).

grupo y paginas los tienen también las páginas generadas cuando salen de coleccion() (§7.2), que es lo que hace posible el índice de arriba.

La compilación incremental da siempre lo mismo que la completa. Una página generada se rehace cuando entra o sale una página de su grupo o cambian los campos de alguna; si solo cambia el cuerpo de una de ellas, no, salvo que la plantilla lo muestre. Las páginas que se generan, o dejan de generarse, se deciden de nuevo en cada compilación. Cómo se consigue es cosa de docs/compilacion.md.

El esquema (§11) no sabe de agrupar. Es cosa de quien desarrolla el sitio, y un editor ni la enseña ni la toca; lo que sí hace es que el editor pueda escribir una etiqueta nueva en una entrada sin que nadie tenga que crear antes su página.

6.4 Partir una lista en páginas

Un listado largo se parte en trozos, uno por página. Una página con paginar es, como con agrupar, una plantilla de páginas: ella misma es la primera, en su url, y el motor genera las demás.

---
plantilla: blog
url: /blog/
paginar:
  coleccion: blog          # de dónde salen las páginas; obligatorio
  porPagina: 10            # cuántas lleva cada página; obligatorio
  orden: fecha desc        # criterio de orden() (§6.2); opcional
  url: "/blog/{{ numero }}/"   # la de las páginas a partir de la segunda; opcional
---

Una entrada nueva que llene la última página abre la siguiente sin que nadie cree nada, y una que se quita la cierra.

  • Qué entra. Las páginas de la colección, tal como las da coleccion() (§6): sin borradores, sin las de publicar en el futuro y sin las de listada: no, y en el idioma de la página (§15.5). La propia página que pagina no entra en su colección, aunque lleve su etiqueta, ni tampoco las que genera: blog/index.md hereda la etiqueta blog de su carpeta (§3) y no por eso se lista a sí misma.

  • El orden. El de orden, que se escribe como el criterio del filtro orden(); sin él, el de coleccion() (§6.1), de la más antigua a la más reciente. Un blog suele querer fecha desc. Se aplica antes de partir.

  • Los trozos. porPagina es un entero mayor que cero. Cada trozo lleva como mucho esa cantidad, y el último, las que sobren. Una colección sin páginas no es un error: hay un solo trozo, vacío, y la página sale, para que un listado sin entradas siga existiendo. Un porPagina que no es un entero mayor que cero se avisa y la página es una más, sin partir.

  • La URL. La primera es la url de la página (§5), con las reglas de siempre. Las siguientes, la de paginar.url, que es un patrón del §5.1 con {{ numero }} (el número de la página, desde el 2) y que tiene que usarlo; sin paginar.url, la de la primera más pagina/2/, pagina/3/… (/blog/ da /blog/pagina/2/). Esa forma por defecto exige que la url de la primera acabe en /: si no es así, y no hay paginar.url, se avisa y la página es una más, sin partir, aunque hoy todo quepa en un trozo (con la entrada que lo llene, la página dejaría de partirse). Sin {{ numero }} en el patrón todas irían al mismo fichero: se avisa y pasa lo mismo. Lo demás del §5 vale: el prefijo de idioma (§15.3) y el error si dos páginas van al mismo fichero. paginar.url existe para quien migra y ya tiene otra forma (/blog/2/, /page/2/) y no quiere perder direcciones.

  • Lo que ve la plantilla. Cada página, la primera también, lleva los campos de la que la define (con la cascada del §3) y además:

    • paginas: las páginas de su trozo, con la forma de las que da coleccion() (§7.2) y en el orden pedido;
    • paginacion: actual y total (cuántas páginas hay), porPagina, entradas (cuántas hay en la colección, repartidas en esas páginas), primera y ultima (sus URL), anterior y siguiente (la URL, o nada en la primera y en la última; los filtros del mismo nombre, §6.2, devuelven una página) y enlaces, una lista con un elemento por página, con numero, url y actual (true en la que se está), para pintar el índice de páginas.

    Como contenido (§7.2), las dos tapan un campo del front matter que se llame igual. El título es el de la página que define: la plantilla añade «Página 2» con pagina.paginacion.actual.

  • Colecciones y sitemap. La primera es una página como cualquier otra, con la etiqueta y el sitemap que le toquen. Las que genera no entran en ninguna colección, tampoco en todo, ni en el sitemap, como las de listada: no (§4): se llega a ellas por los enlaces de la paginación.

  • Idiomas. Cada idioma tiene su propia página con paginar (blog.md, blog.eu.md) y parte las páginas de ese idioma. En una página generada, pagina.traducciones solo trae ella misma; la primera, que es la que define la página, tiene las suyas (§15.8).

  • Con agrupar. Una página no puede llevar las dos claves: se avisa y paginar se ignora. Partir cada grupo en páginas queda reservado, sin definir. paginar no se hereda: en un _datos.yml se avisa y se ignora, porque todas las páginas de la carpeta serían plantillas de páginas.

  • Los avisos y los errores. Un paginar al que le falta coleccion o porPagina avisa, con su línea, y no parte nada. Una clave que no admite se avisa y se ignora. Si ni siquiera es un mapa (paginar: sí), se avisa, se ignora y la página es una más. Un orden que no es un criterio válido se avisa y se usa el de coleccion(). En un fragmento (url: false) no hay dónde poner la primera página: se avisa y no se parte nada.

  • Sin vueltas. Las páginas que genera no están en ninguna colección, así que ninguna página puede depender de ellas por una colección: no hay cadenas que comprobar, a diferencia de agrupar (§6.3). La propia página tampoco entra en su trozo, aunque herede la etiqueta de su colección.

La compilación incremental da siempre lo mismo que la completa. Las páginas que se generan, o dejan de generarse, se deciden de nuevo en cada compilación, y una se rehace cuando cambia lo que la define, o su trozo: que entre, salga o cambie de sitio una página, o cambien los campos de alguna de ellas. Cómo se consigue es cosa de docs/compilacion.md.

El esquema (§11) no sabe de paginar: es cosa de quien desarrolla el sitio, y un editor ni la enseña ni la toca. Lo que sí consigue es que quien edita añada una entrada y la lista se reparta sola.

Ver este documento en GitHub (se abre en una pestaña nueva)