Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Frontmatter Laravel Package

hyn/frontmatter

hyn/frontmatter is a Laravel package for extracting YAML front matter from Markdown and text files. It cleanly separates metadata from content, making it easy to load titles, dates, tags, and custom fields for blogs, documentation, and static content workflows.

Deep Wiki
Context7

Getting Started

  1. Install into a Laravel app:

    composer require hyn/frontmatter
    

    The package is framework-agnostic (no service provider required), so it composes cleanly with Laravel without registering anything.

  2. Look first at the Parser class — that's the primary entry point. The typical first call is:

    use Hyn\Frontmatter\Parser;
    
    $parsed = (new Parser())->parse($markdownString);
    // $parsed['data']    -> array of parsed frontmatter (YAML/TOML/JSON)
    // $parsed['content'] -> remaining markdown body
    
  3. First realistic use case: a blog or docs folder where each *.md file starts with:

    ---
    title: Hello World
    published_at: 2024-01-01
    tags: [laravel, php]
    ---
    
    Body goes here.
    

    Read the file with Storage::get(), hand the string to the parser, and push data into an Eloquent model or view while rendering content through Str::markdown() (or the league/commonmark parser).

  4. Decide early which frontmatter format you're standardising on. The package auto-detects YAML (---), TOML (+++), and JSON ({ … }) delimiters, but consistency across a content tree avoids surprises in tests and tooling.

Implementation Patterns

  • Single-file content object pattern. Wrap the parser so each markdown file becomes a typed value object rather than a loose array:

    class Page
    {
        public function __construct(
            public array $meta,
            public string $body,
        ) {}
    
        public static function fromFile(string $path): self
        {
            $raw = (new Parser())->parse(File::get($path));
            return new self($raw['data'], $raw['content']);
        }
    }
    

    Then Page::fromFile() can live as a static factory on models or in an app/Support namespace.

  • Collection sync into Eloquent. Use File::allFiles(resource_path('content')), loop, parse, and upsert by slug derived from the filename. Store the raw metadata as a json column so you don't need a migration every time you add a frontmatter key:

    Post::updateOrCreate(
        ['slug' => $file->getFilenameWithoutExtension()],
        ['meta' => $page->meta, 'body' => $page->body, 'checksum' => md5(File::get($file->getPathname()))]
    );
    

    The checksum short-circuits resaving unchanged files — a cheap, effective optimisation.

  • Artisan command as the sync driver. php artisan content:sync that walks a directory, parses, and reports a diff (created/updated/skipped). This turns the package into a static-site-to-DB pipeline that can be triggered from a Git deploy hook.

  • Render pipeline. Because the package only splits frontmatter from body, treat it as the boundary layer, not the renderer. Typical chain:

    1. Parser::parse($raw) → meta + markdown
    2. Str::markdown($body) (Laravel 9+ ships this via league/commonmark) or a dedicated MarkdownRenderer service
    3. Pass meta + rendered HTML to a Blade view or spatie/laravel-markdown output.
  • Draft/visibility gating. A common pattern is to use meta['draft'] ?? false at the query level (->where('meta->draft', false) for MySQL JSON columns) rather than filtering in PHP. This keeps pagination correct.

  • Mixed-format content trees. If you must mix YAML and TOML (e.g. legacy Jekyll and Hugo files), detect the delimiter yourself and store the format in the DB alongside the meta. The parser will handle each, but your validators (dates, arrays) should still enforce a single schema.

  • Testing. Build a tests/Fixtures/content/ directory with representative .md files and assert against the parsed array. Fixture-based tests catch delimiter regressions (a --- in the body accidentally parsed as frontmatter) better than inline strings.

Gotchas and Tips

  • Ambiguous delimiters. YAML uses ---; if a document legitimately starts with --- followed by non-YAML text (e.g. a horizontal rule as the first line of prose), behaviour depends on the parser's detection heuristic. Always keep frontmatter at the very top with no preceding whitespace/BOM, and prefer leading title: or similar key to anchor detection.

  • Type coercion. YAML/TOML parsers coerce dates, booleans, and numbers natively, but if you round-trip data through JSON in a database column, dates become strings. Decide on a canonical datetime cast at the model layer ($casts = ['meta->published_at' => 'datetime'] works for MySQL JSON, but keep the raw JSON too for forward-compat).

  • Nested keys and JSON columns. If you plan to query meta->tags, note MySQL JSON path lookups are case-sensitive on string keys and don't index inside arrays. For tag filtering at scale, denormalise into a post_tag pivot during sync rather than querying inside JSON.

  • File encoding. Windows CRLF and UTF-8 BOMs frequently break frontmatter detection. Normalise before parsing: $raw = ltrim($raw, "\xEF\xBB\xBF"); and normalise line endings with str_replace("\r\n", "\n", $raw).

  • Empty frontmatter vs no frontmatter. parse() may return an empty data array for both cases. If "has frontmatter" matters (e.g. a strict schema validator), check for the delimiter yourself; don't rely on empty($parsed['data']).

  • Debugging. When parse output looks wrong, dump($parsed) before rendering (not after) and inspect the raw delimiters. Most issues are formatting at the file boundary (missing closing ---, trailing space before it, a body line that starts with ---).

  • Extension points. The package is intentionally thin — there is no plugin system. Anything beyond split-and-parse (schema validation, slug generation, HTML rendering, image rewriting) belongs in your own service layer. That's a feature: it keeps the package easy to swap for league/commonmark's FrontMatterExtension later if you need full CommonMark compatibility.

  • License is MIT — safe for commercial use; just retain the notice if you fork.

  • Low maintenance signal. With a small star count and no framework bindings, pin the version in composer.json and keep a shallow fork ready. Because the surface area (parse a string → array) is tiny, a replacement is a half-day job if the package stalls — budget accordingly and keep your wrapper (Page::fromFile) decoupled from the parser class so the swap is mechanical.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib