Simple Website Framework

Posts, Categories & Filtering

Wednesday August 19th, 2026

Written by Scary le Poo

Posts are ordinary pages that live in pages/posts/ and carry a few extra metadata tags. The post archive layouts (postarchives, postarchives-notitle, postarchives-styled) scan that folder, read each post's metadata, and render a sorted, paginated listing. This page covers how posts are collected, how to organize them into categories, and how to write your own archive filters.


Anatomy of a Post

A post is any .html file in pages/posts/. For a post to appear in the archives, it must have all four of these tags:

<!-- pagetitle: My Post Title -->
<!-- pagedate: 8/19/2026 -->
<!-- pageimage: pages/posts/images/mypost.webp -->
<!-- pageexcerpt: A short blurb shown in the archive listing. -->

A post missing any of those four is silently skipped by the archive layouts. The postarchives-styled layout also displays pageauthor, and all of the usual page tags (pagelayout, pagekeywords, pagetype) work on posts exactly as they do on pages.


Assigning Categories to a Post

Categories are assigned with a single optional tag, pagecategory. It takes a comma-separated list, so a post can belong to any number of categories:

<!-- pagecategory: tutorials, seo -->

Category names are matched case-insensitively and whitespace around commas is ignored, so Tutorials, SEO and tutorials,seo are the same thing. Use lowercase, hyphenated names (game-dev, not Game Dev) — the name doubles as the category page's URL.

The tag is optional. A post without a pagecategory tag still appears in the main archives exactly as before, and is automatically treated as belonging to a built-in category called uncategorized. This means existing sites upgrade cleanly: nothing needs to be edited, and posts join categories as you tag them.


Creating a Category Page

A category page is a normal page that uses one of the post archive layouts plus one extra tag, postcategory, which tells the layout to only list posts belonging to that category:

<!-- pagetitle: Tutorials -->
<!-- pagelayout: postarchives-styled -->
<!-- postcategory: tutorials -->

Save that as pages/tutorials.html and yoursite.com/tutorials becomes the archive for the tutorials category. That's the whole mechanism — no configuration, no registry of categories. A category exists as soon as one post claims it and one page filters on it.

Naming convention: name the category page after the category, at the root of pages/. The postarchives-styled layout renders each post's categories as links built from this convention (the category tutorials links to /tutorials), so following it makes those links work automatically.

Your main archives page needs no changes. Any archive page without a postcategory tag lists every post, site-wide, regardless of category.


The Uncategorized Category

Posts with no pagecategory tag belong to the built-in uncategorized category. You can create a page for it like any other category:

<!-- pagetitle: Uncategorized -->
<!-- pagelayout: postarchives-styled -->
<!-- postcategory: uncategorized -->

This is especially useful when adopting categories on an existing site — it's a live to-do list of every post that still needs tagging. Once every post is tagged, the page simply renders empty (or you can delete it).


Watch Out for Typos

Because there is no central list of categories, a typo creates an orphan: a post tagged pagecategory: tutorails silently disappears from the tutorials page. If a post isn't showing up where you expect, check the spelling of its pagecategory tag first — it's matched exactly (after lowercasing and trimming) against the page's postcategory value.


How the Filter Works (For Theme Developers)

Inside the archive layouts, each post's categories are parsed into a categories array on its $fileDetails entry, and the filter runs between collection and sorting:

if (!empty($postcategory)) {
    $filterCategory = strtolower(trim($postcategory));
    $fileDetails = array_values(array_filter($fileDetails, function ($post) use ($filterCategory) {
        return in_array($filterCategory, $post['categories']);
    }));
}

$postcategory arrives automatically — it's extracted from the current page's metadata by required/vitalfunctions.php like every other page tag. One important rule when customizing: the category tag must never be added to the required-fields check (if ($titleMatch && $dateMatch && ...)). It is deliberately optional; requiring it would make every untagged post vanish from every archive.


Custom Filtering

The same pattern extends to any metadata. Everything the archive knows about each post lives in the $fileDetails array, so custom archive layouts can filter on anything by adding an array_filter before the sorting step. Some examples:

Only posts from a date range:

$fileDetails = array_values(array_filter($fileDetails, function ($post) {
    return strtotime($post['date']) >= strtotime('1/1/2026');
}));

Only posts by a specific author (the styled layout already extracts author; add the pageauthor preg_match to the other layouts if you need it there):

$fileDetails = array_values(array_filter($fileDetails, function ($post) {
    return $post['author'] === 'Scary le Poo';
}));

Combining conditions — a category page showing only recent posts in that category — is just both checks in one callback. To filter on a tag the layouts don't currently extract (say pagetype), add one preg_match to the collection loop, store the value in the $fileDetails[] = array(...) line, and filter on it.

Filters run on every render, but with caching enabled that cost is paid once per cache lifetime, not per visitor.


Caching Notes

Category pages are ordinary pages with ordinary URLs, so they work with the HTML cache with no special handling — each category page gets its own cache file, and pagination works because the ?page parameter is preserved in the cache key. This is the main reason categories are metadata-driven rather than query-string-driven (archives?category=foo would not survive the cache).

The usual caching caveat applies: after tagging posts or creating category pages on a live site with caching enabled, delete the relevant cached-*.html files (or wait out the cache lifetime) to see the changes.


RSS

The RSS feed understands categories too. ?rss still lists every post, and giving the parameter a value filters the feed: ?rss=tutorials for one category, ?rss=tutorials,seo for several (a post matches if it's in any of them), and ?rss=uncategorized for untagged posts. Filtered feeds append the category name to the channel title. See The RSS Feed for details.