Technical Deep Dive

Embedding a Drag-and-Drop Page Builder into Laravel Filament: The D'Invitations Architecture

How I integrated a custom GrapesJS plugin into a Filament 3 admin panel so non-technical event organizers can visually build digital wedding invitations, and what it took to make two very different ecosystems cooperate.

Yodhimas Geffananda
Yodhimas Geffananda
Software Engineer
March 18, 2026
5 min read
Embedding a Drag-and-Drop Page Builder into Laravel Filament: The D'Invitations Architecture

Embedding a Drag-and-Drop Page Builder into Laravel Filament: The D'Invitations Architecture

D'Invitations is a digital invitation platform I build and maintain for Sajiwa Foundation. On the surface it looks simple: clients order an invitation package, customize a wedding invitation page, and share a link with their guests. Under the surface, it required solving a genuinely interesting integration problem: embedding a full client-side visual page builder (GrapesJS) inside a server-driven admin panel (Filament 3 on Livewire), two ecosystems that were never designed to talk to each other.

The Goal

The platform had one non-negotiable requirement: non-technical users must be able to design invitation pages themselves. Event organizers should pick a template, drag blocks around, swap photos, change colors and music, and publish, without ever seeing HTML. At the same time, admins needed a real back office: packages and pricing, orders and order history, events, guest groups, RSVP tracking, and check-in on the event day.

That split the architecture into two halves:

  • A business platform: classic CRUD territory, perfect for Filament.

  • A visual builder: deeply interactive, client-heavy, perfect for GrapesJS.
  • The hard part was the seam between them.

    Why Filament + GrapesJS Is an Awkward Pair

    Filament 3 is built on Livewire: state lives on the server, and the frontend re-renders based on server round-trips. GrapesJS is the opposite: an entire editing environment living in the browser, holding a large, constantly mutating document model (components, styles, assets) that only occasionally needs to be persisted.

    Dropping GrapesJS into a Livewire component naively causes real problems: Livewire DOM diffing fights the editor's own DOM, large builder payloads get serialized on every interaction, and editor state gets wiped on component re-renders.

    The Two-Package Solution

    I ended up splitting the builder integration into two independently versioned packages:

    1. grapesjs-dinvitations: the editor plugin

    A standalone GrapesJS plugin that owns everything invitation-specific: custom blocks (cover sections, couple profiles, event details, galleries, RSVP forms, countdowns), invitation-appropriate style presets, and locale support. It has its own webpack + Sass build pipeline and is versioned independently (currently v1.0.8x), so the editor can evolve without touching the Laravel application at all.

    Treating the plugin as a real product with releases, not a pile of JS inside resources/, was one of the best decisions in the project. Builder bugs are debugged in isolation with a plain HTML playground, and the Laravel app just consumes a built artifact.

    2. filament-grapesjs-v3: the bridge

    A custom Filament form field package that makes the builder behave like any other input in a Filament form. It mounts GrapesJS into a protected DOM region that Livewire is instructed not to touch, syncs the serialized document into Livewire state only at meaningful moments (save, publish) rather than on every keystroke, and hydrates the editor back from stored state when the form loads.

    From Filament's perspective, the entire page builder is just a form field. That means validation, saving, and authorization all flow through the same pipeline as every other field in the admin panel, with no special cases.

    The Platform Around the Builder

    The rest of the system is a Laravel 12 monolith with PostgreSQL 17 and Redis, fully containerized (Sail in development, Docker Compose with certbot in production). A few pieces worth mentioning:

    • Guest management & QR check-in: guests are organized into groups, each invitation link carries a QR code generated server-side, and on the event day staff scan codes to check guests in.
    • RSVP wishes & greeting wall: guest-facing interactive features backed by the same models the admin panel manages.
    • Orders, packages, and features: packages are composed from feature flags (Feature, FeaturePackage), so new tiers can be assembled without code changes.
    • PDF & assets: DomPDF for printable outputs, S3-compatible storage for uploaded photos and music, Pusher for real-time updates.
    • Roles & policies: Spatie permissions separate admins from clients, with Laravel policies guarding every resource.

    Lessons Learned

    Isolate the chaos. The single most valuable architectural move was containing GrapesJS behind a versioned package boundary. Interactive, client-heavy features will always be the most volatile part of a server-rendered app; give them their own release cycle.

    Sync state at moments, not on events. Trying to keep a rich client-side document continuously in sync with Livewire is a losing battle. Deciding when state crosses the boundary (explicit save points) eliminated an entire class of bugs.

    Make the builder a form field. Once the editor conformed to Filament's field contract, everything else (validation, persistence, permissions) came for free. When integrating a foreign ecosystem, find the smallest native abstraction it can hide behind.

    The platform is live and under continuous development; the builder keeps growing new blocks, and the event-day tooling keeps expanding. It has been the best kind of freelance project: real users, real constraints, and a real architecture problem worth writing about.

    0 views
    Yodhimas Geffananda

    Written by Yodhimas Geffananda

    Software Engineer passionate about building web applications and sharing knowledge with the developer community.

    Comments

    Connect wallet to comment

    No comments yet. Be the first!