jcfrane/laravel-md-blog
Composer 安装命令:
composer require jcfrane/laravel-md-blog
包简介
Write blog posts as markdown files with YAML front matter. No database, no CMS — just files and a clean query API.
README 文档
README
Write blog posts as markdown files with YAML front matter. No database, no CMS — just files and a clean query API.
Built for Laravel 12+ / PHP 8.2+.
Installation
composer require jcfrane/laravel-md-blog
Publish the config (optional):
php artisan vendor:publish --tag=md-blog-config
Usage
Writing Posts
Create .md files in resources/markdown/blog/ (configurable) with YAML front matter:
--- title: My First Post slug: my-first-post date: 2026-02-28 tags: [laravel, php] category: tutorials excerpt: A quick intro to my blog. published: true --- # My First Post Your markdown content here.
Front matter fields:
| Field | Required | Default |
|---|---|---|
title |
No | Filename |
slug |
No | Filename |
date |
No | File modification time |
tags |
No | [] |
category |
No | "" |
excerpt |
No | "" |
published |
No | true |
Any additional front matter fields are available via $post->meta.
Querying Posts
use JCFrane\MdBlog\Facades\MdBlog; // All published posts $posts = MdBlog::all(); // Single post by slug $post = MdBlog::find('my-first-post'); // Filter by tag $posts = MdBlog::whereTag('laravel'); // Filter by category $posts = MdBlog::whereCategory('tutorials'); // Published posts, newest first $posts = MdBlog::latest(); // Clear the cache MdBlog::clearCache();
All query methods return Laravel Collections (except find() which returns a Post or null). Drafts (published: false) are automatically excluded.
The Post Object
Each post is an immutable Post DTO with these properties:
$post->title; // string $post->slug; // string $post->date; // Carbon instance $post->body; // raw markdown $post->html; // rendered HTML $post->tags; // array $post->category; // string $post->excerpt; // string $post->published; // bool $post->meta; // array (extra front matter fields) $post->filePath; // string $post->lastModified; // int (unix timestamp)
Post implements Arrayable, Jsonable, and JsonSerializable for clean Inertia/API integration:
// In an Inertia controller return Inertia::render('Blog/Show', [ 'post' => MdBlog::find($slug), ]);
Custom Post Model
You can extend the base Post class to customize how posts are built — for example, to apply syntax highlighting to code blocks.
Set the post_class config option to your custom class:
// config/md-blog.php 'post_class' => App\Blog\CustomPost::class,
Then create a class that extends Post and overrides the make() factory method:
namespace App\Blog; use JCFrane\MdBlog\Post; class CustomPost extends Post { public static function make(array $attributes): static { // Transform the rendered HTML before readonly properties are set $attributes['html'] = \Spatie\ShikiPhp\Shiki::highlight($attributes['html']); return parent::make($attributes); } }
The make() method receives all post attributes as an array. You can modify any of them (html, title, body, etc.) before calling parent::make() to construct the post.
Note: After changing
post_class, runMdBlog::clearCache()to rebuild cached posts with the new class.
Images
The package automatically handles images stored within your blog directory.
Relative Images
If you have an image at resources/markdown/blog/images/photo.png, you can reference it in your markdown as:

The package will automatically rewrite this URL to a dedicated route that serves the image securely.
Image Configuration
By default, images are served with a long cache duration (24 hours). You can customize this in the config:
'images' => [ 'enabled' => true, 'cache_ttl' => 86400, // in seconds ],
When images.enabled is true (default), the package registers a route at /{route_prefix}/images/{path} to serve your assets.
Send blog posts as emails to your subscribers. The feature is opt-in and disabled by default.
Setup
- Set the environment variables:
MD_BLOG_MAIL_ENABLED=true MD_BLOG_MAIL_RECIPIENT_MODEL=App\Models\User
- Implement the
EmailRecipientinterface on your model:
use JCFrane\MdBlog\Contracts\EmailRecipient; use JCFrane\MdBlog\Traits\ReceivesPostMail; use JCFrane\MdBlog\Post; class User extends Authenticatable implements EmailRecipient { use ReceivesPostMail; public function shouldReceivePostEmail(Post $post): bool { return $this->subscribed_to_newsletter; } }
The ReceivesPostMail trait provides defaults for emailRecipients() (returns static::cursor()), getEmailAddress() ($this->email), and getEmailName() ($this->name). Override any of these if your model differs.
Sending
Three ways to send:
# Artisan command — single post or entire directory
php artisan md-blog:send-mail resources/markdown/blog/my-post.md
php artisan md-blog:send-mail resources/markdown/blog/
// Facade MdBlog::sendPost('resources/markdown/blog/my-post.md');
# HTTP route (POST /{route_prefix}/send-mail, protected by auth middleware) curl -X POST /md-blog/send-mail -d '{"path": "resources/markdown/blog/my-post.md"}'
Queue Support
Enable queued sending to avoid blocking:
MD_BLOG_MAIL_QUEUE=true MD_BLOG_MAIL_QUEUE_CONNECTION=redis MD_BLOG_MAIL_QUEUE_NAME=emails
Customizing the Email Template
Publish the views to customize the email layout:
php artisan vendor:publish --tag=md-blog-views
This copies the template to resources/views/vendor/md-blog/mail/post.blade.php.
Configuration
// config/md-blog.php return [ 'path' => env('MD_BLOG_PATH', 'resources/markdown/blog'), 'post_class' => null, // e.g. App\Blog\CustomPost::class 'cache' => [ 'enabled' => env('MD_BLOG_CACHE_ENABLED', true), 'store' => env('MD_BLOG_CACHE_STORE', null), // null = app default 'ttl' => env('MD_BLOG_CACHE_TTL', 3600), ], 'images' => [ 'enabled' => env('MD_BLOG_IMAGES_ENABLED', true), 'cache_ttl' => env('MD_BLOG_IMAGES_CACHE_TTL', 86400), ], 'commonmark' => [ 'html_input' => 'strip', 'allow_unsafe_links' => false, ], 'mail' => [ 'enabled' => env('MD_BLOG_MAIL_ENABLED', false), 'recipient_model' => env('MD_BLOG_MAIL_RECIPIENT_MODEL', null), 'queue' => env('MD_BLOG_MAIL_QUEUE', false), 'queue_connection' => env('MD_BLOG_MAIL_QUEUE_CONNECTION', null), 'queue_name' => env('MD_BLOG_MAIL_QUEUE_NAME', null), 'chunk_size' => env('MD_BLOG_MAIL_CHUNK_SIZE', 50), 'middleware' => ['auth'], 'subject_prefix' => env('MD_BLOG_MAIL_SUBJECT_PREFIX', ''), ], ];
Caching
Posts are cached per-file with automatic staleness detection — when a file is modified, the cached entry is automatically refreshed on the next read. Use MdBlog::clearCache() to force a full cache bust.
Development
Running Tests
composer install ./vendor/bin/phpunit
Workbench (Demo App)
The package includes a workbench powered by Orchestra Testbench:
php vendor/bin/testbench serve
Visit http://localhost:8000/blog to see the demo blog.
License
MIT
jcfrane/laravel-md-blog 适用场景与选型建议
jcfrane/laravel-md-blog 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 292 次下载、GitHub Stars 达 4, 最近一次更新时间为 2026 年 02 月 28 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「blog」 「cms」 「markdown」 「laravel」 「inertia」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 jcfrane/laravel-md-blog 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 jcfrane/laravel-md-blog 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 jcfrane/laravel-md-blog 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Texy converts plain text in easy to read Texy syntax into structurally valid (X)HTML. It supports adding of images, links, nested lists, tables and has full support for CSS. Texy supports hyphenation of long words (which reflects language rules), clickable emails and URL (emails are obfuscated again
GraphQL authentication for your headless Craft CMS applications.
Laravel package for opinionated blog implementation
Set Links with a specific language parameter
Supercharged text field validation.
Adds more BBCode
统计信息
- 总下载量: 292
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 4
- 点击次数: 30
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-02-28