jooservices/form-builder

Standalone PHP 8.5+ schema-driven form builder with DTO-backed form definitions, HTML rendering, validation, normalization, and repeater support.

Maintainers

Package info

github.com/jooservices/form-builder

pkg:composer/jooservices/form-builder

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-05-18 14:14 UTC

This package is auto-updated.

Last update: 2026-05-18 14:16:29 UTC


README

CI PHP Version License Packagist Version Codecov OpenSSF Scorecard

jooservices/form-builder is a standalone PHP 8.5+ schema-driven form builder for JSON-defined forms. It parses form schemas, validates schema shape, renders semantic HTML, returns renderless config arrays, exposes asset manifests or safe asset tags, validates submitted values, normalizes submitted values, and supports repeaters, rich-text editor metadata, media input metadata, and generic file input metadata without coupling to Laravel, WordPress, Joomla, Symfony upload objects, PSR-7 uploads, or storage frameworks.

Install

composer require jooservices/form-builder

Quick Start

use JOOservices\FormBuilder\FormBuilder;

$builder = FormBuilder::fromJson($json);

$schema = $builder->validateSchema();
$html = $builder->renderHtml([
    'values' => $existingValues,
    'errors' => $errors,
]);

$config = $builder->toConfigArray();

$assets = $builder->assets();
$assetTags = $builder->renderAssetTags();

$submission = $builder->validateSubmission($input);

if ($submission->passes()) {
    $values = $submission->normalizedValues();
}

JSON Schema Example

{
  "key": "default_story_form",
  "name": "Default Story Form",
  "version": "1.0.0",
  "method": "post",
  "fields": [
    {
      "key": "title",
      "type": "text",
      "label": "Title",
      "required": true,
      "placeholder": "Story title",
      "maxlength": 255
    },
    {
      "key": "intro",
      "type": "editor",
      "label": "Intro",
      "required": true,
      "html": true,
      "editor": {
        "provider": "tinymce",
        "profile": "basic_html"
      }
    },
    {
      "key": "chapters",
      "type": "repeater",
      "label": "Chapters",
      "required": true,
      "min_items": 1,
      "default_items": 1,
      "fields": [
        {
          "key": "title",
          "type": "text",
          "label": "Chapter Title",
          "required": true
        },
        {
          "key": "media",
          "type": "media",
          "label": "Chapter Media",
          "accept": ["image/*"],
          "drag_drop": true,
          "preview": true
        },
        {
          "key": "attachment",
          "type": "file",
          "label": "Chapter Attachment",
          "accept": [".pdf", ".docx", "application/pdf"],
          "max_files": 1,
          "max_size_mb": 20
        },
        {
          "key": "content",
          "type": "editor",
          "label": "Chapter Content",
          "required": true,
          "html": true
        }
      ]
    }
  ]
}

Render HTML

Editor fields render as safe <textarea> fallbacks with data-form-editor, data-form-editor-profile, and data-form-editor-config. Media fields render as standard <input type="file"> controls with optional drag/drop and preview containers driven by data attributes. File fields render as standard <input type="file"> controls with data-form-file, data-form-file-accept, data-form-file-max-files, and data-form-file-max-size-mb metadata, including repeater-safe names such as chapters[0][documents][].

Render Config

toConfigArray() returns a renderless structure for Blade, Vue, React, CLI previews, or custom UI adapters. Repeater items, editor metadata, media metadata, and file metadata are preserved in the config output.

Assets

Use the asset facade when the consuming application needs standalone CSS/JS metadata without framework coupling:

$builder = FormBuilder::fromJson($json);

$manifest = $builder->assets([
  'tinymce_url' => 'https://cdn.tiny.cloud/1/no-api-key/tinymce/7/tinymce.min.js',
]);

echo $builder->renderAssetTags();

The manifest exposes css, js, inline_css, inline_js, features, and providers. TinyMCE is opt-in by schema usage and can be disabled or overridden:

$manifest = $builder->assets([
  'load_tinymce' => false,
  'package_asset_mode' => 'url',
  'package_asset_base_url' => 'https://static.example.com/form-builder',
]);

If package_asset_mode is set to url without a usable package_asset_base_url, package helper assets are omitted instead of silently falling back to inline output.

Use renderAssetTags() for simple PHP apps. In WordPress Management or any host application, prefer reading the manifest and enqueueing or serving those assets in the application layer.

File Versus Media

  • media is for media-picker style UX such as image, video, preview, or dropzone flows.
  • file is for generic upload inputs such as PDF, DOCX, CSV, or ZIP.
  • Both stay at schema, HTML, config, validation, and normalization level only.
  • Neither field moves, stores, or validates uploaded bytes.

Validate Submission

Submission validation returns structured ValidationErrorData objects with path, message, code, and context. Repeater paths follow dot notation such as chapters.0.title. File and media validation accept metadata-style values, scalar references, or lists according to field shape, but they do not depend on framework upload classes.

Repeater Behavior

  • Repeater item names render as chapters[0][title] and chapters[0][media][] for multiple media.
  • Default repeater items render when no runtime values exist.
  • Submitted order is preserved.

Custom Fields

Register a custom field adapter when you need application-specific field behavior:

$builder->registerFieldAdapter($adapter);

Or use the convenience registration API when you already have custom renderer, validator, and normalizer objects:

$builder->registerFieldType('media_picker', $renderer, $validator, $normalizer);

Security Notes

  • Labels, placeholders, text values, and attributes are escaped.
  • Event handler attributes like onclick are filtered from rendered attributes.
  • Editor content is preserved safely inside <textarea> output.
  • This package does not sanitize rich HTML content by default.
  • This package does not inspect file contents, read upload bytes, or store uploaded files.
  • Consuming applications must validate real uploads, MIME policy, file size, and storage server-side.
  • Asset tags escape URLs and attributes, but host applications still control CSP, CDN policy, and static file serving.

What This Package Does Not Do

  • It does not save posts, pages, or database records.
  • It does not perform WordPress, Joomla, or Laravel integration.
  • It does not bundle TinyMCE, CKEditor, TipTap, Quill, or uploader libraries into PHP source.
  • It does not move or store uploaded files.
  • It does not call wp_enqueue_script, service providers, route registries, or framework lifecycle hooks.
  • It does not provide AI content generation.

WordPress Management Guidance

  • Store schemas in WordPress Management configuration or database layers.
  • Use FormBuilder to validate schema, render HTML or config, and validate or normalize submitted values.
  • Read assets() and enqueue or serve the returned manifest through WordPress Management, not inside this package.
  • Handle actual upload transport, storage, attachment creation, and security policy inside WordPress Management.

Docs And AI Guidance