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

Symfony Dropzone Laravel Package

ethsam/symfony-dropzone

Symfony Dropzone Bundle integrates Dropzone.js drag-and-drop uploads into Symfony Forms, with automatic Doctrine entity relationship handling, pre-populated edit forms, image resizing, and configurable single or multiple file modes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require ethsam/symfony-dropzone

With Symfony Flex the bundle registers itself in config/bundles.php. If Flex doesn't pick it up (likely, given the package has no recipe), add it manually:

// config/bundles.php
Ethsam\SymfonyDropzone\SymfonyDropzoneBundle::class => ['all' => true],

Prerequisites to confirm before writing any code

  • PHP ≥ 8.1, Symfony 5.4/6.x/7.x, Doctrine ORM 2.12+
  • symfony/doctrine-bridge present (single-file mode builds on EntityType)
  • Dropzone.js assets installed yourself — the bundle wires the form field, not the JS/CSS bundle. Use npm i dropzone + AssetMapper/Encore/Webpack, and include dropzone.min.css / dropzone.min.js in your base layout.

Where to look first (in this order)

  1. README.md → docs/README.fr.md / .es.md — the translated docs are often more complete than the English one in small packages; diff them.
  2. src/Form/Type/ — the DropzoneType (or similarly named) class. Its configureOptions() is the real API documentation; option names in the README may drift from the code.
  3. src/Form/DataTransformer/ — how IDs are hydrated into entities. This dictates what your upload endpoint must return.
  4. Resources/views/ or templates/ — the Twig block that initialises Dropzone and defines the URL/param names the JS posts.
  5. CHANGELOG.md + tags — 3 stars and 0 dependents means you are the QA department; pin an exact version.

First use case: attach images to a Product

Assume an Image entity with ManyToOne $product and a Product with OneToMany $images (cascade: ['persist'], orphanRemoval: true).

// src/Form/ProductType.php
$builder
    ->add('name', TextType::class)
    ->add('images', DropzoneType::class, [
        'multiple'        => true,
        'upload_handler'  => 'app_image_upload',  // route name
        'remove_handler'  => 'app_image_remove',  // route name
        'dropzone_options' => [
            'acceptedFiles' => 'image/*',
            'maxFilesize'   => 5,
        ],
    ]);
// src/Controller/ImageUploadController.php
#[Route('/images/upload', name: 'app_image_upload', methods: ['POST'])]
public function upload(Request $request, EntityManagerInterface $em): JsonResponse
{
    $file = $request->files->get('file'); // verify the expected key in the Twig template
    // ... persist, move to storage
    return $this->json(['id' => $image->getId(), 'name' => $image->getFilename()]);
}

Render normally ({{ form(form) }}); the bundle injects the Dropzone markup. The key check for day one: upload a file, submit the form, and confirm Doctrine actually links the new Image to the Product without you touching the collection.

Implementation Patterns

Single-file avatar with EntityType semantics

Single mode reuses the Doctrine bridge's EntityType, so the stored value behaves like a normal entity choice: your controller receives a hydrated Avatar/Media object, not an ID. Keep the inverse side of the relation mapped, otherwise the post-submit flush silently drops the association.

Multi-file collections with a PRE_SUBMIT guard

Because IDs arrive from JavaScript, validate them defensively — a crafted POST can reference any entity ID. Add a PRE_SUBMIT listener that filters submitted IDs against the owning user:

$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($repo, $user) {
    $ids = (array) ($event->getData()['images'] ?? []);
    $allowed = $repo->findOwnedIds($user, $ids);      // whitelist
    $data = $event->getData();
    $data['images'] = array_values(array_intersect($ids, $allowed));
    $event->setData($data);
});

Edit mode prepopulation

The advertised "pre-populated edit forms" reads existing files from the mapped collection on render. It only works if your template exposes a URL per file. Confirm your entity has a stable public path (a getWebPath() or a route), and that the bundle's Twig block uses it — otherwise supply a custom option or override the template.

