Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Form Bundle Laravel Package

sulu/form-bundle

SuluFormBundle lets content managers create dynamic forms directly in Sulu Admin—such as contact or sweepstake forms—position fields in a grid, and configure emails sent to visitors or themselves. Built on the Symfony Form Component and theming features.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require sulu/form-bundle:^3.0
    

    Ensure your project meets the requirements (PHP 8.2+, Symfony 6.4 / 7.x / 8.0, Sulu 3.0+). As of 3.0.1 the bundle is compatible with Symfony 8 — if you are still on Symfony 6.4/7.x nothing changes for you, but you can now widen your symfony/* constraints and run composer update without a conflict.

  2. Enable the Bundle: Add to config/bundles.php:

    return [
        // ...
        Sulu\Form\SuluFormBundle::class => ['all' => true],
    ];
    
  3. Run Migrations:

    php bin/console doctrine:migrations:diff
    php bin/console doctrine:migrations:migrate
    
  4. Generate a Form:

    php bin/console sulu:form:generate
    

    This creates a basic form structure in the Sulu Admin UI under Forms.

Note: this is a Symfony/Sulu bundle, not a Laravel package. There is no service provider, no config/*.php array config, and no Blade directives. Everything below maps to Symfony concepts (bundles, YAML config, Twig, DI container).


First Use Case: Creating a Contact Form

  1. Admin UI Setup:

    • Navigate to Forms in Sulu Admin.
    • Click Create to add a new form.
    • Configure fields (e.g., Text, Email, Submit) in the drag-and-drop editor.
    • Set up email notifications under the Email tab.
  2. Frontend Integration: Embed the form in a Twig template:

    {{ render(controller('SuluFormBundle:Form:render', {
        'formId': 'your_form_id',
        'action': path('sulu_form_submit', {'formId': 'your_form_id'})
    })) }}
    
  3. Handle Submissions: Extend the form handler to process submissions:

    // config/services.yaml
    Sulu\Form\Handler\FormHandlerInterface: '@App\Service\CustomFormHandler'
    
  4. Success / Redirect Behaviour: In the Settings tab of the form you can pick a redirect target. Since 3.0.1 external redirect links are resolved through Sulu's link provider instead of being stored as a raw URL string. In practice: configure the target via the link picker (internal page, external URL, or any registered custom link provider) and the bundle resolves it to a URL at render/submit time. This means custom link providers you already registered for your website are now usable as form redirect targets for free.


Implementation Patterns

Dynamic Form Workflows

  1. Field Configuration:

    • Use the Dynamic Form Builder in Sulu Admin to define fields (e.g., Text, Checkbox, Media).
    • Configure validation rules (e.g., required, email) via the UI.
  2. Email Notifications:

    • Set up email templates in the Email tab of the form.
    • Use Twig variables like {{ formData.name }} to dynamically populate emails.
    • Example for Sendinblue integration:
      # config/packages/sulu_form.yaml
      sulu_form:
          sendinblue:
              enabled: true
              api_key: '%env(SENDINBLUE_API_KEY)%'
      
  3. Form Submission Handling:

    • Extend the default handler to customize logic:
      namespace App\Service;
      
      use Sulu\Form\Handler\FormHandlerInterface;
      use Sulu\Form\Handler\FormHandlerResult;
      use Sulu\Form\Handler\FormHandlerResultInterface;
      
      class CustomFormHandler implements FormHandlerInterface
      {
          public function handle(array $formData, string $formId): FormHandlerResultInterface
          {
              // Custom logic (e.g., save to database, trigger events)
              return new FormHandlerResult(true, ['message' => 'Form submitted!']);
          }
      }
      
    • Prefer decorating the built-in handler over replacing it, so notification mails and persistence still run:
      # config/services.yaml
      App\Service\CustomFormHandler:
          decorates: sulu_form.handler.default
          arguments: ['@.inner']
      
    • Note that from 3.0.1 the bundle's own service definitions are PHP files (services.php) instead of XML. Your project-level overrides keep working, but if you previously reloaded/patched the bundle's XML (e.g. via a compiler pass keyed on file paths or XmlFileLoader), re-check that logic.
  4. Frontend Rendering:

    • Use Twig extensions for dynamic rendering:
      {% form_theme form _self %}
      {{ form_row(form.field_name) }}
      
    • For AJAX submissions, use the sulu_form_submit route with a CSRF token:
      fetch('/form/submit', {
          method: 'POST',
          body: JSON.stringify({ formData, _csrf_token: '{{ csrf_token('sulu_form_submit') }}' }),
          headers: { 'Content-Type': 'application/json' }
      });
      

Integration with Sulu Ecosystem

  1. Media Fields:

    • Use the Media field type to attach files/images.
    • Configure allowed media types in the Sulu Admin under Settings > Media.
  2. Localization:

    • Forms support multi-language content. Use the Locale dropdown in the Admin UI to manage translations.
  3. Workflows:

    • Leverage Sulu's workflow system to publish/unpublish forms:
      # config/packages/sulu_workflow.yaml
      sulu_workflow:
          resources:
              Sulu\Form\Rest\Resource\FormResource: ~
      
  4. Link Providers for Redirects:

    • Since 3.0.1 redirect targets use the link provider system, so a custom provider (deep links into a shop, app scheme URLs, tracked campaign URLs) can be offered as a redirect target:
      // tag it as a Sulu link provider; it will appear in the link picker
      App\Link\CampaignLinkProvider:
          tags: [{ name: 'sulu.link.provider', alias: 'campaign' }]
      
  5. Testing:

    • Use the generate-form command to create test forms:
      php bin/console sulu:form:generate --name="Test Form" --locale="en"
      
    • Test submissions with PHPUnit:
      $client = static::createClient();
      $crawler = $client->request('GET', '/form/render');
      $form = $crawler->selectButton('Submit')->form([
          'formData[email]' => '[email protected]',
      ]);
      $client->submit($form);
      
    • Because the built-form cache is now cleared between requests, submitting several different forms in one test run / one kernel no longer leaks the previously built form. Prior to 3.0.1 this showed up as "wrong form rendered" in multi-form tests — remove any manual cache:clear workarounds.

Gotchas and Tips

Common Pitfalls

  1. CSRF Token Issues:

    • If using AJAX, ensure the CSRF token is included in the request.
    • Disable CSRF for specific forms (not recommended for production):
      # config/packages/sulu_form.yaml
      sulu_form:
          csrf_protection:
              disabled: true
      
  2. Field Validation:

    • Hidden fields (Hidden) and spacers (Spacer) ignore the required constraint by default.
    • Custom validation requires extending the form type or using Symfony's Constraints.
  3. Media Uploads:

    • Ensure the MediaManagerInterface is properly configured in your services.yaml:
      Sulu\Form\Handler\FormHandlerInterface:
          arguments:
              $mediaManager: '@sulu_media.media_manager'
      
  4. Locale Fallbacks:

    • Forms rely on Sulu's locale system. If translations are missing, the system falls back to the default locale.
    • Debug missing translations with:
      php bin/console debug:translation sulu_form
      
  5. Upgrading to 3.0.1 (patch, but read this):

    • Symfony 8 support: a $request->get() call was replaced with the proper $request->query / $request->attributes accessors. If you subclassed or copied the bundle's controller code, or have your own controller reading route params via get(), migrate now — Request::get() is deprecated and gone in Symfony 8.
    • Redirect targets: external redirect URLs are now resolved through the link provider. If you were reading the raw redirect URL column directly from the database or a custom REST consumer, the stored value may now be a link reference rather than a plain URL — resolve it via the link manager instead.
    • Service definitions are PHP now: don't reference the bundle's Resources/config/*.xml paths in your own imports or build tooling.
    • No database migration is required for 3.0.1.

Debugging Tips

  1. Form Submission Errors:

    • Check the FormHandlerResult for errors:
      $result = $formHandler->handle($formData, $formId);
      if (!$result->isSuccess()) {
          throw new \RuntimeException($result->getMessage());
      }
      
    • Enable debug mode in config/packages/dev/sulu_form.yaml:
      sulu_form:
          debug: true
      
  2. "Stale form" in long-running processes:

    • 3.0.1 resets the built-form cache between requests. If you run the app under a persistent runtime (Messenger worker rendering mails from submissions, RoadRunner/FrankenPHP worker mode, long-lived functional test kernels), make sure your runtime dispatches request/terminate events — otherwise the reset never fires and you can serve a form built from a previous request's data.
    • Symptom before the fix: the second form on a page (or the second form in a test) rendered the first form's fields.
  3. Database Issues:

    • Verify migrations are applied:
      php bin/console doctrine:migrations:status
      
    • Clear the cache after changes:
      php bin/console cache:clear
      
    • If a form renders but the redirect goes nowhere after upgrading, check the link provider resolution for that target rather than the form config:
      php bin/console debug:container --tag=sulu.link.provider
      
  4. Sendinblue Integration:

    • Validate API keys and pagination settings:
      sulu_form:
          sendinblue:
              pagination:
                  limit: 100
      
    • Test with the sulu_form.sendinblue.contact_created event:
      $eventDispatcher->addListener(
          'sulu_form.sendinblue.contact_created',
          function ($event) {
              // Log or debug the event data
          }
      );
      

Extension Points

  1. Custom Form Types:

    • Create a new form type by extending Sulu\Form\Type\AbstractType:
      namespace App\Form\Type;
      
      use Sulu\Form\Type\AbstractType;
      use Symfony\Component\Form\FormBuilderInterface;
      
      class CustomFieldType extends AbstractType
      {
          public function buildForm(FormBuilderInterface $builder, array $options)
          {
              $builder->add('custom_field', TextType::class);
          }
      
          public function getBlockPrefix(): string
          {
              return 'app_custom_field';
          }
      }
      
    • Register the type in config/packages/sulu_form.yaml:
      sulu_form:
          types:
              app_custom_field: App\Form\Type\CustomFieldType
      
  2. Event Listeners:

    • Listen to form submission events:
      namespace App\EventListener;
      
      use Sulu\Form\Event\FormSubmittedEvent;
      use Symfony\Component\EventDispatcher\EventSubscriberInterface;
      
      class FormSubmittedListener implements EventSubscriberInterface
      {
          public static function getSubscribedEvents(): array
          {
              return [
                  FormSubmittedEvent::NAME => 'onFormSubmitted',
              ];
          }
      
          public function onFormSubmitted(FormSubmittedEvent $event)
          {
              // Handle submission (e.g., log, notify)
          }
      }
      
  3. Custom Link Providers for Redirects (new in 3.0.1):

    • Implement a Sulu link provider and tag it; it becomes selectable as a form redirect target. This is the cleanest extension point when redirects must be generated dynamically (signed URLs, locale-aware external links, A/B targets).
  4. Twig Extensions:

    • Extend Twig rendering with custom filters:
      namespace App\Twig;
      
      use Twig\Extension\AbstractExtension;
      use Twig\TwigFilter;
      
      class FormExtension extends AbstractExtension
      {
          public function getFilters(): array
          {
              return [
                  new TwigFilter('format_form_data', [$this, 'formatData']),
              ];
          }
      
          public function formatData(array $data): string
          {
              return json_encode($data, JSON_PRETTY_PRINT);
          }
      }
      
    • Register in config/packages/twig.yaml:
      twig:
          extensions:
              - App\Twig\FormExtension
      
  5. Service Decoration over Replacement:

    • With the move to PHP service definitions in 3.0.1, the internal service ids are stable and safe to decorate. Prefer decorates: for handlers, mail helpers, and link resolution so bundle upgrades stay painless.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator