Skip to main content

Overview

This guide walks you through implementing your first multipart upload using S3M. You’ll learn how to upload files directly from the browser to S3 and handle the response in your Laravel backend.
Make sure you’ve completed the Installation steps before proceeding.

Step 1: Set Up Authorization

Before users can upload files, you need to define who has permission to upload.
1

Create User Policy

Generate a policy for your User model:
php artisan make:policy UserPolicy --model=User
2

Add Upload Permission

Open app/Policies/UserPolicy.php and add the uploadFiles method:
app/Policies/UserPolicy.php
<?php

namespace App\Policies;

use App\Models\User;

class UserPolicy
{
    /**
     * Determine whether the user can upload files.
     *
     * @param  \App\Models\User  $user
     * @return mixed
     */
    public function uploadFiles(User $user)
    {
        return true; // Customize based on your needs
    }
}
Customize the authorization logic based on your requirements. For example, you might check user roles, subscription status, or storage quotas.

Step 2: Add Blade Directive

Ensure the @s3m directive is in your layout file before your application’s JavaScript:
resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>
<head>
    <title>My App</title>
</head>
<body>
    <div id="app">
        @yield('content')
    </div>

    @s3m
    <script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

Step 3: Create Upload Frontend

Implement the file upload interface in your frontend JavaScript:
resources/js/components/FileUpload.vue
<template>
  <div>
    <input 
      type="file" 
      ref="fileInput" 
      @change="uploadFile"
    />
    
    <div v-if="uploading">
      <p>Upload Progress: {{ uploadProgress }}%</p>
      <progress :value="uploadProgress" max="100"></progress>
    </div>
    
    <div v-if="uploadComplete">
      <p>Upload complete!</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import axios from 'axios'

const fileInput = ref(null)
const uploadProgress = ref(0)
const uploading = ref(false)
const uploadComplete = ref(false)

const uploadFile = (e) => {
  const file = e.target.files[0]
  
  if (!file) return
  
  uploading.value = true
  uploadComplete.value = false

  s3m(file, {
    progress: progress => {
      uploadProgress.value = progress
    }
  }).then((response) => {
    // Send file metadata to your backend
    axios.post('/api/files', {
      uuid: response.uuid,
      key: response.key,
      bucket: response.bucket,
      name: file.name,
      content_type: file.type,
    }).then(() => {
      uploading.value = false
      uploadComplete.value = true
    })
  }).catch((error) => {
    console.error('Upload failed:', error)
    uploading.value = false
  })
}
</script>

Step 4: Handle Upload Response

Create a backend endpoint to receive the upload metadata and move the file from temporary to permanent storage:
app/Http/Controllers/FileController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class FileController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'uuid' => 'required|string',
            'key' => 'required|string',
            'bucket' => 'required|string',
            'name' => 'required|string',
            'content_type' => 'required|string',
        ]);

        // Move file from tmp/ to permanent location
        $permanentKey = str_replace('tmp/', 'files/', $validated['key']);
        
        Storage::copy(
            $validated['key'],
            $permanentKey
        );

        // Store file metadata in database
        $file = auth()->user()->files()->create([
            'uuid' => $validated['uuid'],
            'key' => $permanentKey,
            'bucket' => $validated['bucket'],
            'name' => $validated['name'],
            'content_type' => $validated['content_type'],
        ]);

        return response()->json([
            'message' => 'File uploaded successfully',
            'file' => $file,
        ]);
    }
}
Files are initially stored in the tmp/ directory and should be moved to a permanent location after validation.

Step 5: Add Route

Add the route to your routes/api.php:
routes/api.php
use App\Http\Controllers\FileController;

Route::middleware('auth:sanctum')->group(function () {
    Route::post('/files', [FileController::class, 'store']);
});

Understanding the Response

The s3m() function returns a promise that resolves with the following data:
{
  uuid: string,        // Unique identifier for the file
  key: string,         // S3 object key (e.g., "tmp/uuid-here")
  bucket: string,      // S3 bucket name
  extension: string,   // File extension (e.g., "pdf")
  name: string,        // Original filename
  url: string          // S3 URL (if auto_complete is not false)
}

Advanced Options

Customize the upload behavior with additional options:
s3m(file, {
  visibility: 'public-read', // Make file publicly accessible
  progress: progress => {
    console.log(progress)
  }
}).then(response => {
  // Handle response
})
This requires allow_change_visibility to be true in config/s3m.php.

Default Configuration

S3M uses these default settings:
10 * 1024 * 1024 // 10MB per chunk

Testing Your Upload

1

Start Your Application

php artisan serve
2

Open in Browser

Navigate to your upload page and select a file.
3

Monitor Progress

Watch the progress bar as chunks upload to S3.
4

Verify in S3

Check your S3 bucket’s tmp/ directory to see the uploaded file.
5

Check Backend

Verify that your backend endpoint received the file metadata.

Common Issues

Make sure your S3 bucket has the correct CORS configuration. See the Installation guide for details.The ETag header must be exposed:
"ExposeHeaders": ["ETag"]
Check that:
  1. Your UserPolicy has the uploadFiles method
  2. The user is authenticated
  3. The policy returns true for the current user
Ensure the @s3m directive is in your layout before your application’s JavaScript.
Verify your AWS credentials in .env are correct and the IAM user has S3 write permissions.

Next Steps

Authorization

Learn how to implement fine-grained upload permissions

Configuration

Explore all available configuration options

JavaScript API

Deep dive into the JavaScript API reference

Backend Integration

Learn how to process uploaded files in Laravel

Build docs developers (and LLMs) love