tomatophp/tomato-sections
Laravel package for TomatoPHP that lets you create and manage dynamic page sections from the admin panel. Build reusable content blocks with customizable views, ordering, and localization—no code changes needed to update site layouts.
tomato-sections is the dynamic-content/page-section layer of the TomatoPHP admin stack — an admin-managed table of named sections (each with a key and a structured value payload) that your Blade views read back at render time. Treat it as a lightweight, developer-defined CMS, not a free-form page builder.
Minimum path to a working install:
composer require tomatophp/tomato-sections
php artisan tomato-sections:install
php artisan migrate
The install command publishes the config, migration, and (depending on flags) seeds demo data — check the README for your version's --with / --without options before running it on an existing database, since it may also touch tomato-admin prerequisites.
Where to look first, in this order:
config/tomato-sections.php — table name, route/middleware, and any resource registration flags.value column (JSON vs longText determines whether you cast to array or store raw HTML).app/Filament/**/SectionsResource (or the vendor resource, if not published) — this is where fields, form schema, and table columns are defined.First real use case: a marketing homepage whose hero copy, CTA labels, and footer blurb are editable without a deploy.
// app/Models/Section.php (or use the vendor model directly)
$hero = \TomatoPHP\TomatoSections\Models\Sections::where('key', 'home.hero')->first();
{{-- resources/views/sections/home/hero.blade.php --}}
@php($hero = $sections['home.hero'] ?? null)
@if ($hero)
<section class="hero">
<h1>{{ $hero['title'] }}</h1>
<p>{{ $hero['subtitle'] }}</p>
<a href="{{ $hero['cta_url'] }}">{{ $hero['cta_label'] }}</a>
</section>
@endif
The upgrade from v1.0.5 → v1.0.6 is UI-only: dark mode and RTL styling in the admin resource. No API, schema, or method signature changes are advertised, so nothing in the above needs to change.
Resolve sections once, render many. Don't hit the database inside a loop of partials. A small view composer or a single service call keeps N queries from becoming N×M:
// app/Providers/ViewServiceProvider.php
View::composer('sections.*', function ($view) {
$view->with('sections', Cache::rememberForever('sections.all', fn () =>
\TomatoPHP\TomatoSections\Models\Sections::pluck('value', 'key')->toArray()
));
});
Flush that key from the model's saved/deleted events so editors see changes immediately instead of waiting for a TTL.
Key namespacing is the whole contract. Because views look sections up by key string, adopt a convention from day one — {page}.{slot} (home.hero, pricing.faq) or {feature}.{slot}. This lets you group, filter, and later swap a section's implementation without touching the admin UI. Renaming a key in the admin panel silently blanks a live page; a namespaced key at least makes the breakage obvious in the resource list.
Seed keys, don't create them in production. The section's schema (which keys exist, which sub-keys each expects) belongs in a seeder so every environment — local, staging, CI — starts with the same set. The content stays admin-editable.
// database/seeders/SectionSeeder.php
Sections::updateOrCreate(['key' => 'home.hero'], ['value' => [
'title' => 'Welcome',
'subtitle' => '',
'cta_label' => 'Get started',
'cta_url' => '/register',
]]);
Cast and validate the payload. If value is JSON, cast it to array on the model and validate sub-keys on save with a Filament form schema. Without a declared shape, an editor can add a field that your Blade silently ignores — or worse, remove one that your view dereferences unguarded. Use data_get($hero, 'cta.label') with a default rather than $hero['cta']['label'].
Localisation: one key per locale, not one JSON per locale. Storing home.hero.en / home.hero.ar as separate rows keeps the admin UI, the cache key, and your lookup logic simple ("home.hero.{$locale}", falling back to the default locale).
Compose with the rest of the Tomato stack rather than duplicating it. tomato-category for taxonomies attached to sections, tomato-forms for embedded forms referenced by ID inside a section payload, tomato-settings for global values that shouldn't be per-section. Resist storing relational data inside the value blob — IDs and slugs only.
RTL and dark mode (new in v1.0.6). The admin resource now honours Filament's panel-level theming. To actually see it:
->darkMode(true)
->defaultThemeMode(ThemeMode::Dark)
// and for RTL, a panel locale list including e.g. 'ar' with RTL direction
->locales(['en' => 'English', 'ar' => 'العربية'])
These are admin-panel concerns. If your front end also needs RTL, that's your own CSS — the package only styles its resource. Use logical properties (margin-inline-start, padding-inline-end) in any custom section views so a single stylesheet works in both directions, and prefer CSS variables / dark: variants over hardcoded hex values so editor-entered content doesn't break in dark mode.
value field is typically edited through a rich-text/editor field, so printing it requires {!! !!}. That makes the admin panel a stored-XSS surface if any non-trusted role can edit sections. Gate the resource with a policy and sanitise on render (HTMLPurifier or a strict allow-list) for anything user-facing.vendor:publish --tag=tomato-sections-views and edited the files, your copies won't receive the fix — diff the published views against vendor before assuming the upgrade "didn't work." Same for any published CSS assets; re-run the publish/asset step after composer update.Sections::where('key', ...)->first()) first, then bisect the cache layers.JSON columns reject invalid JSON silently in some paths and loudly in others; longText columns store whatever the editor posts. Pick one storage type, cast it consistently, and add a array cast in a custom model if you've extended the vendor one — otherwise $section->value returns a string on one environment and an array on another.register_navigation-style flag if the menu item appears twice.SectionsResource and pointing config at your class) is cheaper than forking the package — you keep the RTL/dark-mode upstream improvements in v1.0.6 while customising form fields and table columns.tomato-roles/Filament shield early. Section editing is effectively "edit the website," so it should not inherit the default authenticated-admin policy.Sections::factory()->create(['key' => 'home.hero', 'value' => [...]]) plus a view assertion is enough to cover the render path; also assert the graceful-empty case ($sections has no matching key), because that's the failure mode a key rename produces in production.How can I help you explore Laravel packages today?