Skip to content
Article Maintainers, Blogs & Comments
☀️ Light Clean and bright
📜 Sepia Warm and vintage
🌤️ Light Gray Subtle and neutral
🔵 Light Blue Calm and serene
🌙 Dark Easy on the eyes
🌑 Dark Gray Deep and modern
🔷 Dark Blue Professional and sleek
🌿 Forest Calm and natural

Maintainers, Blogs & Comments

This is the authoring reference for three docs features: maintainer profiles, blog posts, and blog commenting. Everything here is driven by page front matter — the key: value block at the top of a Markdown file between --- fences. No code changes are needed to add a maintainer, publish a post, or turn comments on.

A note on image paths that applies throughout: avatar, cover, and blog image are resolved against the asset base, i.e. the plugin's public/ folder. So avatar: img/jane.jpg loads from <plugin>/public/img/jane.jpg. If an image doesn't show, open that resolved URL directly — a 404 means the file isn't where the path points, not a bug in the page.


Maintainers

The maintainers feature has two kinds of page: one index that lists everyone as cards, and one profile per person that shows a full social-style header (cover banner, circular avatar, contacts, socials, projects) above a Markdown bio.

How a page is recognised

  • A page becomes the index when its front matter sets maintainers_dir.
  • A page becomes a profile when it lives inside a maintainers folder (maintainers/ or team/), or when it sets maintainer: true from any folder. Profiles do not need maintainers_dir — they're detected by location.

The index page

Create one file (for example maintainers.md) with this front matter:

KeyRequiredDescription
titleyesHeading for the list page, e.g. Core Maintainers.
maintainers_diryesThe folder that holds profile files. Normally maintainers.
descriptionnoShort intro shown by the theme.
ordernoSidebar/sort weight. Higher sorts earlier.
---
title: Core Maintainers
description: The people who build and steward SepoDesk.
maintainers_dir: maintainers
order: 90
---

Meet the team. Click any profile for full details.

Every non-underscore file inside the maintainers/ folder then appears as a card, sorted by order (highest first), then by name.

A maintainer profile

Create one Markdown file per person inside the maintainers/ folder. The Markdown body becomes the bio shown under the header.

KeyRequiredDescription
titleyesThe person's name.
rolenoJob/role line, e.g. Core Maintainer — Routing.
avatarnoCircular profile image, from public/. Falls back to a placeholder icon if omitted.
covernoWide banner image behind the avatar, from public/. Falls back to a gradient if omitted.
emailnoShown as a mailto: chip.
locationnoShown with a pin icon.
descriptionnoOne-line blurb shown on the list card (not the profile page).
socialsnoPipe list of social links (see below).
projectsnoPipe list of projects (see below).
ordernoCard sort weight. Higher sorts earlier.
maintainernoSet true only if the file lives outside the maintainers//team/ folders and you still want it treated as a profile.

banner is accepted as an alias for cover; image/photo are accepted aliases for avatar.

socials and projects syntax

