Features / Extend

Plugins: comments, forms, redirects, wiki

Four optional building blocks, each compiled in individually with its own admin page: comments with moderation, form submissions, URL redirects, a hierarchical wiki. No marketplace, no runtime loading.

One site needs a contact form, the next wants comments under posts, the third has old URLs to redirect. None of that is a content model — but building the same scaffolding every time, storage, spam protection, admin view, is wasted effort. RustyCMS ships these as plugins: optional, compiled in per feature, each with its own admin page.

Compiled in, not loaded later

CARGO_FEATURES=comments,forms — and the binary contains exactly those two, admin UI included. What was not built is not there: no code in the bundle, no sidebar entry, no route. GET /api/plugins tells the frontend what is active. No dlopen, no WASM, no marketplace — RustyCMS stays one binary.

The same principle carries the backends underneath: asset storage and version history sit behind narrow interfaces. The filesystem runs with no configuration; version history already has a second backend for database mode. An environment variable picks which one runs — not a rewrite.

Comments with layered spam protection

Visitors comment without an account — an anonymous session cookie lets them edit or delete their own comment later. In front of that sit several filters: rate limit per IP, cooldown per session, duplicate window, honeypot, minimum page age (bots post in zero milliseconds), IP blocklist. With RUSTYCMS_COMMENTS_REQUIRE_APPROVAL=true every new comment enters the queue; the admin page approves, rejects, deletes.

POST /api/comments/my-article
{ "author": "Max", "body": "Great piece!", "honeypot": "", "client_age_ms": 5000 }

GET /api/comments/counts?ids=article-1,article-2   # counts for the index page

Forms whose submissions live in the CMS

POST /api/forms/contact/submit accepts any JSON object and stores it. No schema needed — the form exists with its first submission. Protection: rate limit per IP, body limit, honeypot field, origin allowlist and an allowlist of permitted form IDs (RUSTYCMS_FORMS_ALLOWED_IDS=contact,feedback) so nobody invents forms of their own. The admin page lists submissions per form with a new/read status.

Redirects the frontend picks up

Send old paths to new ones, 301 or 302, switchable per entry. Editors maintain the list in the admin UI; GET /api/redirects is public and returns only active entries — the frontend loads them at startup and checks every request against them.

A wiki that is a collection

Hierarchical Markdown pages with a tree, drag-to-reorder, wiki links ([[slug]]) and embeds ({{embed:slug}}). The plugin adds no routes of its own — it is a view onto the wiki_page collection. Versioning, search, validation, export: whatever applies to content applies here.

Deliberately left out

No plugin marketplace, no loading of third-party code at runtime. A plugin is Rust code that gets built with the rest. That is the price for a deployment containing exactly what you built — and nothing else.

Want to see this on your own project?

Tell me briefly what you're planning. I'll show the admin UI on a real installation and say honestly whether it fits.

Request early access