Separating upload endpoints from the form controller

Treat the *_handler routes as a small JSON API:

  • POST /images/upload → returns at minimum an identifier the data transformer can resolve (id), plus a display name.
  • DELETE/POST /images/{id}/remove → 204/200, and idempotent.
  • Guard both with #[IsGranted] and the same firewall as the form; they are not protected by the form's CSRF token automatically.

Custom auth headers for token-based frontends

Use the header option when the form is served from a decoupled frontend or behind an API gateway:

'dropzone_options' => [
    'headers' => ['Authorization' => 'Bearer '.$token], // pass a value, not a closure
],

Keep the token out of the Twig global context if possible — inject it via a small inline data- attribute read by your own initialiser.

Reusable factory for the option array

Centralise acceptedFiles, maxFilesize, and resize settings in a service so that upload validation (server side) and client hints never diverge:

final class DropzoneDefaults
{
    public const IMAGE = ['acceptedFiles' => 'image/*', 'maxFilesize' => 5, 'resizeWidth' => 1600];
}

Testing

Use WebTestCase and simulate Dropzone's XHR instead of the form submit: $client->request('POST', '/images/upload', [], ['file' => $uploadedFile]), assert JSON, then submit the form with the returned IDs. This catches the transformer mismatches that plain form tests miss.

Gotchas and Tips

The JSON contract is the whole ballgame. The data transformer expects specific keys (typically id, sometimes name/path). Mismatch produces an empty collection with no error — no exception, no log line. Verify by dumping the PRE_SUBMIT data and the transformer's reverseTransform() input.

CSRF on the AJAX routes. Dropzone posts outside your form, so Symfony's form CSRF token does not apply. Either disable CSRF for these routes deliberately and rely on the firewall, or pass a token in dropzone_options['params'] and validate it with CsrfTokenManagerInterface.

Orphan removal vs. manual deletion. With orphanRemoval: true, removing the ID from the submitted array deletes the file row on flush — but not the bytes on disk. Add a Doctrine listener or service that unlinks the physical file. Without orphanRemoval, removed IDs simply unlink, leaving orphans.

Asset duplication / double initialisation. If you use Turbo, Hotwire, or a SPA router, Dropzone instances leak across page transitions. Ensure the bundle's initialiser is guarded (Dropzone.instances.forEach(d => d.destroy()) on teardown) or initialise manually via the data attributes the Twig template emits.

Upload size limits live outside Symfony. Three places bite you in order: php.ini (upload_max_filesize, post_max_size), the web server (client_max_body_size in nginx), and — if you use it — a proxy. A 413 with an empty response body is nearly always nginx, not the bundle.

Small-package risk management. 3 stars, 0 dependents, latest release tagged 2026 means: pin the exact version in composer.json, commit composer.lock, and read the source of DropzoneType and the transformer before relying on them — you will likely need to vendor-fork within a year. Check the released tag against CHANGELOG.md for silent BC breaks in option names; the opportunity score suggests the package is under-maintained relative to its surface area.

Where it doesn't apply. This is a Symfony Form + Doctrine bundle. In a Laravel codebase the equivalent stack is Livewire/Filament uploads, or FilePond + a HasMany relation with a custom validation rule — don't try to bridge this bundle into Laravel via a form package. If you are evaluating it for a mixed Symfony/Laravel estate, treat it as Symfony-only and share only the JS-side Dropzone configuration.

Extension points worth knowing. Override the form theme block ({% block dropzone_widget %}) to change markup without forking PHP; decorate the data transformer service in services.yaml to alter ID resolution; and pass arbitrary keys through dropzone_options — anything valid for new Dropzone(el, opts) is forwarded, so you rarely need to modify the bundle itself.

Debugging checklist, in order: browser Network tab (request payload key names, response JSON shape) → bin/console debug:router to confirm the handler route names resolve → bin/console debug:config for bundle config defaults → dump() inside reverseTransform(). Nine times out of ten the failure is a key name or a route-name typo, not the ORM mapping.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource