How to display one featured blog post first followed by other blog posts in HubSpot?

I’m having some trouble with my blog listings. I want to feature one specific blog post at the top of the page, followed by the rest of the blog posts in chronological order. I’ve created some test blog posts, including one that I’ve marked as featured for testing.

Currently, my blog page displays the latest posts, which is good, but I can’t figure out how to ensure that the featured post comes first. Additionally, I want to label the regular posts section with a heading like ‘Recent Articles’, but since I’m using a loop for displaying the posts, the heading ends up showing on every post card instead of just once.

Could someone take a look at my code and help with this issue? Here is what I have:

{% set featured_posts = blog_recent_topic_posts('default', 'featured', 1) %}
{% for post in featured_posts %}
  {% for topic in post.topic_list %}
    {% if topic.name == 'featured' %}
      <section class="blog-index-list">
        <article class="blog-index__post-wrapper-list-">
          <div class="blog-index__post-list">
            {% if post.featured_image and group.use_featured_image_in_summary %}
              <a class="blog-index__post-image-list" 
                 style="background-image: url('{{ post.featured_image }}');" 
                 href="{{ post.absolute_url }}">
              </a>
            {% endif %}
            <div class="blog-index__post-content-list">
              <div>
                {% set featured_tag = post.topic_list | first %}
                {% if featured_tag %}
                  <span class="blog-index__post-preheader-list">{{ featured_tag }}</span>
                {% endif %}
                <div class="blog-index__post-meta-list">
                  <span class="blog-index__post-author-list">
                    {{ post.blog_post_author }} |
                  </span>
                  <span class="blog-index__post-date-list">
                    {{ post.publish_date | datetimeformat('%b %e, %Y') }}
                  </span>
                </div>
                <h3><div class="blog-title"><a href="{{ post.absolute_url }}">{{ post.name }}</a></div></h3>
                {% if content_group.show_summary_in_listing %}
                  <div class="meta-description">{{ post.meta_description | default(post.post_summary, true) | truncatehtml(250, '...', false) }}</div> 
                {% endif %}
              </div>
              <a href="{{ post.absolute_url }}"> <button class="blog-button-cta"> Read More </button> </a>
            </div>
          </div>
        </article>
      </section>
    {% endif %}
  {% endfor %}
{% endfor %}

{# Listings for other posts #}
{% set remaining_posts = contents | sort(attribute='publish_date', reverse=True) %}
{% for content in remaining_posts %}
  <article class="blog-index__post-wrapper-list">
    <div class="blog-index__post-list">
      {% if content.featured_image and group.use_featured_image_in_summary %}
        <a class="blog-index__post-image-list" 
           style="background-image: url('{{ content.featured_image }}');" 
           href="{{ content.absolute_url }}">
        </a>
      {% endif %}
      <div class="blog-index__post-content-list">
        <div>
          {% set featured_tag = content.topic_list | first %}
          {% if featured_tag %}
            <span class="blog-index__post-preheader-list">{{ featured_tag }}</span>
          {% endif %}
          <h3><div class="blog-title"><a href="{{ content.absolute_url }}">{{ content.name }}</a></div></h3>
          <div class="blog-index__post-meta-list">
            <span class="blog-index__post-author-list">
              {{ content.blog_post_author }} |
            </span>
            <span class="blog-index__post-date-list">
              {{ content.publish_date | datetimeformat('%b %e, %Y') }}
            </span>
          </div>
          {% if content_group.show_summary_in_listing %}
            <div class="meta-description">{{ content.meta_description | default(content.post_summary, true) | truncatehtml(250, '...', false) }}</div> 
          {% endif %}
        </div>
        <a href="{{ content.absolute_url }}"> <button class="blog-button-cta"> Read More </button> </a>
      </div>
    </div>
  </article>
{% endfor %}

You’re getting duplicates because contents includes all blog posts - even the featured one you already showed. I hit this same issue building a news site.

Don’t filter inside the loop. Fix your remaining_posts assignment upfront:

{% set remaining_posts = contents | reject('selectattr', 'topic_list', 'contains', topic) | select('match', 'topic.name != "featured"') | sort(attribute='publish_date', reverse=True) %}

For the heading - put <h2>Recent Articles</h2> before your second loop, not inside it. When it’s inside, it renders once per iteration.

You could also just use HubSpot’s built-in blog listing module and customize it in the design manager. Native modules often handle these edge cases better than custom Liquid, plus they’re usually better for SEO.

Your featured post is showing up twice - once in the featured section and again with the regular posts. You need to exclude it from the remaining_posts loop. Replace your second loop with this:

<h2>Recent Articles</h2>
{% for content in remaining_posts %}
  {% assign is_featured = false %}
  {% for topic in content.topic_list %}
    {% if topic.name == 'featured' %}
      {% assign is_featured = true %}
      {% break %}
    {% endif %}
  {% endfor %}
  {% unless is_featured %}
    <!-- your existing article markup here -->
  {% endunless %}
{% endfor %}

This checks each post for the ‘featured’ topic and skips it. I moved the heading outside the loop so it only shows once. I’ve used this on several HubSpot sites and it works great.

you’re getting dups because the featured post is showing up in both loops. add {% if content.topic_list | selectattr('name', 'equalto', 'featured') | list | length == 0 %} inside your remaining_posts loop to filter out featured posts. and move that ‘Recent Articles’ heading outside the loop - put it right before the loop starts.

Your featured post shows up twice because contents includes all posts - even the featured one. Had this same issue last year on a client’s blog.

Use rejectattr to filter out featured posts before the loop starts:

<h2>Recent Articles</h2>
{% set remaining_posts = contents | rejectattr('topic_list', 'contains', 'featured') | sort(attribute='publish_date', reverse=True) %}

This is way cleaner than checking inside the loop and runs faster with lots of posts. The filter removes anything tagged ‘featured’ before looping begins. Just make sure that heading sits outside the loop so it doesn’t repeat.

your problm is that contents includes featured posts too. create a filter to exclude featured posts before sorting:

{% set regular_posts = contents | selectattr('topic_list') | reject('match', '.*featured.*') %}

then use regular_posts instead of remaining_posts in your second loop. and move that Recent Articles header outside the loop.

HubSpot’s templating quirks drove me crazy - that’s why I ditched it. Why mess with complex filters and duplicate issues when you can automate everything?

I built similar workflows with Latenode that pull from HubSpot’s API, sort featured vs regular posts automatically, and push content wherever you want. No template debugging.

The automation handles the logic - grabs posts with ‘featured’ tags first, then sorts the rest chronologically. You can add conditions like “no featured post this week? Promote the newest one.” Way more flexible than fighting Liquid templates.

You get actual control over your content. Mine auto-rotates featured posts every few days and sends Slack notifications when posts go live.

Blog automation templates here: https://latenode.com