K KodePressdocumentation
Laravel 11 · MySQL 8 Specification Built 2026-10-10

06 — Blog, taxonomy and SEO#

6.1 Posts are pages#

A post is a row in pages with type = 'post'. It uses the same editor, the same block tree, the same version history and the same publish pipeline. There is no separate posts table and no separate editor. What posts add:

Field Column Purpose
Excerpt pages.excerpt list summaries, meta description fallback, RSS
Featured image pages.featured_media_id list cards, OG image fallback
Author pages.author_id byline, author archive
Categories category_page archives, menus, filtering
Primary category pages.primary_category_id breadcrumbs, permalink if configured
Tags page_tag archives, related posts
Publish date pages.published_at ordering, scheduling

The right panel in the editor gains a Post tab holding exactly these. Nothing about the content editing experience changes.

The pattern lives in sites.options.permalink_pattern, default blog/{slug}.

Pattern Produces
blog/{slug} /blog/my-first-post (default)
{slug} /my-first-post
{category}/{slug} /news/my-first-post (uses primary_category_id, falls back to blog)
blog/{year}/{month}/{slug} /blog/2026/10/my-first-post

The resolved value is written to pages.path, so public lookup stays one indexed query regardless of the pattern. Changing the pattern is an explicit action in Settings -> SEO that rewrites every post path and writes a 301 for each one; the screen states how many URLs will change before it runs, and it runs in chunks via cron if there are more than 200 posts.

6.3 Taxonomy#

Archive URLs:

Archive URL Notes
Blog index /blog a real pages row of type page whose tree contains a post_list block, so the owner can design it
Category /blog/category/{slug} nested categories use the full path: /blog/category/news/local
Tag /blog/tag/{slug}
Author /author/{user-slug} user slug derived from the name, stored in users as a computed unique slug
Search /search?q=... never cached, noindex
RSS /feed, /blog/category/{slug}/feed RSS 2.0, 20 latest items, full excerpt

Archive pages are rendered by a designable layout: Design -> Archives holds a template part tree for archive, so a non-technical owner can change the card design without touching Blade. The default archive template is a single section containing one post_list block pre-filtered by the archive context.

6.4 The post_list block#

Setting Values Default
Source latest, by category, by tag, by author, manual selection, related to the current post latest
Categories / tags multi-select none
Layout grid, list, carousel, headline list grid
Columns 1–4 3
Count 1–24 6
Show image, date, author, excerpt, category, read-more (toggles) image, date, excerpt
Order newest, oldest, title, random newest
Pagination none, numbers, load more none
Empty state translated message, editable default text

In an archive context the block inherits the archive filter automatically (Source: archive context), which is how one block serves the index, category, tag and author pages.

Pagination uses ?page=N. A paginated URL is cached per page number and is noindex past page 1 by default (configurable), with correct rel=prev/next.

6.5 Scheduled publishing#

6.6 Per-page SEO#

pages.seo keys, all optional, all editable in an SEO tab in the editor:

Key Fallback when empty
title page title + | + site name (template configurable)
description excerpt, else the first text block, trimmed to 160 chars
og_media_id featured image, else the site default OG image
canonical the page URL
noindex false
nofollow false

The SEO tab shows a live Google-style preview and a character counter with soft limits (60 / 160). It warns, it never blocks.

Emitted per page: <title>, description, canonical, og:*, twitter:card, hreflang alternates for translations, and JSON-LD — WebSite + Organization on the homepage, Article on posts (headline, image, datePublished, dateModified, author), BreadcrumbList where a hierarchy exists.

6.7 sitemap.xml and robots.txt#

6.8 Redirects#

Source Created when
auto a page slug or parent changes (one row per changed path, including descendants); a category or tag slug changes; the permalink pattern changes
manual the owner adds one in Settings -> Redirects

Matching order on a 404-bound request: exact from_path match first, then regex rules in id order. from_path is stored lower-cased without a leading slash; matching ignores the query string but preserves it on the redirect target. Loops are refused at creation time (A -> B when B -> A exists) and the resolver follows at most 3 hops before giving up with a 404.

The redirect screen shows hits and last-hit time, so dead rules can be pruned, and offers a CSV import/export.

Public search is MySQL-based: a LIKE-plus-relevance query over pages.title, pages.excerpt and a plain-text projection of the published tree stored in page_versions at publish time (a search_text column is not in the schema — the projection is stored in the file cache keyed by page and rebuilt on publish; see ADR-0006).

Bengali matters here: MySQL full-text with the default parser does not tokenise Bengali usefully, so the search normalises the query and the projection the same way (NFC, fold digits, strip zero-width joiners) and matches on substrings. Results are ranked title > excerpt > body. The search page is never cached and is noindex.

6.10 Tests for this document#

Test Asserts
post_uses_same_editor creating a post and publishing stores a page_versions row exactly as a page does
scheduled_post_publishes_on_cron travel past published_at, run the command, page becomes public and the cache is cleared
slug_change_creates_301 renaming a page redirects the old path, including for a child page
nested_category_archive_resolves /blog/category/news/local lists posts from the child category
sitemap_excludes_noindex a noindex page is absent from sitemap.xml
permalink_change_rewrites_all_posts pattern change updates paths and writes one redirect per post
search_finds_bengali_title a Bengali title is found by a Bengali substring query
rss_feed_is_valid /feed returns well-formed RSS with 20 items

KodePress documentation · generated from the Markdown sources by tools/build-docs-site.py · internal preview, not indexed.