I have not used Vapor yet but you can override the upload JS code for Spark to use Vapor.store instead?
You'll want to extend this file and override the proper method..
resources/assets/js/settings/teams/update-team-photo.js
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
I am hosting Laravel Spark on AWS Lambda using Vapor. However, the user or team profile photos are not saving up to S3. The instructions in https://docs.vapor.build/1.0/resources/storage.html seem to suggest that when uploading files from the front end, you need to stream them directly to S3 using the Vapor.store method.
Has anyone else tried this, and if so did you need to rewrite the corresponding parts of the Spark code? Or is there some other way to get this to work?
Photos upload fine when tested in a local (traditional server-based) environment
Many thanks for your help.
The solution turned out to be more complex than expected. Copying it for the user photo here, but the team photo is a similar idea.
resources/js/spark-components/settings/profile/update-profile-photo.js to use Vapor.store(). Also changed the parameters in the call to POST settings/photo
photo_bucket, photo_key, photo_content_type in users tableGET settings/photos to serve the photo from S3 (using a new S3PhotoController)UpdateProfilePhoto@validator to handle the new parameters for POST settings/photo
UpdateProfilePhoto@handle to copy the S3 file from tmp/ to profiles/, to delete the old photo and to save details to the user table. It does not resize the photo, as the original code did, to avoid memory issues in AWS Lambda. Recommending to provide guidance to the user in the View to correctly size the photo prior to uploading.Code copied below
// This component overrides settings/profile/update-profile-photo;
Vue.component('spark-update-profile-photo', {
props: ['user'],
/**
* The component's data.
*/
data() {
return {
form: new SparkForm({})
};
},
methods: {
/**
* Update the user's profile photo.
*/
update(e) {
e.preventDefault();
if ( ! this.$refs.photo.files.length) {
return;
}
var self = this;
this.form.startProcessing();
// Stream the file to S3
Vapor.store(this.$refs.photo.files[0], {
progress: progress => {
this.uploadProgress = Math.round(progress * 100);
}
}).then(response => {
// Now we send details of the uploaded photo to the server.
// We will update the user after this action.
axios.post('/settings/photo',{
bucket: response.bucket,
key: response.key,
content_type: this.$refs.photo.files[0].type,
})
.then(
() => {
Bus.$emit('updateUser');
self.form.finishProcessing();
},
(error) => {
self.form.setErrors(error.response.data.errors);
}
);
});
},
},
computed: {
/**
* Calculate the style attribute for the photo preview.
*/
previewStyle() {
return `background-image: url(${this.user.photo_url})`;
}
}
});
…
Route::get('settings/photo', 'S3PhotoController@userPhoto')->name('settings.photo');
…
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\User;
class S3PhotoController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
// Displays user photo
public function userPhoto (){
$user = auth()->user();
$headers = [
'Content-Type' => $user->photo_content_type,
'X-Vapor-Base64-Encode' => 'True'
];
return Storage::download($user->photo_key,
$user->id,
$headers);
}
}
<?php
namespace App\Providers;
use Laravel\Spark\Spark;
use Laravel\Spark\Providers\AppServiceProvider as ServiceProvider;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class SparkServiceProvider extends ServiceProvider
{
…
/**
* Finish configuring Spark for the application.
*
* @return void
*/
public function booted()
{
…
// We need to over-ride Spark's implementation of the update photo handlers
// in order to be compatible with Vapor
// One thing the original handler did that this doesn't is to resize the image
// to fit 300 pixels. Because of this, the view is modified to recommend the
// image size to the user prior to upload.
Spark::swap('UpdateProfilePhoto@validator',function($user, array $data)
{
return Validator::make($data, [
'bucket' => 'required|string',
'key' => 'required|string',
'content_type' => 'required|string',
]);
});
Spark::swap('UpdateProfilePhoto@handle', function($user, array $data)
{
$targetKey = str_replace('tmp/', 'profiles/', $data['key']);
Storage::copy($data['key'],$targetKey);
$oldPhotoKey = $user->photo_key;
// We save details of the photo to the user record. The parameter passed to
// the URL is a dummy parameter with no meaning, but is used to force the photo
// on the settings page to be updated
$user->forceFill([
'photo_url' => route('settings.photo', ['v' => Str::random(4)]),
'photo_bucket' => $data['bucket'],
'photo_key' => $targetKey,
'photo_content_type' => $data['content_type']
])->save();
try{
Storage::delete($oldPhotoKey);
} catch (Exception $e) {
}
});
}
Please or to participate in this conversation.