Skip to content

Next-gen: javaabu/form-builder v1.0 #18

Open

Description

@n4ff4h

Dynamic form builder: form definitions and entries stored in the database, builder and filling UIs in Blade and React, sections, repeating groups, wizards, cascading and conditional inputs, read-only rendering, import/export, extensible input types, optional AI assistant.

Package: javaabu/form-builder · Target: v1.0 · Wave: 4
Depends on: javaabu/stencil, javaabu/permissions, javaabu/forms, @javaabu/forms, javaabu/manifest
Optional: javaabu/translatable, laravel/ai

Built to the next-gen general rules: CI with test and coverage badges, full docs for people and agents, Laravel Boost guideline and skill, Javaabu codestyle, PHP 8.4 / Laravel 12+ (Laravel packages) or TypeScript (JS packages), Dhivehi and RTL, Blade and Inertia React for views, overridable controllers and policies with javaabu/permissions.

Checklist

Developed by an agent orchestrated from the Javaabu next-gen plan. Cross-package requests go in the dependency's repo with the cross-package label.

Activity

  1. added
    next-genNext-gen Javaabu packages
    epicTracks a whole package
    on Nov 4, 2026
  2. n4ff4h commented on Nov 5, 2026

    @n4ff4h
    CollaboratorAuthor

    Implementation plan

    Laravel package javaabu/form-builder, namespace Javaabu\FormBuilder\, structured like javaabu/widgets: PHP + TypeScript in resources/js shipped through javaabu/manifest, with a committed dist/ for the Blade scripts.

    Architecture

    • Definitions (Form definition storage #2). These tables:

      • forms: key, name, description, settings, status, published_version_id, soft deletes, plus translations/lang columns for optional translatable.
      • form_versions: number, published_at, author morph.
      • form_sections: a version's sections, steps and groups, in order, with conditions.
      • form_fields: key, type, label, help, settings, rules, conditions, position, and parent_id for fields inside a repeating group.

      A form has at most one draft version, which the builder edits. Publishing freezes it, and published versions are immutable (the model refuses updates). Editing a published form copies it into a new draft.

    • Entries (Form entry storage #3). Each form_entries row points at the form_version_id it was filled with. It holds:

      • submitter morph and subject morph (what the entry is about, e.g. a workflow step)
      • status, submitted_at, locale
      • data (the full answers)

      form_entry_values has one row per answer (field key, path such as references.0.name, typed value/number/date columns), so entries are queryable: FormEntry::whereAnswer('age', '>=', 18). CSV export comes from the version's columns.

    • Compiling to stencil. EntryForm extends Javaabu\Stencil\Form gets the version through with(['version' => $version]) and compiles it in definition(). That gives us:

      • Blade and React rendering
      • validation rules, visibleWhen/hiddenWhen conditions and live fields from stencil
      • a rebuildable form class, so stencil's live route works
    • Input types (Extensible input types #9). InputType classes in a registry (FormBuilder::inputTypes(), config('form-builder.input_types')). Each one has:

      • a key, label and icon
      • its builder settings (a stencil schema)
      • toStencil() (field → stencil component)
      • extra rules
      • storage cast (string/number/date/json/file)
      • display and export formatting

      Built-in types: text, textarea, email, number, phone, url, select, radio, checkboxes, checkbox, date, time, datetime, file, cascading select, and paragraph/heading (display only). Apps add types, or replace and extend built-ins (FormBuilder::extendInputType('text', fn …)). Custom React inputs use @javaabu/forms' custom-field hooks and are registered with StencilProvider.

    • Structure (Sections, repeating groups, wizards, cascading and conditional inputs #6).

      • Sections compile to stencil Section. In wizard mode each section is a Step.
      • Conditions on sections and fields: in, not_in, checked, not_checked, combined with AND. They compile to stencil's declarative conditions, so they work in the browser and on the server.
      • Cascading selects use @javaabu/forms' Select2 cascade (parent + ajaxUrl + filterField) against a package options endpoint, or any URL such as a javaabu/locations API. The server validates the pair.
      • Repeating groups are implemented here as a stencil field, Javaabu\FormBuilder\Stencil\Repeater. Its state is a list of rows, and it handles its own per-row rules, attributes, conditions and dehydration. It renders a template row plus the existing rows, with a Blade view and a small script, and a React component. I'll open a cross-package issue on stencil proposing to upstream it once it's settled.
    • Rendering entries (Filling forms and read-only rendering #5). A read-only rendering compiles the version into stencil TextEntry components formatted by input type. The same code serves the Blade and React pages and a printable page with print CSS.

    • Fluent definitions (Define forms fluently in seeders #7). Seeders define forms like this:

      FormBuilder::define('job-application')
          ->name(…)
          ->wizard()
          ->sections([...])
          ->sync();

      Section::make(), Field::text('name')->label()->required()->thaana(), Field::repeater('refs')->schema([...]), and so on. sync() is idempotent: it publishes a new version only when the structure changes. Forms can be locked (code-only), and form-builder.builder can turn the builder UI off.

    • Export/import (Export and import form structure #8). A versioned JSON format, {"form_builder": 1, "form": {…}, "sections": [...]}, validated on import. Importing an existing key makes a new draft. Available as builder actions and as the form-builder:export / form-builder:import commands.

    • HTTP (Overridable controllers, policies and default permissions (RBAC, multi-auth) #15, Form builder UI #4, Filling forms and read-only rendering #5, Blade (javaabu/forms) and Inertia React (@javaabu/forms) views #14).

      • Route::formBuilder([...]) serves the admin side: forms CRUD, the builder (sections and fields: add, configure, reorder, delete), preview, publish, versions, export/import, entries list and CSV, entry view/print, and the AI assistant.
      • Route::formBuilderFill([...]) serves filling (show, submit, thank-you). It can be public or behind any guard.
      • Controllers come from config. FormPolicy/FormEntryPolicy and the permissions view_forms, edit_forms, delete_forms, publish_forms, view_form_entries, delete_form_entries and export_form_entries are registered through javaabu/permissions.
      • form-builder.views chooses blade or inertia. In the Blade builder every action is a server round trip, with a script for keyboard-accessible reordering. The React builder uses the same endpoints.
    • Translatable (Optional javaabu/translatable support #16). Optional, feature-detected in Support\Translations. Form names/descriptions and field labels, help and options become locale maps, and the builder shows one input per active locale.

    • AI (AI assistant to build forms (optional) #10). Optional, with laravel/ai. An agent with structured output returns the export format. It goes through the importer's validation and is saved as a draft, either a new form or an edit of an existing one. Tests use laravel/ai's fakes.

    Public API sketch (for javaabu/workflows)

    $form = FormBuilder::form('job-application');           // Form model
    $version = $form->publishedVersion;                     // FormVersion
    $stencil = EntryForm::for($version)->action($url);       // a stencil Form: {{ $stencil }} or Inertia prop
    $entry = FormBuilder::submit($version, $request, submitter: $user, subject: $step); // validates, stores FormEntry
    $entry->answers();  $entry->answer('full_name');
    FormBuilder::display($entry);                            // read-only stencil Form (Blade/React)

    Order of work

    #1 foundations, then #2 with the input-type registry and compiler (#9 core), #3, #6, #7, #8, #15, #5, #4, #14, #16, #13, #10, #11, #12.

    Open questions (I'll decide and record as I go)

    • Whether the repeater should move to stencil: I'll propose it there, but build it here so nothing is blocked.
    • File answers: stored on a configurable disk/directory, not medialibrary, so entries need no extra package.
  3. n4ff4h commented on Nov 5, 2026

    @n4ff4h
    CollaboratorAuthor

    API for javaabu/workflows (definitions, entries, rendering)

    On main. Install with a VCS repository for Javaabu/form-builder and "javaabu/form-builder": "^1.0@dev". The full reference is docs/advanced-usage/php-api.md. Everything below is a public contract.

    Definitions and versions

    use Javaabu\FormBuilder\Facades\FormBuilder;
    
    $form = FormBuilder::form('leave-request');           // Models\Form by key (FormBuilderException if none)
    $version = $form->publishedVersion;                    // Models\FormVersion; immutable once published
    $definition = $version->definition();                  // Definitions\FormDefinition: getFields(), getField('key' | 'group.key')
    FormBuilder::define('leave-request')->sections([...])->sync();   // define/update in code (idempotent)

    A workflow step should store the form_version_id it was configured with, or always use the form's current publishedVersion. Versions never change once published.

    Rendering a step's form

    $stencil = FormBuilder::fill($version)->action($url)->method('POST');   // Stencil\EntryForm, a Javaabu\Stencil\Form
    $stencil = Stencil\EntryForm::for($version)->model($entry->answers()); // to edit a step's data
    • Blade: {{ $stencil }}.
    • Inertia: pass it as a prop and render <StencilForm schema={form} components={formBuilderComponents} /> (formBuilderComponents from @javaabu/form-builder, needed for repeating groups and headings).

    Storing step data

    $entry = FormBuilder::submit($version, $request, submitter: $user, subject: $stepInstance);  // validates; throws ValidationException
    app(Entries\EntryRecorder::class)->submit($stencil, $version, $input, $user, $stepInstance, status: FormEntry::DRAFT); // saved, not submitted
    app(Entries\EntryRecorder::class)->update($entry, $input);   // re-validated with its own version

    Events Events\EntrySubmitted and Events\EntryUpdated carry $event->entry.

    Reading and showing it

    $entry->answers(); $entry->answer('days'); $entry->formatted('leave_type');
    Models\FormEntry::forSubject($stepInstance)->latest()->first();
    Models\FormEntry::whereAnswer('days', '>', 3)->submitted()->get();
    FormBuilder::display($entry);     // Stencil\DisplayForm: read-only, embedded; {{ }} or <StencilFields schema={display} components={formBuilderComponents} />

    Embedding fields in a bigger form

    app(Compiler\Compiler::class)->compile(new Compiler\CompileContext($definition));

    This returns stencil components whose field names are the field keys.

  4. n4ff4h commented on Nov 5, 2026

    @n4ff4h
    CollaboratorAuthor

    Final summary

    javaabu/form-builder v1.0 is on main and CI is green:

    • the full matrix: PHP 8.4/8.5 × Laravel 12/13 × lowest/stable
    • Pint
    • a leg without Inertia, translatable and laravel/ai
    • the JS job: lint, format, typecheck, Vitest with coverage, the examples build and the dist freshness check

    That's 112 PHPUnit tests and 31 Vitest tests. Every next-gen issue is closed except #17 (needs a human).

    What was built

    • Definitions. These are stored in forms, form_versions, form_sections and form_fields.

      • Each form has drafts and immutable published versions, and entries keep the version they were filled with.
      • Fluent definitions (FormBuilder::define()->…->sync()) are idempotent and can be locked to code. form-builder.builder turns the builder off.
    • 17 input types in an extensible registry: registerInputType(), extendInputType(), forgetInputType(). One test type has its own stencil field and a React component built with @javaabu/forms' custom-field hooks.

    • Compiled to stencil (EntryForm, DisplayForm):

    • Entries. These are stored in form_entries and form_entry_values.

      • FormBuilder::submit() validates and stores, and EntryRecorder and its events are there for other packages.
      • whereAnswer() queries indexed answers.
      • Entries can be shown read-only or printed, exported to CSV, and their files downloaded.
    • Builder pages (Route::formBuilder()):

      • the forms list, creating forms and their settings
      • the builder itself: add, configure, move and delete sections and fields, and reorder them by drag and drop or the keyboard (announced to screen readers)
      • preview, publish, discard draft, versions, import and export, entries

      Fill pages come from Route::formBuilderFill(). Every page is in Blade and in Inertia React, and every route also answers JSON.

    • Controllers and permissions.

      • Controllers are overridable and have hooks, and routes take guard, prefix, name and middleware.
      • FormPolicy and FormEntryPolicy check seven permissions through javaabu/permissions, for any guard or user model.
    • JSON export and import (format 1), validated, from the builder or form-builder:export / form-builder:import.

    • Optional packages.

      • javaabu/translatable: form names in every language, and per-locale inputs throughout the builder.
      • laravel/ai: an assistant that builds or changes a draft from a description. Its answer is validated, it gets one retry, and the tests use laravel/ai's fakes.
    • Dhivehi and RTL. Every string is translated into Dhivehi, Thaana inputs work, and the CSS uses logical properties only.

    • Docs and tooling. llms.txt, the Boost guideline and skill, runnable examples, and AGENTS.md.

    • For workflows. The API is in the comment above.

    Intentionally out of scope for v1

    • Nesting. Repeating groups can't be nested.
    • Live fields in repeater rows. Stencil live fields (server re-evaluation) aren't supported inside repeater rows. Conditions and cascading selects work there.
    • Cascading options from your own URL (options_url) can't be checked against a list on the server. The docs say so and show how to add a rule.
    • Save-and-resume and editing entries. There's no UI for people to save and resume, or for admins to edit an entry's answers. The API supports both (FormEntry::DRAFT, EntryRecorder::update()).
    • The builder is page-based. Each section or field is edited on its own page, rather than in an all-in-one canvas. PUT forms/{form}/structure is there for a richer client later.
    • The print page is Blade in both stacks, as a standalone printable document.
    • Notifications on submission are left to apps, through EntrySubmitted or FillController::submitted().

    For the user

    • Codecov. Uploads run but are rejected with "Token required". Make CODECOV_TOKEN available to this repo; the upload is set to fail_ci_if_error: false.
    • Docs registration. Register with docs.javaabu.com (Register with Javaabu Docs (docs.javaabu.com) #17).
    • Dhivehi review. The lines in docs/advanced-usage/dhivehi-and-rtl.md#for-review need a Dhivehi speaker.
    • Release. Tag and submit to Packagist. Once stencil, forms, manifest, permissions and translatable v3 are released, drop the VCS repositories in composer.json. Once @javaabu/forms is on npm, change its npm dependency in Assets and package.json. pnpm is pinned to 10.28 for the Git-hosted @javaabu/forms build, as in the other packages.
    • Not checked in a real browser. The React pages and Blade scripts are tested with Vitest/jsdom, and the examples build (pnpm examples), but I haven't looked at the styling in a real browser.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicTracks a whole packagenext-genNext-gen Javaabu packages

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions