glhd/linen
Linen is a lightweight flat-file CMS for Laravel that turns Markdown and Blade files into a fast, database-free website. Write content in plain files, organize pages with simple routing, and publish docs or marketing pages with minimal setup.
Linen is a lightweight, Blade-inspired template engine for Laravel that renders to a string instead of returning an HTTP response. Its reason to exist is safety: templates are compiled by Linen's own parser into a restricted expression set rather than into raw PHP, which makes it viable for templates you didn't write yourself (CMS blocks, email/notification copy, tenant-editable content, AI-generated snippets).
Minimal path:
composer require glhd/linen
use Glhd\Linen\Linen;
$html = Linen::render('welcome', ['name' => 'Chris']);
@if, @foreach, @include, etc.), echo forms ({{ }} vs {!! !!}), and helper functions are supported before you promise any syntax to template authors.php artisan vendor:publish --tag=linen-config or equivalent) to see the template path, file extension, and compile-cache settings — that tells you where authors put files and what to clear when things look stale.{{ $x }} escapes HTML, and one asserting {!! $x !!} does not. Those two tests encode the security contract you're relying on.TemplateRenderer/LinenContent service or value object rather than calling the facade from controllers. You get one place for sanitization, caching, error handling, and an escape hatch if you ever swap engines.templates table (key, body, updated_at) plus Linen::render($template->body, $data). Hash the body into the cache key so an edit invalidates cached output automatically.$user->password) to anyone who can write a template. Build an explicit view-model array in your service.{!! $safeHtml !!} after your sanitizer pass. This keeps the trusted surface in Blade.@price($amount), @button('label')) so authors compose UI without expression access. Keep them pure: no DB calls or side effects inside a directive, or rendering becomes unpredictable and untestable.hash(template) + hash(serialized data + locale) with a short TTL.{{ system('id') }}-style attempts, unknown directives) fails closed rather than executing.illuminate/* requirement. Check composer.json before upgrading: if the constraint moved to ^13 only, apps on Laravel 11/12 must stay pinned to 0.0.x. On Laravel 13, run the full suite — the compiler/parser transitive dependencies may resolve to different versions than you tested against.0.x are allowed to break. Pin with ~0.1.0, read every changelog, and don't assume an upgrade guide exists yet. There is no deprecation policy to lean on.{!! !!} is the entire attack surface. Once authors have raw echo, escaping-by-default buys you nothing. Consider forbidding it in author-supplied templates (grep/lint on save, or a directive allowlist enforced before compile) unless you also run an HTML sanitizer.php artisan view:clear only helps if Linen hooks Laravel's view compiler.\Throwable at the service boundary and convert to a validation-style error. Never let a stack trace with absolute paths reach an authoring UI.@php, @props, @slot, @auth and friends are probably absent or intentionally excluded). When a template does something surprising, the first debugging step is "is this actually a supported directive?" — Linen tends to fail quietly or literally rather than throwing on unrecognized syntax.How can I help you explore Laravel packages today?