guillaumeferron/post-content
PostContent is a Laravel package for managing post content in Eloquent models. It offers a clean API to store, organize, and render post bodies and related content, making it easy to add flexible publishing or CMS-style features to your application.
Install the field via Composer into an existing Laravel Nova app:
composer require guillaumeferron/post-content
Before wiring it into a resource, read the README end-to-end and open src/ to inspect the field class. At 22 stars and a single maintainer, the source is the documentation for anything the README glosses over — check the Field subclass to see what $component it registers, what meta() it exposes to the frontend, and whether it ships a publishable config.
Add the field to a Nova resource:
use GuillaumeFerron\PostContent\PostContent;
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title')->rules('required', 'max:255'),
PostContent::make('Content'),
];
}
The first real use case is replacing a Trix or Textarea field where editors were hand-writing repeated block markup. This field gives you a structured builder instead: you keep the model column as a single JSON/comma column and render it in Blade. Create the migration column as json (or longText on older MySQL) and add an array/json cast to the model before you first save, so you never debug a half-stringified payload.
Store structured, render declaratively. The field's value comes back as a nested structure, not HTML. Cast it in the model and hand blocks to partials:
protected $casts = ['content' => 'array'];
@foreach ($post->content['blocks'] ?? [] as $block)
@includeIf("partials.blocks.{$block['type']}", ['block' => $block])
@endforeach
@includeIf keeps unknown block types from 500-ing your page when an editor adds a block you haven't built a partial for yet — important, because the field lets non-developers mint structure faster than you can ship templates.
Pair it with the rest of a post resource. The field is deliberately a content builder, not a page builder. Compose around it: Text for title, Slug/Text for the permalink, DateTime/Date for published_at, BelongsTo for author, and a Boolean for draft status. Keep the content field isolated so index views stay cheap — use PostContent::make('Content')->onlyOnForms() and surface a derived excerpt accessor in the index instead.
Draft/revision workflows. Because the whole payload is one JSON column, revisions are trivial: a PostRevision model plus a Nova action that copies $post->content into a revision row on save gives you version history without fighting the field. This is often the single highest-value integration and it's why a JSON column beats per-block Eloquent models here.
Feed the front end. Expose content through an API resource unchanged and let the SPA render blocks — the structure is already serializable. Do not pre-render to HTML in the field or in an accessor; you'll lose the ability to restyle blocks later.
meta() contract). Check the package's composer.json constraint against your Nova version before adopting, and pin the field version.dd() one saved payload early and snapshot the JSON shape in a test fixture. A future block-type rename is a data migration; decide now whether your renderer tolerates unknown/missing type keys.object. Use array and null-coalesce aggressively (?? [], ?? ''). Half-saved or editor-cleared blocks are the number one source of Trying to access array offset on null in Blade.rules() on a JSON field is weak; add a FormRequest or model-level validation for required content and block count limits, and validate required subkeys of each block type when you render.Text-based index. Add a derived excerpt column or scope with whereJsonContains if you need filtering, and avoid sortable() on the content field.renderBlock() helper so a maintained fork — or a swap to a Block-model approach — is a one-file change.meta(), whether the frontend component list is hardcoded (in which case custom block types mean publishing and editing the Vue component), and whether a config file can be published (php artisan vendor:publish). If block types are hardcoded with no config hook, that's your main long-term constraint — plan for a fork or a wrapper field.How can I help you explore Laravel packages today?