tivents/livewire-form-builder

A powerful drag-and-drop form builder for Laravel 12 with Livewire 5, supporting all field types, conditional logic, multi-column layouts, and form submissions.

Maintainers

Package info

github.com/tivents/livewire-form-builder

pkg:composer/tivents/livewire-form-builder

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.4 2026-03-26 14:13 UTC

This package is auto-updated.

Last update: 2026-03-26 14:20:04 UTC


README

A powerful, drag-and-drop form builder for Laravel 12 and Livewire 5 — a drop-in replacement for form.io.

The package ships no Models and no Migrations. You own your data layer. The package communicates with your app through a clean FormRepositoryContract interface.

Features

Feature
Drag & Drop builder canvas
15 field types
Multi-column layout (full, 1/2, 1/3, 2/3, 1/4, 3/4)
Repeater groups with nested columns
Conditional logic (show/hide per AND/OR rules)
Real-time per-field validation
File uploads (single & multiple)
JSON schema import / export
CSV submission export
Repository pattern — bring your own Model
Artisan scaffolding commands
Fully publishable views

Field types

Group Types
Inputs text, textarea, number, select, checkbox, radio, toggle, datetime, file, repeater, hidden
Layout heading, hint, html, divider, row

Requirements

  • PHP ^8.2
  • Laravel ^12.0
  • Livewire ^4.0

Installation

composer require tivents/livewire-form-builder

1. Publish config

php artisan vendor:publish --tag=livewire-form-builder-config

2. Publish the repository stub

php artisan livewire-form-builder:publish-stubs

This places the following file in your project:

File Purpose
app/Repositories/LivewireFormBuilderRepository.php Eloquent implementation of FormRepositoryContract

The package ships no migration. Create your own migration for the forms and form_submissions tables (the stub's docblock shows the expected columns) and run php artisan migrate when ready.

You are free to rename tables, add columns, or swap out Eloquent for anything else — as long as your repository implements the contract.

3. Include the package styles (Tailwind CSS)

The builder and renderer use Tailwind CSS classes. When you embed the components inside your own app layout, Tailwind's build step must scan the package views — otherwise the classes will be purged and the UI will be unstyled.

Tailwind v4 — add an @source directive to your resources/css/app.css:

@source "../../vendor/tivents/livewire-form-builder/resources/views";

Tailwind v3 — add the path to the content array in your tailwind.config.js:

content: [
    // ... your existing paths
    './vendor/tivents/livewire-form-builder/resources/views/**/*.blade.php',
],

Then rebuild your assets (npm run dev / npm run build).

Note: The built-in admin routes (/livewire-form-builder) use the package's own layout, which loads Tailwind via CDN and does not require the above. The step above is only needed when embedding <livewire:livewire-form-builder::builder /> or <livewire:livewire-form-builder::renderer /> inside your own Blade layouts.

4. Bind the repository

In app/Providers/AppServiceProvider.php:

use Tivents\LivewireFormBuilder\Contracts\FormRepositoryContract;
use App\Repositories\LivewireFormBuilderRepository;

public function register(): void
{
    $this->app->bind(FormRepositoryContract::class, LivewireFormBuilderRepository::class);
}

Or set it in config/livewire-form-builder.php:

'repository' => \App\Repositories\LivewireFormBuilderRepository::class,

Usage

Admin builder UI

Navigate to /livewire-form-builder — requires the middleware configured in config/livewire-form-builder.php (auth by default).

Embed the builder in your own view

{{-- Create new form --}}
<livewire:livewire-form-builder::builder />

{{-- Edit existing form --}}
<livewire:livewire-form-builder::builder :form-id="$form->id" />

Embed the renderer (public-facing)

{{-- By form ID --}}
<livewire:livewire-form-builder::renderer :form-id="$form->id" />

{{-- Inline schema (form-id still used to save the submission) --}}
<livewire:livewire-form-builder::renderer :form-id="$product->id" :schema="$schemaArray" />

{{-- Custom success message --}}
<livewire:livewire-form-builder::renderer
    :form-id="$form->id"
    success-message="Vielen Dank für Ihre Anfrage!" />

{{-- Redirect after submit --}}
<livewire:livewire-form-builder::renderer
    :form-id="$form->id"
    redirect-url="{{ route('thank-you') }}" />

{{-- Edit mode: pre-fill with existing submission data --}}
<livewire:livewire-form-builder::renderer
    :form-id="$product->id"
    :schema="$schema"
    :submission-id="$participant->id" />

{{-- Edit mode with initial data (avoids extra API call) --}}
<livewire:livewire-form-builder::renderer
    :form-id="$product->id"
    :schema="$schema"
    :submission-id="$participant->id"
    :initial-data="$participant->data" />

{{-- Extra fields injected from the backend (not part of the schema) --}}
<livewire:livewire-form-builder::renderer
    :form-id="$product->id"
    :schema="$schema"
    :extra-fields="[
        ['key' => 'notify_participant', 'type' => 'checkbox', 'label' => 'Teilnehmer benachrichtigen', 'value' => '1', 'width' => 'full'],
    ]" />