Both use the same pipe-delimited list the rest of the theme uses. Entries are separated by commas; fields within an entry by |.

  • socialsLabel|URL. The label picks the icon automatically.
  socials: [GitHub|https://github.com/jane, Website|https://jane.dev, LinkedIn|https://linkedin.com/in/jane]
  • projectsName|URL|Note. The note is an optional short description.
  projects: [SepoEngine Core|https://github.com/sepodesk/core|Routing & CMS, Sepo CLI|https://github.com/sepodesk/cli|Scaffolding]

Because entries are comma-separated, avoid commas inside a label, URL, or note.

Social icon mapping

The label you write is matched (case-insensitively) to a built-in icon. Anything unmatched gets a generic link icon.

If the label contains…Icon shown
github, gitlab, bitbucket, git, codecode
mail, emailmail
twitter, x, mastodon, threads, discord, slack, telegram, chatmessage
youtube, vimeo, twitchfilm
instagram, dribbble, behance, photocamera
linkedin, facebookusers
site, web, blog, portfolio, homeglobe
(anything else)link

Full profile example

---
title: Jane Okoro
role: Core Maintainer — Routing & CMS
avatar: img/team/jane.jpg
cover: img/team/jane-cover.jpg
email: jane@sepodesk.dev
location: Nairobi, KE
description: Leads the router, page store, and docs mount.
socials: [GitHub|https://github.com/jane, Website|https://jane.dev]
projects: [SepoEngine Core|https://github.com/sepodesk/core|Router & page store, Docs Renderer|/docs|The docs mount]
order: 100
---

Jane has maintained the routing layer since the first release.

## Focus areas

- Multi-root URL resolution
- The file-backed page store

Making it reachable

Maintainer files are hidden from the left sidebar automatically (the folder is in the sidebar's exclusion list alongside blogs). To surface the index in the top navigation, add a line to _nav.md:

- [Maintainers](/docs/maintainers)

Blogs

Blogs also have an index (the post list) and individual posts.

The blog index page

One file (for example blog.md) turns on the aggregator:

KeyRequiredDescription
titleyesHeading for the list page.
aggregate_diryesThe folder that holds posts. Normally blogs.
ordernoSidebar/sort weight.

The index renders every post in that folder as a card with list/grid toggle, pagination (10 per page), a support widget, and a featured-sponsor panel built from posts that carry an advert.

A blog post

Create one Markdown file per post inside the blogs/ folder.

KeyRequiredDescription
titleyesPost title.
datenoDisplay date, shown in the list meta line.
authornoShown in the list meta line.
descriptionnoCard blurb (alias: subtitle).
imagenoThumbnail on the list and a short banner at the top of the post (alias: hero_image). From public/.
advertnoFeatured-sponsor entry, `LabelURLImagePath` (see below).
ordernoList sort weight. Higher sorts earlier.
allowCommentOnPostnotrue turns on the comment thread and form. Defaults to off.
allowCommentsListToGuestUptonoHow many comments a signed-out visitor sees before a login prompt. Defaults to 0.

The banner image is intentionally short (about 210px, less on mobile). The two comment keys are covered in full in the next section.

A single pipe-delimited value: Label|URL|ImagePath. The image path resolves from public/. Posts with an advert are collected into the "Featured Sponsors" panel on the index (first four on the page).

advert: Acme Hosting|https://acme.example|img/ads/acme.png

The support widget (_support.md)

The donate/sponsor card on the blog index is configured by a file named _support.md inside the blogs/ folder (underscore-prefixed files are never listed as posts). Its front matter:

KeyDescription
titleCard heading.
descriptionShort text under the heading.
raisedAmount raised (number).
goalFundraising goal (number).
currencyCurrency symbol, e.g. $.
donate_urlDonate button link.
sponsor_urlSponsor button link.
donate_button_textDonate button label.
sponsor_button_textSponsor button label.

Full post example

---
title: Shipping the new router
date: 2026-08-01
author: Jane Okoro
description: How multi-root URL resolution finally landed.
image: img/blog/router.jpg
advert: Acme Hosting|https://acme.example|img/ads/acme.png
allowCommentOnPost: true
allowCommentsListToGuestUpto: 5
order: 100
---

The routing rewrite is done. Here's what changed…

Comments

Comments are attached to blog posts and controlled entirely by the two post front-matter keys above.

Turning them on

KeyEffect
allowCommentOnPost: trueShows the comment thread. Signed-in visitors also get the comment form; the server refuses posts to any post where this is not true.
allowCommentsListToGuestUptoThe number of top-level comments a signed-out visitor may read. Beyond that they see a "log in to read them" prompt. Signed-in visitors always see everything. Set to 0 to show guests none.

Omit allowCommentOnPost (or set it false) and the post has no comments at all.

Who can do what

  • Signed-out visitors can read up to allowCommentsListToGuestUpto comments, then are prompted to log in. They cannot post.
  • Signed-in visitors can read everything, post comments, and reply to any comment.
  • Editing and deleting a comment is allowed for its author or an admin. This is enforced on the server — the buttons alone grant nothing.

How a commenter appears

The form does not expose real names from the database. Each commenter chooses how they're shown:

  • Nickname — type one in the nickname field.
  • Anonymous — leave the nickname blank; the comment shows as "Anonymous".
  • Email metadata — an opt-in checkbox to attach their account email to the comment's stored metadata. The email is kept as metadata and is not printed in the thread.

Internally each comment stores the session user_id as an ownership key (never displayed) so edit/delete can be authorised.

Where comments live (data model)

Comments are stored as one JSON file per post, as a nested tree:

content/pages/blogs/comments/<post-basename>.comments.json

<post-basename> is the post's filename without its folder or .md extension — so blogs/shipping-the-new-router.md maps to shipping-the-new-router.comments.json.

Each comment node:

FieldDescription
idUnique comment id.
nicknameDisplay name, or Anonymous.
bodyComment text.
timestampUnix time posted.
user_idAuthor's session id — ownership key, never shown.
repliesArray of child comment nodes (same shape).
edited_timestampPresent after an edit.

API reference

All comment actions use one endpoint:

POST /api/v2/pages/comments        (requires AuthMiddleware — any signed-in user)

The action is selected by the action field. JSON body fields:

FieldUsed byDescription
slugallThe post's slug, e.g. blogs/my-post.md.
actionallcreate (default), edit, or delete.
comment_bodycreate, editThe comment text.
nicknamecreateChosen display name (blank → Anonymous).
parent_idcreateSet to reply under an existing comment.
comment_idedit, deleteWhich comment to change.
use_emailcreate1 to attach account email as metadata.

Responses follow the site convention: { "status": "success", "data": … } or { "status": "error", "message": … } with an appropriate HTTP code (401 not signed in, 403 not permitted / comments closed, 404 comment not found, 422 validation).

Security model

  • Only signed-in users reach the endpoint (AuthMiddleware), and the route sits outside the admin-only pages group, so ordinary members can comment.
  • create is refused unless the target post's front matter has allowCommentOnPost: true.
  • edit and delete require the caller's user_id to match the comment's, or the caller to be an admin.
  • The guest read limit is enforced server-side as well as in the view.

Prerequisites

For the form to appear and for tokens to be sent, the docs shell must expose two values to the view: $isLoggedIn (true for any signed-in user) and a $csrfToken for signed-in users, not just editors. Both are set in the shell's bootstrap; if the form never shows for a logged-in member, that's the place to check.