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

Tomato Builder Laravel Package

tomatophp/tomato-builder

A Laravel builder package from TomatoPHP for creating dynamic forms and pages with a drag-and-drop interface. Define fields, layouts, and components visually, then render them in your app with minimal code.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the source before you composer require. This package has two plausible identities in circulation: the description advertises a Tailwind/Vue drag-and-drop page builder, while the shipped documentation describes a config-driven form/layout builder for TomatoPHP admin panels (fields, validation, reusable components). Those are different products with different persistence shapes. Your first hour should answer which one the code actually implements:

  1. src/*ServiceProvider.php — what it registers, what tags it publishes, how it hooks into the Filament panel.
  2. database/migrations/ — the fastest read on the persisted content shape.
  3. composer.json + package.json — Laravel/Filament/Livewire pins, Vue 2 vs 3, Vite vs Mix, Tailwind version.
  4. config/tomato-builder.php — component/field-type registry, disk, route prefix, templates.

Clone it, don't require it, for that pass. Last release was 2023-12-26 with 10 stars and zero dependents, so you are evaluating a frozen artifact, not a living dependency.

Install pinned, on a scratch branch. An unpinned require on a repo with no recent tags can resolve to a branch head; pin to the exact tag you read.

composer require tomatophp/tomato-builder:"<exact-tag>"
php artisan vendor:publish --tag=tomato-builder-config
php artisan vendor:publish --tag=tomato-builder-assets
php artisan migrate

Then register the plugin in your Filament panel provider (TomatoPHP's standard pattern — confirm the exact class in the service provider, don't trust the README):

// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(\TomatoPHP\TomatoBuilder\TomatoBuilderPlugin::make());
}

Rebuild assets (npm run build, php artisan filament:assets, php artisan optimize:clear) and record the full matrix you tested: PHP, Laravel, Filament, Livewire, Node/Vite, Vue, Tailwind. A 2023 tag will likely be stale on at least two of those.

First use case: one small thing, end to end, through the real pipeline. Don't start with anything you care about. Build one low-stakes landing page (if it's the page builder) or one resource with 3–5 mixed fields — text, select, relation (if it's the schema builder). Save it, then render it through your production route with caching enabled and Tailwind's production build on. If the content round-trips and the classes survive, you have something. If not, you've spent two days instead of a quarter.

Where to look when the docs are thin: the demo app in the repo is usually the most reliable source of truth for panel registration and asset wiring.

Implementation Patterns

Pick a mode and don't mix them. The two viable integration styles are structurally different:

  • Scaffold-and-own. The package generates models/migrations/controllers. Run the generator once, commit the output, treat it as first-party code. Best case you can drop the package from the runtime request path entirely. Low risk, reviewable, diffable.
  • Builder-at-runtime. Content persists in a package-defined shape and is read back on every render. You are now coupled to that shape. Only accept this if the payload is JSON you can inspect and transform.

Decide in the spike, write it down, and don't let a second developer quietly do the other one.

Put an adapter between your app and the package. Controllers, Blade views, and Livewire components should never touch package classes directly.

interface BlockRepository
{
    public function forPage(string $slug): BlockCollection;
    public function save(string $slug, array $blocks): void;
}

A thin implementation that satisfies this with the package's models buys you the one thing this package cannot offer: a swap path. When you fork (assume you will), only the adapter changes.

Drive forms and layouts from config, not from Blade repetition. That's the actual advertised value — reusable field definitions with validation and rendering handled for you. Keep definitions in dedicated config or a PHP registration class, not inline in controllers:

// config/forms/contact.php  (illustrative — verify field-type keys in src/)
return [
    'fields' => [
        ['type' => 'text',     'name' => 'name',    'rules' => 'required|max:120'],
        ['type' => 'select',   'name' => 'topic',   'options' => 'topics'],
        ['type' => 'relation', 'name' => 'owner_id','model' => User::class],
    ],
    'template' => 'default',
];

One definition, rendered into the admin panel and reused anywhere else a form is needed.

Prefer server-side rendering of stored content. If the package can render a stored block/field schema through Blade, take it: normal Laravel caching, normal SEO, normal CDN invalidation. If production pages hydrate a stored Vue tree client-side, you've coupled rendering to a JS bundle and to Tailwind class availability — budget for bundle size, hydration cost, and per-publish CDN purges. The target pattern is a Blade component per block type that reads the stored schema.

Version the persisted shape yourself. Add schema_version to your own wrapper model and write a transformer per version on read. The package's content shape is undocumented and unversioned; a version column plus transformers is cheap now and is the difference between a migration and a rewrite later. Ship an export/import artisan command in the same sprint — that script is your escape hatch and should never be deferred.

Handle Tailwind purge explicitly. JIT scans source files, not database rows. Any class that only exists inside stored content gets stripped in production while looking flawless in the builder. Two strategies that work: derive an explicit safelist from the component set the builder can emit, or compile a small CSS bundle per saved payload and serve it with the page. Prove this in the POC, not in staging.

Sanitize on write and again on render. Non-developer-authored HTML and inline styles are an XSS surface and a CSP surface. Clean on save so bad data never lands, clean on render so a poisoned row can't execute. Review CSP implications of any inline styles the builder emits — unsafe-inline is a real cost.

Invalidate cache by tag, not by flush. A page editor saving a block should bust that page's entry and nothing else.

static::saved(fn ($page) => Cache::tags(['pages', "page:{$page->slug}"])->flush());

Give the render path a cache-fallback so one broken page degrades instead of 500-ing the whole section.

Extend through the documented registry, but namespace your additions. Custom field/component types and output templates are the advertised extension points. Keep yours in your own package (acme/builder-components) so a fork bump is a merge, not archaeology.

Ship behind a feature flag with the old editor intact. Marketing pages first, then CMS content, then anything user-facing. The flag is what makes this a bounded evaluation rather than a commitment.

Gotchas and Tips

Assume you own the code from day one. 10 stars, 0 dependents, no release since December 2023. Zero dependents means essentially no independent production validation against Laravel 11/12, Filament 3, Livewire 3, or Tailwind 4. MIT makes vendoring legitimate and clean; it obligates nobody upstream. Fork under an internal namespace, track upstream as a reference implementation, and write the fork policy before the first cherry-pick.

The Vue/Livewire seam is where the bugs live. Both are reactive, both want to own the DOM, both have event systems. Expect asset load-order problems, event-name collisions, and double renders during the spike. If your app is Livewire-only or React-first, you're importing a second frontend paradigm — look elsewhere.

Framework upgrades will fight you. A 2023 tag predates a lot. If it pins Vue 2 or Laravel Mix, you're choosing between forking to bump it and freezing the rest of your stack. Find out in the spike, not the sprint.

Zero dependents means you are the QA. Write one smoke test and run it in CI: create a record, save it, render it through the production route, assert a representative builder-emitted class survived into the HTML. That single test catches the Tailwind purge regression, the route regression, and the schema-shape regression at once.

Debugging checklist when a page renders blank or unstyled:

  1. Dump the raw persisted payload — valid JSON/HTML, or did sanitization eat it?
  2. Render the same payload through a bare route outside the admin panel — separates storage from rendering.
  3. grep the compiled CSS for the class names found in the payload — confirms purge.
  4. Disable the cache layer and re-render — confirms invalidation.
  5. Re-run php artisan filament:assets / optimize:clear — published assets are a common silent casualty.

Don't migrate an existing builder on the strength of this. If you already run GrapesJS, Puck, or bespoke Blade blocks, this is a lateral move into an unmaintained dependency, not an upgrade. Sequence it: one-day scope audit → three-to-five-day contained POC → decision gate. Proceed only if production render is correct (classes survive), the UX is acceptable to a real non-technical editor, and export is lossless.

Watch for scope drift. If the code implements the CRUD/schema generator while stakeholders were pitched a page builder, stop and reset expectations before any engineering time is spent. That discrepancy is a decision input, not a footnote.

Budget the ongoing cost honestly. Two engineers should be able to read the Vue source, the persistence layer, and the render path. A single-person bus factor on a forked dependency is an organizational risk. Expect 10–20% of one engineer for two quarters to stabilize, then steady dependency bumps and security patches.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core