{"slug": "query-markdown-content-with-eloquent-in-laravel-filtering-sorting-and-pagination", "title": "Query Markdown Content with Eloquent in Laravel: Filtering, Sorting, and Pagination", "summary": "The developer behind the open-source Fusion package released a Laravel library that exposes Markdown files with YAML Frontmatter as Eloquent models, letting developers filter, sort, and paginate file-based content with familiar methods like where(), orderBy(), and paginate(). Fusion parses Frontmatter into model attributes, derives slugs from filenames unless overridden, and adds caching plus a fusion:check --validate command for enforcing Frontmatter rules. The package targets documentation and knowledge collections that need querying by metadata such as section or order.", "body_md": "Markdown is common in documentation, project knowledge bases, website content, and instructions for AI coding tools. If your code needs to load one Markdown file and already knows its path, that is straightforward. The challenge appears when content becomes a collection, for example, documentation split across many chapter and section files.\n\nMarkdown describes the body of each file, but a collection also needs structured information to identify and organize its content. A common way to add it is YAML Frontmatter: a small block at the beginning of a Markdown file, enclosed by `---`, with fields such as `title`, `section`, `order`, or `published`. For example, `section: installation` lets the application identify which part of the documentation a file belongs to, while `order: 2` can define its position within that section.\n\nOnce files have this metadata, the application needs a way to query it, to retrieve the whole collection or a relevant subset, such as all installation sections or the chapters in one guide, sorted in the intended order. Without a query layer, the application has to scan files, parse Frontmatter, and implement filtering and sorting itself.\n\nFusion gives Laravel developers a familiar way to work with that content. It exposes Markdown files as Eloquent models, so you can use the same methods you already know from database-backed models (`where()`, `orderBy()`, and `paginate()`) without learning a separate query API. Your Markdown remains the source of truth; Fusion handles parsing Frontmatter and rendering the content so you can retrieve it through Eloquent.\n\nThis guide builds a practical article collection and shows how to configure its content folder, query it, and use Fusion's caching and content checks.\n\nInstall Fusion with Composer:\n\n```\ncomposer require hi-folks/fusion\n```\n\nA simple content collection can live under `resources/content/article`. Each file contains YAML Frontmatter for structured values and Markdown for the body:\n\n```\n---\ntitle: Working with Markdown in Laravel\ndate: \"2026-10-19\"\npublished: true\ncategory: laravel\nexcerpt: A practical guide to querying file-based content.\n---\n\n# Working with Markdown in Laravel\n\nThis article is stored as a Markdown file and can be retrieved through Eloquent.\n```\n\nFusion uses each Markdown filename without its extension as the slug when the Frontmatter has no `slug` key. You can set a `slug` value in Frontmatter when the URL should differ from the file name. Avoid setting `slug` to an empty or null value: because the key is present, Fusion treats it as an explicit override instead of falling back to the filename.\n\nThis format is useful when content should be reviewed in Git, edited with any Markdown editor, or kept alongside application code. It also works for curated documentation and knowledge collections, including material that developers or AI coding tools need to retrieve by category or metadata.\n\nCreate a model for the `article` content directory and use Fusion's trait. The `frontmatterFields()` method lists the Frontmatter keys that Fusion should load as model attributes:\n\n``` php\n<?php\n\nnamespace App\\Models;\n\nuse HiFolks\\Fusion\\Traits\\FusionBaseModelTrait;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Article extends Model\n{\n    // Loads Markdown-backed content rows into this Eloquent model.\n    use FusionBaseModelTrait;\n\n    // List the Frontmatter keys Fusion should expose as model attributes.\n    public function frontmatterFields(): array\n    {\n        return [\n            'title',\n            'date',\n            'published',\n            'category',\n            'excerpt',\n        ];\n    }\n\n    // Cast values when accessed as attributes; casts do not validate Frontmatter.\n    protected function casts(): array\n    {\n        return [\n            'date' => 'date:Y-m-d',\n            'published' => 'boolean',\n        ];\n    }\n\n    // Used by `fusion:check --validate` to check each file's Frontmatter values.\n    public function frontmatterRules(): array\n    {\n        return [\n            'title' => ['required', 'string', 'max:255'],\n            'date' => ['required', 'date'],\n            'published' => ['required', 'boolean'],\n            'category' => ['required', 'string'],\n            'excerpt' => ['nullable', 'string'],\n        ];\n    }\n}\n```\n\nFusion reads the model's content directory and builds Eloquent records from the files. Frontmatter becomes attributes, the Markdown body is rendered to HTML in the `body` attribute, and each record includes path information. The source content remains in Markdown; Fusion does not require you to store it in your application's database.\n\nThe casts shown above make attribute access convenient. For example, `$article->date` is returned as a Carbon date and `$article->published` as a boolean. Casts do not define query rules or make fields required. Keep date values in a consistent sortable format, such as `YYYY-MM-DD`, when ordering by the raw date field.\n\nOnce the model is configured, retrieval looks like a normal Eloquent query:\n\n``` php\nuse App\\Models\\Article;\n\n// Keep published articles in selected categories, newest first, 12 per page.\n$articles = Article::query()\n    ->where('published', true)\n    ->whereIn('category', ['laravel', 'php'])\n    ->orderByDesc('date')\n    ->paginate(12);\n```\n\nThis query filters out drafts, keeps articles in either the Laravel or PHP category, sorts the results newest first, and returns twelve per page. The fields used in queries—here `published`, `category`, and `date`—must be listed in `frontmatterFields()`. You can combine these with familiar Eloquent methods such as `where`, `orderBy`, `latest`, and `limit` to shape the result set.\n\nFor a detail page, retrieve one record by its slug:\n\n``` php\n// The filename is used only when Frontmatter has no `slug` key.\n$article = Article::where('slug', $slug)->firstOrFail();\n```\n\nThen render the stored HTML body in a Blade view:\n\n``` php\n<article>\n    <h1>{{ $article->title }}</h1>\n    <time>{{ $article->date->format('F j, Y') }}</time>\n\n    {{-- Fusion renders Markdown as HTML; only output trusted or sanitized content. --}}\n    <div class=\"prose\">\n        {!! $article->body !!}\n    </div>\n</article>\n```\n\nAs with any rendered HTML, treat content as trusted or sanitize it according to your application's security requirements before outputting it unescaped.\n\nBy default, Fusion looks under `resources/content`, using a subdirectory based on the model name. An `Article` model reads from `resources/content/article`, while a `Page` model reads from `resources/content/page`. This convention keeps content organized by type and requires little configuration.\n\nTo keep content somewhere else—for example, alongside project documentation or in a shared content folder—set `FUSION_CONTENT_DIRECTORY` to the root directory containing the model-specific subdirectories:\n\n```\n# Relative to the Laravel app root; Fusion appends each model's subdirectory.\nFUSION_CONTENT_DIRECTORY=content/markdown\n```\n\nRelative paths resolve from the Laravel application's base directory. With this setting, `Article` reads from `content/markdown/article`. Absolute paths are also supported. The configured directory is a shared root, and Fusion appends each model's folder name. This is the simplest option when all content should move together.\n\nIf one model needs a uniquely named folder, override `getResourceFolder()` in that model:\n\n```\npublic function getResourceFolder(): string\n{\n    // Use this model-specific folder instead of the shared root/model-name path.\n    return base_path('docs/posts');\n}\n```\n\nThis gives that model a specific location without changing the shared root used by other models. The tradeoff is that the path is now part of the model code. Fusion uses Sushi to build a table per model class, so changing this path after the model's rows have loaded is not a supported way to switch folders at runtime. For one-off directory inspection, `fusion:check --dir=...` can target a folder without changing a model.\n\nWhen Fusion loads content rows, it parses Frontmatter and renders Markdown. For a large collection, repeating that work on every request can add overhead.\n\nThe cache contains parsed content rows, including Frontmatter values and rendered body HTML. It does not cache the results of Eloquent filters or sorting. Fusion fingerprints the files to detect edits, additions, removals, and renames; even with a warm cache, it still scans and hashes the content files before it can reuse cached rows.\n\nFusion automatically caches resolved content rows using Laravel's configured default cache store. Choose the store in Laravel's `config/cache.php`; there is no separate Fusion setting to enable or disable row caching. Cached rows expire after one hour by default. Set the lifetime in seconds in `config/fusion.php` or with an environment variable:\n\n```\n# Row-cache lifetime in seconds (7200 seconds = two hours).\nFUSION_CACHE_TTL=7200\n```\n\nThis sets the cached row lifetime to two hours. The fingerprinting means caching is most useful when the same collection is loaded repeatedly, especially when parsing and Markdown rendering are significant parts of request work. It does not remove the cost of the Eloquent query itself.\n\nThe `fusion:check` command can inventory Frontmatter fields before you finish a model. Deep validation also checks Frontmatter parsing and Markdown rendering:\n\n```\n# Deep-check Frontmatter parsing and Markdown rendering.\nphp artisan fusion:check --validate\n```\n\nWhen the model defines a `frontmatterRules()` method, Fusion can validate each file's Frontmatter using Laravel's validation rules. Unknown Frontmatter keys can be reported as warnings with `--warn-unknown-fields`.\n\nFor CI, JSON output is easier to process, and changed-file checking can limit work to files in a pull request:\n\n```\n# Validate changed content since the base ref, including schema and unknown-field checks.\nphp artisan fusion:check \\\n    --validate \\\n    --changed \\\n    --base=\"origin/main\" \\\n    --model=\"App\\Models\\Article\" \\\n    --warn-unknown-fields \\\n    --format=json\n```\n\nThis is useful when a repository has many Markdown files but a change touches only a few. The check can report invalid content before it is merged without requiring a full scan on every pull request. Make sure the base ref is available in the CI checkout; for GitHub Actions, use sufficient history (for example, `fetch-depth: 0`) or fetch the base branch before running the command.\n\nFusion keeps the content authoring format simple while exposing the retrieval patterns Laravel developers already know. Teams can review Markdown changes in Git, use Frontmatter to describe structured content, and filter or sort records with Eloquent instead of building a separate content-query layer for every application.\n\nThe package also keeps those concerns separate: Laravel casts shape values when attributes are accessed, Laravel validation rules define which Frontmatter values are acceptable, Fusion handles parsing and rendering, and Eloquent handles querying. That makes the content workflow easier to understand and extend.\n\nFusion lets Laravel applications keep Markdown as the content source while using Eloquent to filter, sort, and paginate it. Teams can describe content fields on a model and use familiar Laravel tools for caching and validation, without building a separate query layer or moving the source content into a database.", "url": "https://wpnews.pro/news/query-markdown-content-with-eloquent-in-laravel-filtering-sorting-and-pagination", "canonical_source": "https://dev.to/robertobutti/query-markdown-content-with-eloquent-in-laravel-filtering-sorting-and-pagination-46o4", "published_at": "2026-10-21 20:58:41+00:00", "updated_at": "2026-10-21 21:11:33.637139+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["Fusion", "Laravel", "hi-folks", "Composer", "Eloquent"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/query-markdown-content-with-eloquent-in-laravel-filtering-sorting-and-pagination", "markdown": "https://wpnews.pro/news/query-markdown-content-with-eloquent-in-laravel-filtering-sorting-and-pagination.md", "text": "https://wpnews.pro/news/query-markdown-content-with-eloquent-in-laravel-filtering-sorting-and-pagination.txt", "jsonld": "https://wpnews.pro/news/query-markdown-content-with-eloquent-in-laravel-filtering-sorting-and-pagination.jsonld"}}