File Uploads

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.

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>

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 file uploads are handled automatically by detecting the multiple attribute on the <input> 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.

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.

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

Here are the dispatched events:

EventDescription
wire-upload-startDispatched when the upload starts
wire-upload-finishDispatches if the upload is successfully finished
wire-upload-errorDispatches if the upload fails in some way
wire-upload-progressDispatches 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>

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

<script>

    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)

</script>

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.

********************************** ************************* ************************ **************** ****************** *********** ************** ************* ************ *************