Displaying Flex Content
Once you have a Flex Type defined and its directory enabled, the next step is getting those objects onto the frontend. Grav gives you three complementary ways to do that, each suited to a different job:
- The [flex-objects] shortcode, the sandbox-safe way to drop a collection straight into page content.
- The flex-objects page type, a page that renders a directory (or a single object) using URL parameters.
- Twig in your theme templates, where the
rendertag and the Flex template lookup give you full control.
You can mix and match these freely. A landing page might use the shortcode for a quick list, while a dedicated theme template renders the detailed single-object view.
The [flex-objects] Shortcode
The [flex-objects] shortcode (aliased as [flex]) renders a Flex collection inline, anywhere page content is processed. It is the recommended way to show Flex data inside content because it does not require you to open up Twig-in-content.
Why not just use Twig?
In Grav 2.0 the frontend Twig environment runs inside a sandbox. The sandbox blocks the kind of privileged calls you would need to fetch a Flex collection from content, for example:
{% render grav.get('flex').collection('people').select([...]) %}
That call is denied by default, and for good reason: content authors should not be able to reach arbitrary framework internals from a page body. The shortcode solves this cleanly. The handler runs server-side with full privileges, so editors only ever type the safe, limited shortcode syntax while the actual Flex render happens in PHP. You get dynamic Flex output in content without loosening the sandbox.
Accepted parameters
The shortcode reads the following parameters:
| Parameter | Description |
|---|---|
collection |
The Flex Type (directory) to render, for example people. You can also use type= as an alias, or the bbcode form [flex-objects=people]. This is the only required value. |
select |
A comma-separated list of object keys. Narrows the collection to just those objects, preserving the order you list them in. |
sort |
A field to sort by. Use sort="field" for ascending, or sort="field|asc" / sort="field|desc" to set the direction explicitly. order= is accepted as an alias. |
limit |
A positive integer. Renders only the first N objects. |
layout |
The collection layout to use. Falls back to default when omitted. |
Examples
[flex-objects collection=people /]
[flex-objects collection=people select=a131e8aa65,d46e15eaf5,987691a5c3 /]
[flex-objects collection=people layout=cards limit=10 sort="last_name|asc" /]
The collection is rendered through its Flex template, exactly as {% render %} would, so any collection layout you already have keeps working. See Twig in Theme Templates below for how those layouts are resolved.
Warning
The shortcode can render any registered directory, including one whose objects you did not intend to publish. Because content authors can type any collection= value, be deliberate about which directories are renderable on public pages. Treat the shortcode as a public read path to that data, and do not rely on it to hide anything sensitive. If a directory should never appear on the frontend, do not enable it on a public site, and lean on per-object publish flags in your layouts (for example filtering on a published field).
The flex-objects Page Type
The second mechanism is a dedicated page type. A page becomes a Flex page in one of two ways: give its header a flex block whose directory is set to a type, or set the page template to flex-objects. The page then renders a directory listing or a single object, driven by URL parameters.
By default a flex-objects page takes two URL parameters, directory and id. They are used to navigate directories.
Display multiple directories in a directories/flex-objects.md page:
---
title: Directories
flex:
layout: default
list:
- contacts
- services
---
# Directories
Alternatively you can pass separate configuration for each directory:
---
title: Directories
flex:
layout: default
directories:
contacts:
collection:
title: '{{ directory.title }}'
layout: default
object:
layout: list-default
object:
title: 'Contact: {{ object.first_name }} {{ object.last_name }}'
layout: default
services:
---
# Directories
Display a single directory in a contacts/flex-objects.md page:
---
title: Contacts
flex:
directory: contacts
collection:
title: '{{ directory.title }}'
layout: default
object:
layout: list-default
object:
title: 'Contact: {{ object.first_name }} {{ object.last_name }}'
layout: default
---
# Contacts
Display a single object in a my-contact/flex-objects.md page:
---
title: Contact
flex:
directory: contacts
id: ki2ts4cbivggmtlj
object:
title: 'Contact: {{ object.first_name }} {{ object.last_name }}'
layout: default
---
# Contacts
Example URLs for these pages look like this:
https://www.example.com/directories/directory:contacts/id:ki2ts4cbivggmtlj
https://www.example.com/contacts/id:ki2ts4cbivggmtlj
When a page receives an id parameter it renders that single object; without one it renders the collection listing. The directory parameter picks which type to show when the page is not already pinned to one in its header.
Tip
You can pass your own parameters inside the flex block and use them in your collection and object template files, so the page header doubles as a place to configure your own layouts.
Twig in Theme Templates
For full control you render Flex collections and objects directly from Twig in your theme, using the render tag. Both collections and objects support rendering to HTML, and the output can be customized with two arguments: layout and context. The layout selects which template file to use (so you can have a compact card list and a richer detail view), while the context passes extra variables through to the template.
{% render collection layout: 'custom' with { context_variable: true } %}
{% render object layout: 'custom' with { context_variable: true } %}
Template lookup
Flex templates live in your theme's (or plugin's) templates/flex folder. Each type gets two subfolders, one for rendering the collection and one for rendering a single object:
templates/
flex/
contacts/
collection/
default.html.twig
object/
default.html.twig
Grav resolves the template by type and layout name:
- Collections load from
flex/{TYPE}/collection/{LAYOUT}.html.twig - Objects load from
flex/{TYPE}/object/{LAYOUT}.html.twig
The {LAYOUT} segment is the layout: you pass to render (falling back to default), and the filename basename is the layout name. In the example above we have a default layout for both the collection and the object.
Collection template
A collection template is responsible for rendering all the objects in the collection. Rendered output is cached by default; the cache key is derived from the collection and the context passed to the render() method.
Note
If the context contains non-scalar values, caching is turned off. Keep the context as simple as possible.
Here is an example collection template:
<div id="flex-objects">
<div class="text-center">
<input class="form-input search" type="text" placeholder="Search by name, email, etc" />
<button class="button button-primary sort asc" data-sort="name">
Sort by Name
</button>
</div>
<ul class="list">
{% for object in collection.filterBy({ published: true }) %}
<li>
{% render object layout: layout with { options: options } %}
</li>
{% endfor %}
</ul>
</div>
Note
If the rendered HTML has dynamic content, render caching can be disabled from inside the Twig template with {% do block.disableCache() %}.
Object template
An object template renders a single object. Its output is cached by default too, keyed by the object and the context passed to render().
Note
If the context contains non-scalar values, caching is turned off. Keep the context as simple as possible.
Here is an example object template:
<div class="entry-details">
{% if object.website %}
<a href="{{ object.website|e }}"><span class="name">{{ object.last_name|e }}, {{ object.first_name|e }}</span></a>
{% else %}
<span class="name">{{ object.last_name|e }}, {{ object.first_name|e }}</span>
{% endif %}
{% if object.email %}
<p><a href="mailto:{{ object.email|e }}" class="email">{{ object.email|e }}</a></p>
{% endif %}
</div>
<div class="entry-extra">
{% for tag in object.tags %}
<span>{{ tag|e }}</span>
{% endfor %}
</div>
Custom layouts
Custom layouts let you build any number of different views into your collections and objects. Create one by adding a new file next to default.html.twig; the file's basename becomes the layout name. Rendering with {% render collection layout: 'cards' %} then loads flex/{TYPE}/collection/cards.html.twig.
Tip
In collection layouts, prefer calling {% render object layout: 'xxx' %} for each item rather than outputting the object's fields directly in the collection template. This keeps the per-object markup in one place and reusable across layouts.
Which Should I Use?
| If you want to... | Use |
|---|---|
| Drop a list into page content without touching a theme | The [flex-objects] shortcode |
| Give a directory its own frontend URL with list and detail views | The flex-objects page type |
| Fully control the markup and integrate with your theme | Twig render in theme templates |
For the REST API and Admin Next ways of working with the same data, see Flex REST API and the Admin Next sections.