How to Exclude Certain Blog Posts from Blog Listings While Keeping Them Accessible via URL

Is there a way to prevent certain posts within a specific blog type from being displayed on the front end?
For example, in a Hubl template we might loop through posts like this:

{% for content in contents %}

This is often paired with pagination.

If we try to manipulate contents by, say, hiding posts with a specific tag using CSS display:none, the pagination becomes broken and the approach feels unnatural.

  1. Does HubSpot provide any built-in functionality to exclude only specific posts from being shown on the front end?
    (I don’t think draft or archive status covers this case.)
  2. Is it possible to keep a post hidden from the front end while still allowing access via direct URL?
  3. Since {% for content in contents %} will always output these posts, is the only practical option to create a separate blog type dedicated to posts that shouldn’t appear on the front end?

Hi @TMisuna,

One approach you could use is to add a specific tag to posts you want to hide and then exclude posts with that tag in your for loops.

This would work on your blog listing template (“/blog”) or other custom modules (i.e. recent blog posts) but would still be visible on the “/blog/all” page that shows every single post. However, if you don’t include a link to that “all” page on your website, then the likelihood that a visitor would land there is quite low.

Hope this helps!

Thank you.

Are there any specific code samples available somewhere for this?

Sure. The HubSpot developer docs is a good resource for all the Blog variables available.

One option you could do is to use the “unless” statement and specify the slug of that tag (in this case the tag slug “exclude-me”):

{% for content in contents %}
 {% unless content.tag_list|selectattr("slug", "equalto", "exclude-me")|list|length %}
 <!-- your blog card/summary -->
 <div><a href="{{ content.absolute_url }}">{{ content.name }}</a></div>
 {% endunless %}
{% endfor %}

Though, I realize this may not be ideal for you. This option will exclude posts containing that tag but you may run into situations where pages might look uneven, as they get still accounted for by the loop for that page, just not displayed (similar to display:none; but the HTML is not even present).

Another approach would be to use the “blog_recent_posts” function.

{% set posts = blog_recent_posts("default", 100) %}

{% for post in posts %}
 {% set ns = namespace(exclude=false) %}

 {% for tag in post.tag_list %}
 {% if tag.slug == "exclude-me" %}
 {% set ns.exclude = true %}
 {% endif %}
 {% endfor %}

 {% if not ns.exclude %}
 <!-- your blog post card/summary -->
 <div><a href="{{ post.absolute_url }}">{{ post.name }}</a></div>
 {% endif %}
{% endfor %}

The downside here is that you would need to set up your own custom pagination, unless you lazy-load them on the blog listing without using pages.

You may need to weigh whether the effort of custom set up is worth what you might achieve by simply setting up a separate blog for those posts, as there currently aren’t any settings that would allow to do this easily.

Thank you for your thoughtful reply.

I tried the former approach, but as expected, when pagination is involved, it becomes distorted and customizing it is quite difficult.
If only pagination worked correctly, that would be enough, but it seems challenging.

I may give up and either create a new blog type or consider using website pages instead of the blog function.

Just in case you had not come across this option, wanted to mention that you can also set up private content for blogs. The private posts would still be visible in the listing but would require registration in order to view them.

Does not address the hiding part but dropping the link below in case useful:

Something like:

{% set tag_that_hides = "hidden" %}
{% for content in contents %}
 {% if tag_that_hides in content.tag_list|map(attribute="name") %} 
 <!-- do nothing here -->
 {% else %}
 <img
 class="hs-blog-post-listing__post-image hs-blog-post-listing__post-image--{{ layout|escape_attr }}"
 src="{{ content.featured_image|escape_url }}"
 loading="lazy"
 alt="{{ content.featured_image_alt_text|escape_attr }}"
 >
 <h2 class="hs-blog-post-listing__post-title">
 <a class="hs-blog-post-listing__post-title-link" href="{{ content.absolute_url|escape_url }}">{{ content.name|sanitize_html }}</a></h2>
 <time class="hs-blog-post-listing__post-timestamp" datetime="{{ content.publish_date|escape_attr }}">
 {{ content.publish_date_localized|sanitize_html }}
 </time>
 {% endif %}
{% endfor %}

Should work for you on the listing page. Just make sure that you’ve typed the values properly. If within the blog post you’re displaying each post’s tags, you might want to exclude the ‘hidden’ tag from showing up there as well. There is likely a loop that iterates through all the tags in the post and displays them at which point you can add an if statement to effectively not do anything when the controlling tag pulls through.