Support Wire Drupal

Fund development, features & content

Sponsor

File Uploads

Handle file uploads in components

Basics

To upload a file with WireDrupal, add the \Drupal\wire\WithFileUploads trait to your component.

As with any other input field types, you can use wire:model on file inputs.

Here's an example of a component that handles uploading an image:

use Drupal\wire\Plugin\Attribute\WireComponent;
use Drupal\wire\WireComponentBase;
use Drupal\wire\View;
use Drupal\wire\TemporaryUploadedFile;
use Drupal\wire\WithFileUploads;
use Drupal\file\Entity\File;


#[WireComponent(id: 'upload_image')]
class UploadImage extends WireComponentBase {

  use WithFileUploads;
  
  public $image;
  
  public function saveImage(): void {
  
    $tempFile = $this->image ?? NULL;
    if (!$tempFile instanceof TemporaryUploadedFile) {
      return;
    }
    
    // Store file into temporary directory.
    if (!$filepath = $tempFile->store()) {
      return;
    }
    
    // Create File entity.
    try {
    
      $file = File::create([
        'filename' => basename($filepath),
        'uri' => $filepath,
        'status' => 1,
        'uid' => \Drupal::currentUser()->id(),
      ]);
      $file->save();
      
    } catch (\Exception $e) {
      watchdog_exception('wire_save_file', $e);
    }
    
  }
  
  public function render(): ?View {
    return View::fromTpl('upload_image');
  }
  
}

And the TWIG associated with above:

<div>

  <input
    wire:model="image"
    type="file"
    accept="image/*"
    id="image"
  >
  
  <button wire:click="saveImage">Save Image</button>
  
</div>

When an image is chosen, the wire:model directive will store the file in your temporary directory while triggering saveImage action will attempt to create a Drupal \Drupal\file\Entity\File instance.

File Upload Preview

It is possible to preview the file before it is being saved to Drupal's File System(from Temp directory).

To achieve this, the temporaryUrl method is available as part of Wire's TemporaryUploadedFile object which will retrieve the path to the temporarily stored file.

However, due to TwigSandboxPolicy, in order to use the method you need to register the method as allowed in your settings.php file as following:

$settings['twig_sandbox_allowed_classes'] = [
  '\Drupal\wire\TemporaryUploadedFile',
];

After witch you can use the method as following:

<div>

  <input
    wire:model="image"
    type="file"
    accept="image/*"
    id="image"
  >
  
  {% if image %}
  
    <img src="{{ image.temporaryUrl() }}" style="width: 100px">
    
  {% endif %}
  
  <button wire:click="saveImage">Save Image</button>
  
</div>

File Upload Remove

It is possible to remove the file from temporary directory.

To achieve this, add a new action into your template:

{% if image %}

    <button wire:click="removeImage">Remove Image</button>

{% endif %}

And the called method itself:

public function removeImage(): void {

    $this->removeUpload('image', $this->image->getFilename());
    
}

Multiple Files

Multiple file uploads are handled automatically by detecting the multiple attribute on the tag. E.g:

<input
  wire:model="image"
  type="file"
  accept="image/*"
  id="image"
  multiple
>

*Make sure your property is defined as array in the PHP component.

Loading Indicators

You can display a loading indicator scoped to the file upload like so:

<div wire:loading wire:target="image">Uploading...</div>

Now, while the file is uploading the "Uploading..." message will be shown and then hidden when the upload is finished.

This works with any other Loading States API.

JavaScript Events

On every file upload JavaScript events are dispatched on the <input> element for custom JavaScript to listen to.

Here are the dispatched events:

Event Description
wire-upload-start Dispatched when the upload starts
wire-upload-finish Dispatches if the upload is successfully finished
wire-upload-error Dispatches if the upload fails in some way
wire-upload-progress Dispatches an event containing the upload progress percentage as the upload progresses

Above are useful for showing for example the progress bar.

Here is an example implementation with AlpineJS:

<div
  x-data="{ isUploading: false, progress: 0 }"
  x-on:wire-upload-start="isUploading = true"
  x-on:wire-upload-finish="isUploading = false"
  x-on:wire-upload-error="isUploading = false"
  x-on:wire-upload-progress="progress = $event.detail.progress"
>

  <input
    wire:model="image"
    type="file"
    accept="image/*"
    id="image"
  >

  {# Progress Bar #}
  <div x-show="isUploading">

    <progress max="100" x-bind:value="progress"></progress>

  </div>

  {% if image %}

    <img src="{{ image.temporaryUrl() }}" style="width: 100px">

    <button wire:click="removeImage">Remove Image</button>

  {% endif %}

  <button wire:click="saveImage">Save Image</button>

</div>

JavaScript upload API

To integrate with file-uploading libraries or have a more granular control over the behavior, Wire exposes dedicated JavaScript functions.

  let file = document.querySelector('input[type="file"]').files[0];

  // Upload a file.
  @this.upload('myFile', file, (uploadedFilename) => {
    // Success callback.
  }, () => {
    // Error callback.
  }, (event) => {
    // Progress callback.
  })

  // Upload multiple files.
  @this.uploadMultiple('myFiles', [file], successCallback, errorCallback, progressCallback);

  // Remove single file from multiple uploaded files.
  @this.removeUpload('myFiles', uploadedFilename, successCallback)

Note: the @this directive compiles to the following string for JavaScript to interpret: Wire.find([component-id])

See A complete Drag and Drop file uploader with progressbar using Wire Drupal, Alpinejs and tailwindcss for a complete example.