labrodev/laravel-markdownable
Laravel package to load Markdown pages with YAML front matter by slug, parse metadata, convert to HTML, and return a structured PageData object. Includes contracts for swapping implementations and a Markdownable facade for simple access.
Installation:
composer require labrodev/laravel-markdownable
No additional setup required—the service provider auto-registers.
Define Content Path:
Set the default Markdown directory in .env:
MARKDOWNABLE_PAGES_PATH=resources/markdown
Or publish the config and customize:
php artisan vendor:publish --tag=markdownable-config
Create a Markdown File:
Place a file like about.md in resources/markdown with YAML front matter:
---
title: About Us
meta_description: Learn about our company
---
# Welcome to Our Site
This is **markdown** content.
First Use Case: Fetch a page in a controller or Blade view:
use Labrodev\Markdownable\Facades\Markdownable;
$page = Markdownable::getPageBySlug('about');
return view('pages.show', ['page' => $page]);
Access fields like $page->title, $page->bodyHtml, or $page->toArray() for SEO.
Reading Pages:
Markdownable::getPageBySlug('slug')Markdownable::listPages() → returns Collection of PageData.Markdownable::setPagesPath('custom/path');
Integration with Blade:
Pass PageData to views and use toArray() for SEO meta tags:
<title>{{ $page->title }}</title>
<meta name="description" content="{{ $page->meta_description }}">
@foreach($page->toArray() as $key => $value)
@if($key !== 'bodyHtml')
<meta property="og:{{ $key }}" content="{{ $value }}">
@endif
@endforeach
Dynamic Routing: Use Laravel’s route model binding with a custom resolver:
Route::get('/{slug}', function (PageData $page) {
return view('pages.show', compact('page'));
})->name('pages.show');
Register the resolver in AppServiceProvider:
Route::bind('page', function ($slug) {
return Markdownable::getPageBySlug($slug);
});
Extending PageData:
Add custom fields to front matter and access them via PageData:
---
title: Blog Post
author: John Doe
published_at: 2023-01-01
---
Access in PHP:
$page->author; // "John Doe"
$page->published_at; // "2023-01-01"
Caching:
Cache PageData for performance (e.g., in AppServiceProvider):
Markdownable::setCacheEnabled(true);
Markdownable::setCacheTTL(60 * 60); // 1 hour
Case-Sensitive Slugs:
Slugs in filenames are case-sensitive. Ensure consistency (e.g., about.md vs. About.md).
Front Matter Validation: Invalid YAML in front matter will throw exceptions. Validate files locally before deployment:
php artisan markdownable:validate
(Note: This command may not exist; manually check files or use a linter like yamllint.)
HTML Escaping:
bodyHtml is raw HTML. Escape dynamically generated content to prevent XSS:
{!! e($page->bodyHtml) !!}
File Permissions: Ensure the Markdown directory is readable by Laravel:
chmod -R 755 resources/markdown
League CommonMark Dependencies:
The package relies on league/commonmark. Conflicts may arise if other packages use different Markdown parsers. Prefer this package’s converter for consistency.
Inspect PageData:
Dump the object to verify parsed data:
dd(Markdownable::getPageBySlug('about')->toArray());
Check File Paths: Debug path resolution:
$reader = app(MarkdownPageFileReaderContract::class);
dd($reader->getPagePath('about')); // Should point to the correct .md file.
Override Implementations:
Swap contracts for testing or custom logic (e.g., mock FrontMatterParserContract):
$this->app->bind(
FrontMatterParserContract::class,
CustomFrontMatterParser::class
);
Handle Missing Pages:
Always check for null when fetching pages:
$page = Markdownable::getPageBySlug('nonexistent');
if (!$page) {
abort(404);
}
Custom Front Matter Parsing:
Extend FrontMatterParser to support additional YAML fields or validation:
class CustomFrontMatterParser implements FrontMatterParserContract {
public function parse(string $content): array {
$data = parent::parse($content);
// Add custom logic (e.g., convert dates, validate fields).
return $data;
}
}
Post-Processing HTML:
Hook into the MarkdownToHtmlConverter to modify output (e.g., add classes, wrap content):
$converter = app(MarkdownConverterContract::class);
$converter->setPostProcessor(function (string $html) {
return '<div class="markdown-content">' . $html . '</div>';
});
Event Listeners: Dispatch events for page reads or updates (e.g., log access or update analytics):
// In a service provider:
Markdownable::listen('page.read', function (PageData $page) {
// Track page views.
});
(Note: Events may not be built-in; use Laravel’s event system directly on PageData.)
Multi-Language Support:
Use the paths config array to organize content by locale:
'paths' => [
'pages' => [
'en' => 'resources/markdown/en',
'es' => 'resources/markdown/es',
],
],
Dynamically resolve paths in your code:
$reader->setPagesPath('resources/markdown/' . app()->getLocale());
How can I help you explore Laravel packages today?