Posts and comments
On a listing page, walk the posts with {each blog_posts as=post} and read each entry through the loop alias. On a single-post page, the current post is also exposed as {blog_post} and its related posts as {each blog_post_related as=related}. Every post, in a loop or on its own, exposes the same fields.
The post loop
{each blog_posts as=post limit=3}
<article>
<a href="{post.url}"><img src="{post.image}" alt="{post.image_alt}"></a>
{if post.category_name}<p>{post.category_name}</p>{/if}
<h2><a href="{post.url}">{post.title}</a></h2>
<p>{post.excerpt}</p>
<p>{post.published_at} - {post.reading_time_text} read</p>
</article>
{/each blog_posts}
limit caps the number of posts and offset skips posts from the start. Both take a whole number, so limit=9 offset=1 renders nine posts after the first. {post.index} is the position in the rendered run, starting at 1.
Post fields
Read a field with the loop alias, for example {post.title}, or with {blog_post.title} on a single-post page. On a single post the alias post is also available.
Content and identity
| Field | Meaning |
|---|---|
{post.title} | Post title. |
{post.slug} | URL slug. |
{post.url} | Full path, for example /blog/hello. |
{post.link} | Same value as {post.url}. |
{post.excerpt} | Summary text. |
{post.subtitle} | Same value as {post.excerpt}. |
{post.content} | Rendered body as HTML. |
{post.image} | Featured image URL. |
{post.image_url} | Same value as {post.image}. |
{post.image_alt} | Featured image alt text. |
{post.date} | Publication date, formatted for reading. |
{post.published_at} | Same value as {post.date}. |
{post.comments_count} | Number of approved comments. |
{post.categories} | Category links as HTML. |
{post.tags} | Tag links as HTML. |
{post.index} | Position in the loop, starting at 1. |
Author
| Field | Meaning |
|---|---|
{post.author} | Author display name. |
{post.author_name} | Same value as {post.author}. |
{post.author_url} | Author archive path. |
{post.author_bio} | Author biography. |
{post.author_image} | Author profile image URL. |
Category
| Field | Meaning |
|---|---|
{post.category} | Primary category name. |
{post.category_name} | Same value as {post.category}. |
{post.category_url} | Primary category path. |
Reading and length
| Field | Meaning |
|---|---|
{post.reading_time} | Reading time in whole minutes. |
{post.read_time} | Same value as {post.reading_time}. |
{post.reading_time_text} | Reading time as text, for example 8 min. |
{post.read_time_text} | Same value as {post.reading_time_text}. |
{post.word_count} | Word count of the body. |
{post.heading_count} | Count of level-two and level-three headings in the body. |
Reading flags
Each flag is a presence test for one of the fields above. Use it to keep an element in place only when it has something to show.
| Flag | True when |
|---|---|
{if post.has_image} | A featured image is set. |
{if post.no_image} | No featured image is set. |
{if post.has_category} | A primary category is set. |
{if post.no_category} | No primary category is set. |
{if post.has_excerpt} | An excerpt is set and differs from the body. |
{if post.no_excerpt} | There is no distinct excerpt. |
{if post.has_author} | An author name is set. |
{if post.no_author} | No author name is set. |
{if post.has_bio} | An author biography is set. |
{if post.no_bio} | No author biography is set. |
{if post.has_toc} | The body has three or more headings. |
{if post.no_toc} | The body has fewer than three headings. |
{if post.is_long} | The body has 250 words or more. |
{if post.is_short} | The body has fewer than 250 words. |
{if post.show_inline_cta} | A long body with at least two headings. |
{if post.no_inline_cta} | The post is not eligible for an inline call to action. |
{if post.has_author}
<p>By {post.author}{if post.has_bio} - {post.author_bio}{/if}</p>
{/if}
Single-post fields
On a single-post page the current post is bound to {blog_post} as well as {post}, so the same fields work without a loop.
<article>
{if blog_post.has_category}
<a href="{blog_post.category_url}">{blog_post.category_name}</a>
{/if}
<h1>{blog_post.title}</h1>
{if blog_post.has_excerpt}<p>{blog_post.excerpt}</p>{/if}
<p>{blog_post.published_at} - {blog_post.reading_time_text} read</p>
{if blog_post.has_image}<img src="{blog_post.image}" alt="{blog_post.image_alt}">{/if}
<div>{blog_post.content}</div>
</article>
{blog_post.content} and the {post.content} alias hold rendered body HTML, so place them as content and never inside an attribute.
Related posts
{each blog_post_related as=related} walks the posts related to the current post. It takes the same limit and offset parameters and exposes the same fields through the related alias.
{each blog_post_related as=related limit=3}
<article>
<h3><a href="{related.url}">{related.title}</a></h3>
<p>{related.excerpt}</p>
</article>
{/each}
Wrap the section in {if blog_post_related} when you only want it to appear once there is at least one related post.
Comments
{blog.comments} outputs the approved comments and the comment form as HTML. It is empty when the post or the blog has comments turned off, so test it before rendering a wrapper.
{if blog.comments}
<section>
<h2>Comments</h2>
{blog.comments}
</section>
{/if}
The form posts to the public comments endpoint and shows the new comment or a moderation notice after submission. {post.comments_count} reports the approved comments for a post inside the listing loop.
Pagination
{blog.pagination} outputs the newer and older links for the listing. It is empty on the first page when there is no previous page and on the last page when there is no next. Pair it with {blog.page} to show the reader where they are.
<div class="listing">
{each blog_posts as=post}
<a href="{post.url}">{post.title}</a>
{/each}
</div>
<p>Page {blog.page}</p>
{blog.pagination}

