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

Nova Flexible Content Laravel Package

think.studio/nova-flexible-content

A Laravel Nova field that adds flexible, repeatable content blocks to your resources. Build layouts from configurable block types with nested fields, reorder them in the admin panel, store as JSON, and render your own Blade views on the front end.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and verify first. This is a zero-star, zero-dependent package with a NOASSERTION license, so treat the install as an evaluation, not a commitment:

composer require think.studio/nova-flexible-content
php artisan vendor:publish --provider="Think\Studio\NovaFlexibleContent\FieldServiceProvider"

Before writing any application code, confirm three things in the repo: (1) which Nova major version the composer.json requires (nova ^4.0 vs ^5.0) — Nova 5 broke a lot of field packages; (2) whether the service provider name and namespace in the README actually match the src/ tree; (3) the license file contents. NOASSERTION on Packagist means the license couldn't be machine-detected — open LICENSE.md and get sign-off before this ships to production. If the file is missing entirely, assume all rights reserved and stop.

Where to look first, in order:

  1. README.md — layout syntax and the JSON shape it stores. On a near-zero-adoption package the README is often the only spec.
  2. src/Fields/FlexibleContent.php (or equivalent) — the resolve/fill methods tell you exactly what lands in the database column and how it hydrates back.
  3. src/Layout.php, src/Group.php — the block/repeater primitives and any FlexibleContent cast.
  4. composer.json + tests/ — Nova version constraint and whether the layout resolution logic is covered at all. No tests plus no stars means you are the QA department.
  5. git log and the author's other repos — this is very likely a fork or re-skin of whitecube/nova-flexible-content. Diff the two before adopting; the upstream is battle-tested and this one may be a thin wrapper.

First use case. The canonical fit is a heterogeneous page builder: a pages table with a content JSON column, where editors add blocks (hero, rich text, image + caption, CTA, gallery) in any order. Define one field named content on the Page resource, declare 3–4 layouts, and render them in Blade by switching on the layout key. Build the smallest possible version of this on a throwaway resource first — one layout, one text attribute — and confirm the round trip (create → save → reload → edit → save) works before you invest in a full block library.

Implementation Patterns

Declare layouts close to the field, not scattered. Keep the layout definitions in a dedicated class or a PageBlocks config so the editor UI and the Blade renderers stay in sync:

FlexibleContent::make('Content')
    ->addLayout('Hero', 'hero', [
        Text::make('Heading'),
        Textarea::make('Subheading'),
        Image::make('Image'),
    ])
    ->addLayout('Rich Text', 'rich_text', [
        NovaTiptap\Tiptap::make('Body'),
    ])
    ->addLayout('CTA', 'cta', [
        Text::make('Label'),
        Text::make('URL'),
    ])
    ->button('Add block');

Cast the column explicitly. Do not hand-decode JSON in views. Use the package's cast (typically AsFlexibleContent / FlexibleContent::class) on the Eloquent model and add the column to $casts. Then a helper like $page->content returns an iterable of layouts whose ->layout() gives you the slug and ->getAttributes() the values. If the package ships no cast, write a small one yourself; it's the single highest-leverage thing you can do to keep controllers clean.

Render with a layout-per-partial convention. One Blade file per layout slug, resolved by convention:

@foreach ($page->content as $block)
    @includeIf("blocks.{$block->layout()}", ['block' => $block])
@endforeach

This means adding a block type is a three-file change (layout definition, partial, content entry) with no controller churn — the main reason to use a flexible-content field instead of a dozen nullable columns.

Use nested repeaters for homogeneous sub-lists, layouts for heterogeneous ones. A gallery is a repeater of images; a page body is a set of layouts. Mixing the two (layouts inside a repeater) is supported but the editor UX degrades fast past two levels of nesting. Cap it at two.

Validate per layout, not globally. Nova's rules() on the parent field will run against the whole payload. For per-layout rules, either put constraints on the child fields (they're evaluated per block) or add a ->rules() on each layout's fields. Don't try to write a single content.*.heading rule set — layout order is user-controlled and the indexes won't be stable.

Seed and mutate programmatically through the same shape the field produces. If you need to migrate existing content into blocks (e.g. split a body column into rich-text blocks), write the JSON in the exact structure the cast expects — usually [['layout' => 'rich_text', 'attributes' => ['body' => '...']], ...]. Test the round trip with $page->save(); $page->refresh(); before running it over real data.

Keep a version marker in the JSON. Add a top-level key or a sibling column like content_version so you can detect and migrate old block schemas later. Flexible content is schema-on-read; without a version you'll be guessing at legacy shapes in six months.

Gotchas and Tips

The adoption risk is the real headline. 0 stars, 0 dependents, and NOASSERTION licensing means: no community to answer questions, no security review, no guarantee of upstream fixes, and a legal question mark. Budget for reading the source yourself. If you need the capability and Nova 5 support long-term, evaluate whitecube/nova-flexible-content or outl1ne/nova-page-manager alongside this and compare commit history — if this package is a fork, the diff will tell you whether it adds anything or just rebrands.

Nova version drift is the most likely day-one failure. Nova field packages break quietly between majors: fields() signatures, resolveUsing, and the Nova::script asset registration change. If composer require resolves but the field renders blank, open the browser console first — a JS asset 404 or a Vue 2/3 mismatch is the usual cause, not your PHP. Pin the Nova version in composer.json once it works.

JSON casting collisions. If the column already has a cast, or if you're also using ->asJson() on the model, the field's hydration can silently double-encode. Symptom: the field shows empty on edit but the DB has data. Check the raw column with $page->getRawOriginal('content') and compare to $page->content.

Edit forms are where these fields leak. Large flexible fields (10+ blocks, nested repeaters, image fields) make the Nova edit screen slow and the payload huge. Symptoms are a laggy drag handle and a save that times out at max_input_vars. Mitigations: lazy/onlyOnForms file fields, limit layouts per field, and split very large pages across multiple fields or tabs.

File and image attributes need care. Media stored inside block JSON doesn't get cleaned up when a block is deleted, and Image::make() inside a layout behaves differently from a top-level field in some Nova versions. Verify delete-and-save actually removes the file reference, and consider a scheduled orphan sweep for the storage disk.

Searching and indexing block content is not free. Everything lives in one JSON column, so WHERE content LIKE ... is your only out-of-the-box query and it's fragile. If editors expect site search over block text, plan for a separate index (Scout/Meilisearch) or denormalize a content_plain column on save via an observer.

Debugging checklist, in order: dd($page->getRawOriginal('content')) to confirm storage → dd($page->content) to confirm cast hydration → browser console for Vue/asset errors → php artisan nova:publish if assets look stale → check config/nova.php middleware if the field 403s on save. Because there's no issue tracker activity to lean on, capture your own notes; you'll be the only documentation this package has.

Extension points to prefer over patching. Override the field class in your app (class PageContent extends FlexibleContent) rather than editing vendor/. If you must patch, use composer-patches. Never rely on a dev-main constraint here — with no release discipline, an unpinned fork can change shape without warning.

Have an exit plan. Because the stored shape is simple JSON, migrating off this package is feasible — write an artisan command that reads the column through the cast and writes to explicit tables or a different field package. Keep that option open by not letting layout definitions leak into controllers or models beyond the cast, and by always rendering through the blocks.{layout} convention so a swap only touches the include path.

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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings