magna-cms / blog
A Magna CMS plugin.
Requires
- php: ^8.3
- magna-cms/plugin-sdk: ^1.0
Requires (Dev)
- phpunit/phpunit: ^11.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-23 15:38:03 UTC
README
Magna Blog
A blog plugin for Magna CMS, the open-source Laravel headless CMS — public post pages through Magna Pages, and a delivery API for headless frontends.
Magna Blog is a plugin for Magna CMS. It manages blog content through the Magna admin panel using an Editor.js block editor, and exposes a read-only delivery API for any frontend — a headless site, a static build, or a native app.
With Magna Pages installed, every published post is also served as a page of your site, at its permalink and inside your theme's layout. Without Pages the blog stays headless: it owns authoring, organisation and delivery, and presentation is left to your frontend.
Screenshots
The full-screen post builder
An Editor.js canvas beside a tabbed sidebar for post settings, block settings and SEO.
Blocks and paragraph templates
Insert from the block toolbox, then restyle the selected block from the Block tab — thirty-six paragraph treatments, all sanitised server-side, no CSS required.
Posts
Every post with its category, status, visibility, author and schedule, filterable and searchable.
Per-post SEO
With the optional Magna SEO plugin installed, each post gains a search-appearance panel: SEO title, meta description, canonical URL, focus keyword, robots directives and social cards.
Settings
General, slugs, permalinks, editor, fonts, content, custom fields and comment options — all in the admin panel.
Overview
- Block editor — a rich Editor.js authoring experience with 30+ content blocks, sanitised server-side and delivered as structured JSON.
- Full content model — posts, nested categories, tags, series, co-authors, per-post language and translation groups.
- Editorial workflow — draft, pending review, scheduled, published and archived states, with soft- delete Trash, revisions and scheduled publishing.
- Public post pages — with Magna Pages, each published post is served at its permalink inside the site's theme.
- Delivery API — a versioned, cache-friendly JSON API for headless frontends.
- Engagement — threaded comments with spam protection, and anonymous reactions.
- Operations — CSV export, WordPress (WXR) import, GDPR personal-data export/erase, and webhooks.
- Optional SEO — integrates with the Magna SEO plugin for per-post metadata when it is installed.
Features
- Posts — title, slug, excerpt, Editor.js content, featured image, author and co-authors, category, tags, series and position, status, visibility (public / private / password-protected), and per-post locale with translation groups.
- Editor.js — a configurable, extensible block set; content is stored as JSON and sanitised on the server through an allowlist model.
- Taxonomies — nested categories and flat tags, each managed as its own admin resource, with a self-healing default ("Uncategorised") category.
- Series — group related posts into an ordered set with automatic previous/next navigation.
- Revisions — every create/update snapshots the post into the core revision store, pruned to a configurable maximum; restore any revision from the editor.
- Scheduling — scheduled posts publish automatically when their date arrives.
- Delivery API — read-only JSON for published, public posts, with filtering, search, pagination, related posts, adjacent posts, taxonomies, comments and reactions.
- Comments — held for moderation or auto-approved, protected by a honeypot and an optional Akismet driver; one level of threading.
- Reactions — anonymous, per-visitor reactions with a non-reversible fingerprint (no personal data stored).
- Import / export — bulk CSV export (formula-injection safe) and WordPress WXR import.
- Privacy — GDPR personal-data export and erase for authors and commenters.
- Webhooks — emits
blog.post.published,blog.post.updatedandblog.post.deletedevents. - Internationalisation — a
blog::translation namespace for the admin surface.
Requirements
- PHP 8.3+
- Magna CMS 1.6.2 or later, below 2.0 (provides Laravel, Filament, the media library, users, permissions, settings, revisions, privacy and webhook subsystems)
- Magna Pages — optional, for public post pages
- Magna SEO — optional, for per-post SEO metadata
- Laravel's task scheduler running (
php artisan schedule:runevery minute from cron) — scheduled posts are published byblog:publish-scheduled, and buffered view counts are saved byblog:flush-views
Installation
Magna Blog is installed from the Plugin Marketplace inside Magna CMS — no Composer commands, no manual file copying, no build step.
- Sign in to your Magna CMS admin panel.
- Go to Plugins → Add New Plugin.
- Find Magna Blog in the marketplace and click Install.
- Activate the plugin when the install finishes.
- Open Blog → Posts and write your first post.
Updates arrive the same way: when a new version is published, the Plugins page shows it under Update Available.
Configuration
Backend and content settings are edited from Admin → Blog → Settings (general, slugs, permalinks,
editor, fonts, content, custom fields and comments). Infrastructure options — the comment spam driver
and reaction types — live in config/blog.php and are environment-driven:
BLOG_COMMENT_SPAM_DRIVER=honeypot # or "akismet" AKISMET_KEY= BLOG_COMMENT_MIN_SECONDS=2 BLOG_DEFAULT_LOCALE=en
Usage
Create and manage posts from Admin → Blog. The full-screen builder pairs an Editor.js canvas with a tabbed sidebar for post settings, block settings and (when the Magna SEO plugin is installed) SEO.
Artisan commands
| Command | What it does |
|---|---|
php artisan blog:import-wxr <file> |
Import posts, categories and tags from a WordPress WXR (.xml) export |
php artisan blog:export [--out=<file>] |
Export all blog content to a JSON bundle (default storage/app/blog-export.json) |
php artisan blog:import <file> |
Import a JSON bundle created by blog:export (idempotent, matched by slug) |
php artisan blog:reindex |
Rebuild the search index column for all posts |
php artisan blog:publish-scheduled |
Publish scheduled posts whose date has passed (runs every minute via the scheduler) |
php artisan blog:flush-views |
Save buffered view counts to the database (runs every five minutes via the scheduler) |
Public post pages
With Magna Pages installed, each published, public post is served at the Permalink pattern set
in Blog → Settings (default /blog/{slug}), inside the site's layout, with the post's title and
description as the page's meta. For the /blog listing, place the Blog posts block on a Pages page.
- Patterns — tokens
{slug},{year},{month},{day}and{category}, each a whole path segment (/blog/{year}/{slug}, not/blog/{year}-{slug}).{slug}is required; a pattern the site cannot serve falls back to/blog/{slug}and logs a warning. Every blog link, and the sitemap when Magna SEO is installed, follows the same pattern; only a post's canonical path answers. - Visibility — drafts, scheduled, private and password-protected posts, and unknown slugs, get the site's 404. A Pages page published at the same path wins over the post.
- Theming — themes style the page with CSS:
<article>sits directly inside.magna-frontend-page--content, with.post-title,.post-meta(.who,.dot),.featured,.post-bodyand.post-nav. The comments section is a sibling after</article>:section.post-comments#commentswith.post-comments__title,.post-comments__list,li.post-comment#comment-{id}(.post-comment__meta,.post-comment__author,.post-comment__date,.post-comment__text,details.post-comment__reply,.post-comment__replies,.post-comment--reply),.post-comments__notice(--pending,--approved),.post-comments__closed,.post-comments__loginandform.post-comments__formwith.post-comments__submit. - Pagination — set the Blog posts block's Pagination to Newer / older pages: "How many" becomes
the number per page (empty means the Posts per page setting), page 1 lives at the bare path and the
rest at
?page=N(up to 100), and a page past the end is a 404. The block renders core'smagna-paginationnavigation. - Languages — a post whose language is one of the site's languages other than the default is served
under that language's prefix (
/fr/blog/bonjour); every other post at the bare path. Prev/next links stay within the language, and translations sharing a translation group are offered as alternates. - Caching — guest post pages and listings are stored by the Pages cache and purged when a post, comment, category, tag or Blog setting changes; a post scheduled for later appears on time with no save, and a comment form closes on time.
- Redirects — changing a published post's slug, date, category or language, renaming a category, or editing the permalink pattern redirects the old URL to the new one (through Magna Pages' redirects).
- Comments — each post shows its approved comments (oldest first) and, while comments are open, a form. New comments are held for moderation or auto-approved per Blog → Settings. The form works on cached pages, carries a signed render stamp and a honeypot, is rate limited, and can show a captcha when Magna Defence enables the Blog comment form surface.
Known limits
- Commenter identity. There is no frontend member system: "Require login to comment" means signed in to the Magna panel, and a signed-in commenter's name and email come from their panel account. Guests give a name and email.
- One
?pageper page. Two paginated Blog posts blocks on the same page follow the same?page=parameter and move together. - Threads are one level deep and a post page shows at most 100 top-level comments.
- Pattern edits run inline. Redirects for a permalink pattern change are worked out during the save, a few queries per published post; on a very large blog the save takes a moment.
Editor
Content is authored with Editor.js and stored as a structured block document. The tool set is open for extension without modifying this plugin — register a tool before an editor mounts:
window.magnaBlog.registerTool('myBlock', { class: MyBlockTool, inlineToolbar: true });
Server-side security allowlists (block types, templates, code languages, social networks) live once, in PHP, and are shared by the sanitiser and renderer. See docs/adding-a-block.md for the full block-authoring checklist.
API
The delivery API is auto-prefixed at /api/v1/blog/ and protected by the magna.api middleware
(delivery-token auth with per-token rate limiting). Only published, public posts are ever returned.
| Method | Path | Returns |
|---|---|---|
| GET | /api/v1/blog/posts |
Paginated, filterable, searchable list |
| GET | /api/v1/blog/posts/{slug} |
A single published public post |
| GET | /api/v1/blog/categories |
The category tree with post counts |
| GET | /api/v1/blog/tags |
Tags with post counts |
| GET | /api/v1/blog/posts/{slug}/comments |
Approved comments (threaded one level) |
| GET | /api/v1/blog/posts/{slug}/comments/stamp |
A signed _ts for a comment form (no-store) |
| POST | /api/v1/blog/posts/{slug}/comments |
Submit a comment for moderation |
| POST | /api/v1/blog/posts/{slug}/reactions |
Toggle a reaction |
Responses are frontend-agnostic and never expose internal fields (password hashes, soft-delete state).
Fields are only ever added: since 1.2.0 a post also carries summary (its excerpt, else one generated
from the body) and url (its public, language-prefixed path). Since 1.2.1 adjacent prev/next stay in
the post's own language, and each entry in translations carries the url it is served at.
Comment bot check. Fetch GET …/comments/stamp when your comment form is shown and send the returned
_ts with the submission. A stamp younger than blog.comments.min_submit_seconds (default 2) is treated
as a bot and quietly not stored. The unsigned form_started_at field is deprecated: it is still honoured
(it can only make a submission look automated, never admit one) and will be removed in 2.0.
Permissions
blog.posts.view blog.posts.create blog.posts.edit
blog.posts.edit-others blog.posts.delete blog.posts.publish blog.posts.restore
blog.categories.manage blog.tags.manage blog.series.manage
blog.comments.manage blog.settings.manage
Per-record post actions follow a permission-plus-ownership rule: an author acts on their own posts, while
blog.posts.edit-others lifts the ownership restriction for editors and administrators.
Content blocks
The editor ships a broad block set, including paragraph (with visual templates), headings, lists, checklists, quotes and pullquotes, tables, code, callouts, CTAs, buttons, FAQ, details, galleries, cover, image, video and audio, embeds, maps, social icons, footnotes, RSS, table of contents, related posts, group and columns layouts, and dynamic post blocks (excerpt, featured image). Every block is validated server-side before it can be persisted.
Contributing
Working on the plugin itself (rather than using it) needs Node.js, because the editor bundle is a build artifact:
npm ci
npm run build # rebuild dist/blog-editor.js after changing resources/js/
php artisan filament:assets
Testing
The plugin ships an extensive Pest suite (feature and unit) that runs inside a Magna CMS application.
# From the Magna CMS application php artisan test --testsuite=Plugins # Style, static analysis and tests in one go composer plugin-check -- magna/blog # Or separately vendor/bin/phpstan analyse -c plugins-dev/magna/blog/phpstan.neon.dist vendor/bin/pint --test plugins-dev/magna/blog
Security
Content is protected in depth: an allowlist sanitiser cleans every Editor.js block before it is persisted, and the renderer escapes on output. Outbound editor requests (link preview, RSS) are SSRF-hardened with special-use IP-range blocking and connection pinning. CSV export is protected against formula injection, imports parse XML with network access disabled, and per-record authorisation is enforced server-side on every mutation.
To report a security issue, please contact the Magna CMS maintainers privately rather than opening a public issue.
Changelog
See CHANGELOG.md.
License
Magna Blog is open-source software licensed under the MIT license.





