Download the PHP package dirkblack/blog without Composer
On this page you can find all versions of the php package dirkblack/blog. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download dirkblack/blog
More information about dirkblack/blog
Files in dirkblack/blog
Package blog
Short Description A full-featured blog package for Laravel 12
License MIT
Informations about the package blog
dirkblack/blog
A Laravel 12 composer package that installs a full-featured blog with a Vue 3 SPA frontend and Laravel API backend.
Features
- Single
Postmodel withtypecolumn for both blog posts and static pages - Vue 3 SPA frontend with Pinia stores, Tailwind CSS, and live markdown preview
- Role-based access control (guest, member, author, admin) via pivot table
- Custom markdown engine with footnotes/citations, WikiLinks, image classes, and configurable HTML class map
- Threaded comments with moderation (enable/disable commenting; auto-approve or require approval)
- Media management via spatie/laravel-medialibrary with drag-and-drop upload
- Tag system via spatie/laravel-tags
- Post scheduling with automatic publishing via Laravel scheduler
- Draft auto-save with 30-second debounce
- Inter-post link tracking with Wikipedia-style red links for missing posts
Requirements
- PHP 8.2+
- Laravel 12.x
- Node.js (for building frontend assets, if customizing)
Installation
1. Require the package
The service provider is auto-discovered — no manual registration needed.
2. Publish assets and config
3. Run migrations
This creates the blog_posts, blog_comments, and blog_user_roles tables (plus spatie tag/media tables if not already present).
4. Assign your first admin
Or use the HasBlogRole trait on your User model:
5. Visit your blog
Navigate to /blog (or your configured prefix) in your browser.
Configuration Reference
Publish the config with php artisan vendor:publish --tag=blog-config. All options are in config/blog.php:
Route Prefix
All blog routes are prefixed with this value. Change to 'news' and your blog lives at /news.
Middleware
Add your own middleware (e.g., 'auth' to restrict the entire blog to logged-in users).
User Model
The fully qualified class name of your application's User model. Used for author relationships and role assignments.
Comments
Media
Set BLOG_MEDIA_DISK in your .env to use a different storage disk (e.g., s3).
Notifications
Set to true and ensure your User model uses the Notifiable trait.
Load Default CSS
When true, the package serves its built-in stylesheet. Set to false if you've published and customized the styles.
Footnotes
Customize the CSS classes and ID prefixes used by the footnote extension. The defaults work well with the built-in stylesheet. See Footnotes for syntax details.
Markdown Class Map
Apply CSS/Tailwind classes to all rendered markdown HTML elements globally. Empty strings are ignored. See the HTML Class Map section below.
Roles & Authorization
The package uses a 4-tier role system stored in the blog_user_roles pivot table:
| Role | Level | Capabilities |
|---|---|---|
| guest | 0 | Read public posts and pages |
| member | 1 | All guest capabilities + post comments |
| author | 2 | All member capabilities + create/edit/delete own posts, manage own media |
| admin | 3 | All author capabilities + manage all posts, moderate comments, assign roles |
Using the HasBlogRole Trait
Add the trait to your User model for convenience methods:
Using BlogRoleResolver Directly
API Reference
All API routes are prefixed with /{prefix}/api (default: /blog/api). Responses use JSON:API-style resource formatting.
Public Endpoints (no authentication required)
| Method | Endpoint | Description |
|---|---|---|
| GET | /posts |
List published posts (paginated). Query: ?page=, ?tag= |
| GET | /posts/{slug} |
View a single published post |
| GET | /pages |
List published pages |
| GET | /pages/{slug} |
View a single published page |
| GET | /tags |
List all tags with post counts |
| GET | /posts/{slug}/comments |
List approved comments (threaded) |
| GET | /auth/user |
Get current authenticated user and blog role |
Member Endpoints (authentication required)
| Method | Endpoint | Description |
|---|---|---|
| POST | /posts/{slug}/comments |
Create a comment. Body: { "body": "...", "parent_id": null } |
| PUT | /comments/{id} |
Update own comment. Body: { "body": "..." } |
| DELETE | /comments/{id} |
Delete own comment |
Author Endpoints (author role required)
| Method | Endpoint | Description |
|---|---|---|
| GET | /author/posts |
List own posts (all statuses). Query: ?status=, ?search=, ?page= |
| POST | /author/posts |
Create post. Body: { "title", "body_markdown", "type", "tags": [] } |
| GET | /author/posts/{id} |
View own post with media |
| PUT | /author/posts/{id} |
Update own post |
| DELETE | /author/posts/{id} |
Soft-delete own post |
| POST | /author/posts/{id}/publish |
Publish immediately or schedule. Body: { "scheduled_at": null } |
| POST | /author/posts/{id}/unpublish |
Revert to draft |
| GET | /author/posts/search |
Search posts by title. Query: ?q= |
| POST | /author/preview-markdown |
Preview rendered markdown. Body: { "body_markdown": "..." } |
| GET | /author/media |
List own media (paginated) |
| POST | /author/posts/{id}/media |
Upload media file (multipart form) |
| DELETE | /author/media/{id} |
Delete media |
Admin Endpoints (admin role required)
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/posts |
List all posts (all authors, all statuses) |
| PUT | /admin/posts/{id} |
Update any post |
| DELETE | /admin/posts/{id} |
Delete any post |
| GET | /admin/comments |
List all comments. Query: ?status= |
| PUT | /admin/comments/{id}/approve |
Approve a comment |
| PUT | /admin/comments/{id}/spam |
Mark comment as spam |
| DELETE | /admin/comments/{id} |
Delete a comment |
| GET | /admin/users |
List users with blog roles. Query: ?role=, ?search=, ?page= |
| PUT | /admin/users/{id}/role |
Assign blog role. Body: { "role": "author" } |
AI Integration (MCP)
The blog ships an MCP server so a site owner can build and maintain the site by working with Claude (or any MCP-capable AI client). It's opt-in and off by default. The AI acts as a real blog user — bounded by the blog role system, a writes switch, a destructive-actions switch, and per-token abilities — and every mutation is audited.
1. Requirements
Add Laravel Sanctum's trait to your host User model (the one change the AI feature needs):
Publish Sanctum's migration if you haven't (php artisan vendor:publish --tag=sanctum-migrations)
and migrate.
2. Enable it
Set the flags in config/blog.php (or via env). They are layered, strictest last:
Setting (blog.ai.*) |
Env | Default | Effect |
|---|---|---|---|
enabled |
BLOG_AI_ENABLED |
false |
Master switch — the /blog/api/ai + /blog/mcp routes 404 when off |
write_enabled |
BLOG_AI_WRITE_ENABLED |
false |
Allow create/update/publish/tag/moderate/upload tools |
allow_destructive |
BLOG_AI_ALLOW_DESTRUCTIVE |
false |
Allow delete-post, delete-comment, assign-role |
rate_limit |
BLOG_AI_RATE_LIMIT |
60,1 |
Throttle for AI routes (max,minutes) |
audit |
BLOG_AI_AUDIT |
true |
Log AI mutations to blog_ai_audit |
3. Mint a token
The token resolves to that user, so its blog role still governs what it can do. Abilities are a second, narrower gate:
| Ability | Unlocks |
|---|---|
posts:read |
read/search posts, tags, preview, validate, maintenance tools |
posts:write |
create / update / publish / tag posts, upload media |
posts:delete |
delete posts (destructive) |
comments:read |
list comments |
comments:moderate |
approve/spam/delete comments (delete is destructive) |
users:manage |
assign roles (destructive) |
4. Connect a client
- Remote (HTTP): point the client at
https://your-site/blog/mcpwith headerAuthorization: Bearer <token>. - Local (stdio, for development):
php artisan mcp:start blog.
What the AI can do
- Read:
list-posts,get-post,search-posts,list-tags,list-comments,preview-markdown,validate-content. - Maintain:
list-scheduled-queue,find-broken-wikilinks,find-orphan-pages,audit-seo,suggest-internal-links,list-audit(admin). - Write (needs
write_enabled):create-post,update-post,publish-post,unpublish-post,manage-tags,moderate-comment,upload-media. - Destructive (also needs
allow_destructive):delete-post,delete-comment,assign-role.
The server also exposes resources that teach the AI your conventions
(blog://guide/markdown, blog://guide/shortcodes, blog://guide/roles, blog://site/config)
and prompts for common workflows (draft-post, build-home-page, weekly-maintenance).
Note:
upload-mediacan fetch from a URL server-side. Only enable writes for tokens you trust.
Custom Markdown Syntax
The blog includes a custom markdown engine built on league/commonmark with GitHub Flavored Markdown support, footnotes, and three custom extensions.
All standard markdown and GFM features work out of the box (headings, bold, italic, lists, tables, strikethrough, code blocks, etc.).
Footnotes — Citations & References
Add footnotes using standard markdown syntax. References render as superscript numbered links, and a footnote section with backlinks is auto-generated at the bottom of the content.
Named references can be reused: write [^label] multiple times and they all link to the same [^label]: definition. Labels are for authoring only — the rendered output is auto-numbered by order of first reference.
The footnote section, backref symbol (↩), and CSS classes are all configurable via config('blog.footnotes').
WikiLinks — Internal Post & Page Links
Link to other posts and pages using double-bracket syntax. The renderer automatically resolves the slug to a URL and pulls the post title as the link label.
Slugs are unique across all content, so you only need the slug — the renderer looks up the target and links to the correct URL whether it's a post (/blog/about-us) or a page (/blog/page/about-us), using the right title as the label.
The page: / post: prefix is an optional authoring hint, not required for linking. Because the type is derived from the resolved record, it is ignored when the target already exists. It only matters for a missing target: the prefix tells the "create" link what type to scaffold. Write [[page:future-page]] if you want a not-yet-existing link to create a page (otherwise it defaults to a post).
Existing targets render as: <a href="/blog/my-post-slug" class="wiki-link">My Post Title</a> (pages link to /blog/page/{slug}).
Missing targets render as red links with a wiki-link--missing class, linking to the creation page with the slug — and the type hint, if given — pre-filled. The URL prefix respects your blog.prefix config value.
Image Classes — CSS Classes on Images
Append CSS classes to images using curly-brace syntax after the standard markdown image tag:
Produces: <img src="/images/sunset.jpg" alt="Sunset photo" class="half-right" />
Predefined class shortcuts: .half-right, .half-left, .banner, .thumbnail. You can use any CSS class.
HTML Class Map — Global CSS Classes on Rendered Elements
Apply CSS classes to all rendered markdown elements via the markdown_classes config. Supports 12 element types: h1, h2, h3, p, ul, ol, blockquote, code, pre, a, img, table.
Empty strings are ignored — only elements with non-empty class values get modified.
Auto-Rendering on Save
Markdown is automatically converted to HTML whenever a post is saved:
Using the Converter Directly
The converter is registered as a singleton, so the CommonMark environment is only built once per request.
Theming & CSS Customization
Built-in Styles
The package ships with a Tailwind CSS v4 stylesheet scoped under .blog-app. It includes:
- Layout and component styles for the Vue SPA
.blog-prosecontent styles for rendered markdown (headings, paragraphs, lists, links, code blocks, tables, blockquotes, images)- Image layout classes:
.half-right,.half-left,.banner,.thumbnail - Wiki-link styling including
.wiki-link--missingfor red links
Customizing Styles
Option 1: Override via Markdown Class Map — Use config('blog.markdown_classes') to apply your own Tailwind/CSS classes to rendered markdown elements without touching CSS.
Option 2: Publish and Modify the Stylesheet
This copies the CSS to resources/css/vendor/blog.css. Include it in your own build pipeline and set config('blog.load_default_css') to false.
Option 3: Override with Custom CSS — The .blog-app scope and .blog-prose content class make it straightforward to override styles in your app's stylesheet.
Asset Publishing
| Tag | Destination | Description |
|---|---|---|
blog-assets |
public/vendor/blog/ |
Compiled JS and CSS (required) |
blog-config |
config/blog.php |
Configuration file |
blog-migrations |
database/migrations/ |
Migration files |
blog-views |
resources/views/vendor/blog/ |
Blade templates |
blog-styles |
resources/css/vendor/blog.css |
Source CSS for custom builds |
Post Scheduling
Posts can be scheduled for future publication:
The blog:publish-scheduled command runs every minute via Laravel's scheduler to auto-publish due posts. Ensure php artisan schedule:run is in your crontab.
When a scheduled post is published, the author can optionally receive an email notification (set config('blog.notifications.post_published') to true).
Testing
PHP Tests (Pest)
170 tests covering models, enums, policies, middleware, all API endpoints, markdown extensions, link tracking, and the scheduling system.
JavaScript Tests (Vitest)
95 tests covering all 6 Pinia stores, 7 Vue components, and composables.
License
MIT
All versions of blog with dependencies
laravel/framework Version ^12.0 || ^13.0
spatie/laravel-tags Version ^4.0
spatie/laravel-medialibrary Version ^11.0
league/commonmark Version ^2.0
symfony/html-sanitizer Version ^7.0
laravel/sanctum Version ^4.0
laravel/mcp Version ^0.7