neophp / formbuilder-package
Dev-only drag-and-drop form builder UI generating FormFactory code from entity metadata
Package info
github.com/NeoPHP-Dev/neo-formbuilder-package
Language:CSS
pkg:composer/neophp/formbuilder-package
Requires
- php: >=8.5
README
A dev-only, drag-and-drop UI for building a NeoPHP Form from an
existing entity's ORM metadata. Drag fields onto a canvas, reorder them,
tweak which ones are required, click Generate code, and copy the
resulting FormFactory snippet into your controller. Nothing is written
to disk automatically — this is a code generator you paste from, not a
live editor.
The UI is a reusable Twig component: use it at its own standalone route,
or embed it in any page of your project — including a neo-admin-package
page — via a single macro call.
Structure
formbuilder-package/
├── composer.json
├── README.md
└── src/
├── NeoFormBuilderPackage.php
├── Controllers/
│ └── FormBuilderController.php
├── Middleware/
│ └── DevOnlyMiddleware.php
├── Service/
│ ├── EntityScanner.php
│ ├── EntityFieldExtractor.php
│ └── FormCodeGenerator.php
├── Assets/
│ ├── css/builder.css
│ └── js/builder.js
└── Templates/
├── components/
│ └── FormBuilder.macro.html.twig
└── pages/
└── builder.html.twig
Dev-only, by design
Every route this package exposes is protected by DevOnlyMiddleware,
which checks Config/app.config.php's environment key. Outside
environment: dev, every route returns a blocked response. This tool
generates code from your entity structure; it has no reason to ever be
reachable on a live site.
Installation
php bin/neo package:require neophp/formbuilder-package --project=MyProject
Register it in the project's Config/app.config.php:
return [ // ... 'packages' => [ \Vendor\NeoPHP\FormBuilderPackage\NeoFormBuilderPackage::class, ], ];
No configuration file — there is nothing to customize per project.
Using the standalone page
Visit /_formbuilder/ (only reachable in dev). Pick an entity, drag
fields onto the canvas, click Generate code, copy the result from the
modal that opens.
Embedding the builder in your own page
The entire UI is a single reusable Twig macro,
@FormBuilder/components/FormBuilder.macro.html.twig. Embed it in any
page of your project — a custom admin page, a dedicated dev tools
section, anywhere — without duplicating any markup, CSS, or JS.
1. Provide the list of entities from your own controller
<?php declare(strict_types=1); namespace Neo\Src\MyProject\App\Controllers\NeoAdmin; use Neo\Core\Controller\AbstractController; use Neo\Core\Http\Response\Types\Response; use Neo\Core\Routing\Attribute\MainRoute; use Neo\Core\Routing\Attribute\Route; use Neo\Core\Security\Middleware\Attribute\Middleware; use Vendor\NeoPHP\AdminPackage\Middleware\AdminAuthMiddleware; use Vendor\NeoPHP\FormBuilderPackage\Service\EntityScanner; #[MainRoute(path: '/admin/formbuilder', name: 'admin.formbuilder')] #[Middleware( use: AdminAuthMiddleware::class, onError: 'block', params: ['requiredRole' => 'ROLE_ADMIN'], )] final class FormBuilderPageController extends AbstractController { #[Route(path: '/', name: 'index', methods: ['GET'])] public function index(EntityScanner $scanner): Response { return $this->render('pages/admin/formbuilder.html.twig', [ 'entities' => $scanner->scan(), ]); } }
EntityScanner is the same service the standalone page uses — it lists
every entity under the project's Database/Entity/ and any installed
package's database/Entity/. You don't need to build your own scanning
logic.
2. Import the macro and render it in your template
{# src/MyProject/Templates/pages/admin/formbuilder.html.twig #} {% extends '@NeoAdmin/layouts/admin_layout.html.twig' %} {% import '@FormBuilder/components/FormBuilder.macro.html.twig' as FormBuilder %} {% block stylesheets %} <link rel="stylesheet" href="/packages-assets/FormBuilder/css/builder.css"> {% endblock %} {% block content %} {{ FormBuilder.render(entities) }} {% endblock %} {% block javascripts %} <script src="/packages-assets/FormBuilder/js/builder.js"></script> {% endblock %}
That's the whole integration — three includes (macro import, CSS link,
JS script) plus one line to render it. This works the same whether the
host page extends neo-admin-package's layout, your own project layout,
or no layout at all.
3. FormBuilder.render(entities, instanceId)
| Parameter | Default | Purpose |
|---|---|---|
entities |
(required) | List of entity class-strings, from EntityScanner::scan() |
instanceId |
'fb' |
Prefix for internal element IDs — change it if you ever render the component more than once on the same page, to avoid ID collisions |
{{ FormBuilder.render(entities, 'my-custom-id') }}
4. Registering it in the admin sidebar (optional)
If embedding it inside neo-admin-package, add it to
admin-system.config.php like any other page:
'sidebar' => [ // ... 'formbuilder' => [ 'controller' => \Neo\Src\MyProject\App\Controllers\NeoAdmin\FormBuilderPageController::class, 'icon' => 'layout-dashboard', 'title' => 'Form Builder', ], ],
Theming
The component never hardcodes colors — every visual value is a CSS
custom property scoped to .fb-layout, with a light theme applied by
default. Override any of them on an ancestor element (or directly on the
component's root, via its id="{instanceId}-root") to match your page:
#my-custom-id-root { --fb-bg: #161923; --fb-bg-alt: #1b2030; --fb-bg-raised: #1e2436; --fb-border: #2d3342; --fb-text: #e5e7eb; --fb-text-muted: #9ca3af; --fb-text-faint: #4b5563; --fb-accent: #818cf8; --fb-accent-hover: #6366f1; --fb-accent-soft: rgba(129, 140, 248, 0.15); --fb-danger: #f87171; }
Only the accent color usually needs overriding to match a host page — as
done in the neo-admin-package example above (--fb-accent set to the
admin panel's own indigo).
How fields are discovered
EntityFieldExtractor reads the entity's ClassMetaData
(fieldMappings), the same metadata NeoPHP's ORM uses internally — no
separate configuration or annotation is needed beyond the entity's
existing #[Column] attributes. The identifier field (#[Id]) is
always excluded from the available field list.
ORM types are mapped to form field types as follows:
| ORM type | Form type |
|---|---|
integer, float |
number |
boolean |
checkbox |
datetime |
datetime |
text, json |
textarea |
| everything else | text |
Relations (#[ManyToOne], etc.) are not currently supported — only
scalar column fields are listed. Enums are treated as plain text, not
as a select with the enum's cases — add relation/enum handling to the
generated code by hand.
What this package does not do
- It never writes files — the code is generated in-browser and copied manually, on purpose, so nothing changes on disk without you reviewing it first.
- It does not persist your form layout between sessions — reload the page and you start from an empty canvas.
- It does not validate the generated code compiles — always review it before pasting into a real controller.
License
MIT