Creating a page with posts filtered by tag

Apologize if this is a basic question, but I think I’m stuck on a conceptual understanding of something related to tags. I have a blog loop on my home page, and I’d like to display the post’s tag linked to a page that contains posts with that tag.

I have the tag fields successfully set up in the panel and displaying correctly on my posts, however I can’t get the links to work right. Do I need to actually create a separate ‘tag’ page and template to link to such a filtered list of posts?

So far, in my templates (both on the home page template and on the single post template), I’m using this to display the tag and constructing the link:

<?php foreach ($page->tags()->split() as $tag): ?>
			<a href="<?php echo url('/' . url::paramsToString(['tag' => $tag])) ?>">#<?php echo html($tag) ?></a>
		<?php endforeach ?>

This results in the following output:

<a href="link_to_base_site_url">#nameoftag</a>

Any ideas on what I’m missing? Thinking maybe I need to create a specific, dedicated tag.php page and template to display these? I guess I was assuming that this would still work falling back to the default.php template, but maybe not? Thanks!

I understand I need to create a controller in order to create the filtered list of posts, as explained on this post. Do I create this controller in conjunction with a new page/template, as I mention in the OP? Thanks!

I would create a category-page + controller (or a route).

There’s a special recipe for this in the cookbook. You should check it out :+1:

1 Like

Is a category page a special kind of page? Or do you just mean create a page intended to hold the filtered list of posts (like I’m trying to communicate in the OP), and then create a corresponding controller which would house the filtered loop referenced in your link?

And thanks for the reply! :slight_smile:

Where do you display your list of posts? On the homepage? Then you can do the filtering in the homepage controller. Same as in this example, but on the home page instead of in the blog page: https://getkirby.com/docs/cookbook/content/filtering-with-tags#blog-controller

1 Like

I display a list of all posts on the home page. Each post is tagged, and I want to be able to click a tag to see a filtered list of all posts with that tag.

You can see a working example of what I’m attempting to recreate here: https://polaricecap.net/

Then all that is left to do is add the filtering by tag in your home.php controller, like in the example I posted, at least if you use tag: sometag. If you want to use a slug for the tag, you have to do it with a route, like in the example @bart posted above.

Thanks, I don’t use a home.php controller. In fact I 'm not using any controllers yet. My site is very basic — I just use a default.php template that calls a blog_loop.php snippet for the loop. In this case would I create a default.php controller with the logic you linked to? I just tried that and it had no effect on the output of my tag links, unfortunately.

Controllers need to match the name of template they belong too, so if you home page is using home.php as its template, then the controller also needs to be named home.php.

Sounds to me like you just running the site off the default.php template that came with kirby. Is that correct?

As I said, if you want to use a url scheme like mysite.com/tags/mytag you need an additional route.

But yes, if your only template is the default.php template, then the matching controller would be a default.php controller.

My home page is just using default.php as it’s template, which is extremely basic, containing only a simple header, footer, and a blog loop which I’m storing in a snippet called blog_loop.php. Here’s what the loop looks like:

<?php foreach($posts = page('blog')->children()->listed()->flip()->paginate(10) as $post): ?>

	<article>
		<figure>
			<a href="<?= $post->url() ?>">
				<?php foreach ($post->images() as $img): ?>
					<img src="<?= $img->url() ?>" alt="<?= $post->title()->html() ?>"></li>
				<?php endforeach ?>
			</a>
		</figure>
		<h1><a href="<?= $post->url() ?>"><?= $post->title()->html() ?></a></h1>
			<?php foreach ($post->tags()->split() as $tag): ?>
				<a href="<?php echo url('/' . url::paramsToString(['tag' => $tag])) ?>">#<?php echo html($tag) ?></a>
			<?php endforeach ?>
	</article>
<?php endforeach ?>	
<!-- END BLOG LOOP -->

<!-- PAGINATION -->
<?php if ($posts->pagination()->hasPages()): ?>
	<nav class="pagination">

	<?php if ($posts->pagination()->hasNextPage()): ?>
		<a class="next" href="<?= $posts->pagination()->nextPageURL() ?>">‹ older posts</a>
	<?php endif ?>

	<?php if ($posts->pagination()->hasPrevPage()): ?>
		<a class="prev" href="<?= $posts->pagination()->prevPageURL() ?>">newer posts ›</a>
	<?php endif ?>

	</nav>
<?php endif ?>
<!-- END PAGINATION -->

Sorry, I meant another filtering example: https://getkirby.com/docs/cookbook/content/filter-via-route

Note that if you set the list of posts in your template, any filtering you do in a controller will be overwritten by your template. So once you use a controller, remove your $posts definition from the template.

1 Like

Hadn’t seen that one yet, thanks! Will work through it.

See the edit I made to my last post!

1 Like

I solved this by integrating the Filterizr JavaScript library into my site. The page is still under construction, but here you can see that it works: https://www.sgp.de/sgpneu/referenzen

Sorry to bump this up again: I had this a while ago (Tags: list results of all existing pages), and with Kirby 2 it worked like a charm, but now, updating to Kirby 3, I cannot make it work, even if I don’t see too many differences in the code. Maybe something got lost with all the cop&paste back and forth:

So this is what I have:

The article.php:

<div class="tag">
        <?php foreach($page->tags()->split(',') as $tag): ?>
        <a <?php e($tag == urlencode(param('tag')), '') ?> href="<?= url('results/tag:' . $tag)?>"><?= $tag ?></a>
        <?php endforeach ?>
      </div>

Everything looks fine here.

The results.php controller according to the cookbook (“Controlling the filter by URL”)):

<?php return function($site, $pages, $page) {

  // fetch the basic set of articles
	
$articles = $site->index()->filterBy('template', 'article');

  // add the tag filter
  if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', $tag, ',');
  }

  return compact('articles');
  
};
?>

And here is the results.php that is meant to display the titles of all the articles that have a certain tag in common:

<div class="content">
	<?php
		$tag = param('tag');
	?>
	<h3 class="tag-title"><?= param('tag'); ?></h3>
		<?= $page->text()->kirbytext() ?>
	<?php foreach($articles as $article): ?>
	<div class="tag-list">
		<div class="tagdate"><?= $article->date()->toDate('%d. %B %Y') ?></div>
		<div class="result"><a href="<?= $article->url() ?>"><?= $article->title()->html() ?></a></div>
	</div>	
	<?php endforeach ?>
</div>

What happens is that in the new result-page, coming from a tag-link on an article page, only the h3-title is displayed, but not the umlaut nor an empty space. The rest: date and titles of the articels don’t appear at all. I wonder what I am doing wrong…

If you have umlauts and spaces in your tags, you have to urlencode() and urldecode() them (and the e() helper logic also seems somehow incomplete. If you don’t want to apply and active class, you might as well remove the complete statement)

<a <?php e($tag == urlencode(param('tag')), ' class="active"', '') ?> href="<?= url('results/tag:' . urlencode($tag))?>"><?= $tag ?></a>

In your controller

 if($tag = param('tag')) {
    $articles = $articles->filterBy('tags', urldecode($tag), ',');
  }

You are up very late! Thanks a lot!

Now I see it, purrrfect! Accordingly the title should look that way:

<h3 class="tag-title"><?= urldecode(param('tag')); ?></h3>

And in the controller I get the missing links by adding

$tag = urldecode(param('tag'));

And this is the complete result.php controller:

<?php return function($site, $pages, $page) {

  // fetch the basic set of articles
	
$articles = $site->index()->filterBy('template', 'article');

  // add the tag filter

$tag = urldecode(param('tag'));

  if(param('tag')) {
    $articles = $articles->filterBy('tags', urldecode($tag), ',');
  }

  return compact('articles');
  
};
?>

Good night!

1 Like