{{-- Show fields marked as hidden in the schema (e.g. admin backend) --}}
<livewire:livewire-form-builder::renderer
    :form-id="$product->id"
    :schema="$schema"
    :submission-id="$participant->id"
    :show-hidden="true" />

Renderer props

Prop Type Default Description
form-id int|string|null null Load schema from repository and associate submissions
schema array [] Pass schema directly (flat field array or full {name, schema, settings} object). When set, the repository is not called for schema loading. form-id is still used to save submissions.
submission-id int|string|null null Edit mode — pre-fills the form and calls updateSubmission on submit instead of saveSubmission
initial-data array [] Pre-fill data for edit mode. Skips the repository findSubmissionOrFail call when provided.
extra-fields array [] Additional fields injected from the backend, not part of the schema. Values are merged into the submission data.
show-hidden bool false Show fields marked as hidden: true in the schema (with a "Hidden field" badge).
success-message string 'Thank you! Your response has been recorded.' Message shown after successful submission (no redirect).
redirect-url string '' Redirect to this URL after submit instead of showing the success message.

Listen to Livewire events

// In a parent Livewire component
#[On('form-submitted')]
public function onFormSubmitted(int|string|null $formId, array $data): void
{
    // New submission — $data contains all field values incl. extra-fields
}

#[On('form-updated')]
public function onFormUpdated(int|string $submissionId, int|string|null $formId, array $data): void
{
    // Existing submission was updated
}

#[On('form-saved')]
public function onFormSaved(int|string|null $formId): void
{
    // Builder saved/updated a form schema
}

Listen to JS events

document.addEventListener('livewire:init', () => {
    Livewire.on('form-submitted', ({ formId, data }) => {
        console.log('New submission', data);
    });
    Livewire.on('form-updated', ({ submissionId, formId, data }) => {
        console.log('Updated submission', submissionId, data);
    });
    Livewire.on('form-saved', ({ formId }) => {
        console.log('Builder saved form', formId);
    });
});

Configuration (config/livewire-form-builder.php)

return [
    // Your repository implementation
    'repository' => \App\Repositories\LivewireFormBuilderRepository::class,

    // URL prefix for the built-in admin routes
    'route_prefix'   => 'livewire-form-builder',
    'middleware'     => ['web', 'auth'],
    'builder_routes' => true,   // false to disable built-in CRUD routes

    // Pagination
    'per_page' => 25,

    // File upload
    'disk'             => 'public',
    'upload_directory' => 'livewire-form-builder/uploads',
    'max_file_size'    => 10240,   // KB

    // Register custom field types
    'field_types' => [
        // 'signature' => \App\FormFields\SignatureField::class,
    ],
];

Adding a Custom Field Type

Use the scaffold command:

php artisan livewire-form-builder:make-field StarRating

This generates:

  • app/FormFields/StarRatingField.php — implement your logic
  • resources/views/vendor/livewire-form-builder/fields/star_rating.blade.php — renderer view
  • resources/views/vendor/livewire-form-builder/settings/star_rating.blade.php — builder settings panel

Then register it:

// config/livewire-form-builder.php
'field_types' => [
    'star_rating' => \App\FormFields\StarRatingField::class,
],

Row / Column Layout

Fields support a flat width property (full, 1/2, 1/3, 2/3, 1/4, 3/4) that positions them on a shared 12-column grid. For explicit structural grouping, use the row field type as a container.

A row is always full-width itself and renders a nested 12-column grid for its children. Fields inside a row use the same width values.

When to use row:

  • You want to move or delete a group of fields as a unit in the builder
  • You need semantically grouped columns (e.g. first name + last name side by side)
  • You want to mix different widths within a clearly bounded section

Schema structure:

{
  "type": "row",
  "key": "name_row",
  "width": "full",
  "children": [
    { "type": "text", "key": "first_name", "label": "First Name", "required": true, "width": "1/2" },
    { "type": "text", "key": "last_name",  "label": "Last Name",  "required": true, "width": "1/2" }
  ]
}

Children support all standard field properties (validation, conditional logic, etc.). The row field itself carries no validation rules.

JSON Schema Format

{
  "name": "Kontaktformular",
  "schema": [
    { "type": "heading", "key": "h1", "text": "Kontakt", "level": "h2", "width": "full" },
    {
      "type": "row", "key": "name_row", "width": "full",
      "children": [
        { "type": "text", "key": "name_abc",  "label": "Name",   "required": true, "width": "1/2" },
        { "type": "text", "key": "email_xyz", "label": "E-Mail", "required": true, "width": "1/2", "input_type": "email" }
      ]
    },
    {
      "type": "select", "key": "topic_def", "label": "Thema", "width": "full",
      "options": [{ "label": "Vertrieb", "value": "sales" }, { "label": "Support", "value": "support" }]
    },
    {
      "type": "textarea", "key": "msg_ghi", "label": "Nachricht", "required": true, "rows": 5,
      "conditions": {
        "action": "show", "logic": "and",
        "rules": [{ "field": "topic_def", "operator": "!=", "value": "" }]
      }
    },
    {
      "type": "text", "key": "internal_note", "label": "Interne Notiz", "hidden": true, "width": "full"
    }
  ]
}

Field properties

Property Description
type Field type (see field types table)
key Unique identifier within the form — used as the data key in submissions
label Display label
width Column width: full, 1/2, 1/3, 2/3, 1/4, 3/4
required true to make the field mandatory
hidden true to hide the field in the renderer by default. The field is still initialised and its default value is submitted. Visible when :show-hidden="true" is passed to the renderer. Configurable in the builder settings panel.
default Default value pre-filled on mount
disabled true to render the input as read-only
placeholder Input placeholder text
hint Helper text shown below the label
conditions Conditional logic — see below

Examples

The examples/ directory contains ready-to-copy code for common integration scenarios:

File Pattern Use case
ApiWebhookRepository.php Repository decorator Saves to DB and POSTs a signed JSON payload to a webhook URL
FormSubmissionListener.php Laravel event listener Reacts to a FormSubmitted event — supports ShouldQueue for background processing
api-form-page.blade.php Client-side JS Listens to the browser form-submitted event and calls any HTTP endpoint via fetch()
CentralApiFormRepository.php Central API repository Full repository backed entirely by an HTTP API — no local DB needed; for multi-system setups

See examples/README.md for setup instructions and payload shapes.

Repository Contract

Your repository must implement Tivents\LivewireFormBuilder\Contracts\FormRepositoryContract:

Method Description
findOrFail(id) Return form object with at least id, name, schema, settings
create(data) Persist new form, return it
update(id, data) Update form, return it
delete(id) Delete form
paginate(perPage) Return paginated list of forms
saveSubmission(formId, data, meta) Store a new submission
updateSubmission(submissionId, data, meta) Update an existing submission (called in edit mode)
paginateSubmissions(formId, perPage) Return paginated submissions for a form
findSubmissionOrFail(formId, submissionId) Return single submission object with a data array property
deleteSubmission(submissionId) Delete submission

Artisan Commands

# Scaffold a new custom field type
php artisan livewire-form-builder:make-field MyType

# Publish Eloquent repository stub
php artisan livewire-form-builder:publish-stubs

# Publish config
php artisan vendor:publish --tag=livewire-form-builder-config

# Publish views (to customise)
php artisan vendor:publish --tag=livewire-form-builder-views

License

MIT