Modify image permissions on S3 file uploads within Laravel Nova

If you are a Laravel aficionado like myself, you like just about everything that Taylor Otwell and team cranks out. Those guys over there develop some very good platforms with extraordinary design and support. This includes their administrative dashboard that they released a couple years ago at Laracon: Laravel Nova.

If you aren't familiar with Laravel Nova, it is an administrator dashboard that automatically hooks in to your application's model structure and provides a phenomenal PHP-based API for you to configure the fields and their behavior; not to mention that it can be extended via third-party plugins and extensions.

I happen to have written a few of these extensions myself, so check them out if you are interested. A shameless plug; I KNOW.

Going public

While using another one of Laravel's first-party platforms, Laravel Vapor, I ran into an issue where by default, all file uploads from Laravel Nova are being made private within an S3 bucket. This is extremely inconvenient because it either forces you to manually change the access permissions of the uploaded object or the bucket itself, or use a temporary signed-url. If you decide to go with the second option, this can cause it's own set of issues within Nova. I really struggled with this and for the life of me could not find a way around this.

Using invokables

After doing some digging I stumbled upon a section within the Nova documentation where you can use invokable classes to manipulate the Image and File Nova input facades. What they essentially let you do is pass a series of parameters into an invoked class, run a bunch of logic, and then return a single/series of values to be saved into the database.

Admittedly there is a lot of rather confusing stuff in this section of the docs, but with a little trial and error I was finally able to solve my issue: upload images from Laravel Nova into S3 with public permissions while not manipulating the object at the bucket level. See below.

How To

First create a class within your application; the name and associated namespace doesn't mater, and add the following code:

<?php


namespace App\Library\Contracts;

use Illuminate\Http\Request;


class NovaImageUpload
{
    /**
     * Store the incoming file upload.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $attribute
     * @param  string  $requestAttribute
     * @param  string  $disk
     * @param  string  $storagePath
     * @return array
     */
    public function __invoke(Request $request, $model, $attribute, $requestAttribute, $disk, $storagePath)
    {
        $currentFile = $request->file($attribute);

        return [
            $attribute => $currentFile->storePublicly($storagePath)
        ];
    }
}

Essentially what this code does is take a series of parameters and allow you to sort through them fairly easily and fully customize the upload experience. While this code sample is pretty sample, hopefully you can see how much power you actually have. Just a small note: the $attribute variable is the property of the model that will store the file path, and the $storagePath is the directory that the file will be stored in; which for me was /.

Next we need to add the following lines to your Nova resource:

use App\Library\Contracts\NovaImageUpload;

Image::make('Image', 'product_image')
// code to add
    ->store(new NovaImageUpload);
////

Using the store method, you pass the invokable function to hijack the file upload process and then use the functionality that you outlined in the invoked class. It looks kind of complicated on the surface, but once you figure out what you are doing, it is kind of empowering knowing how/where you stuff is being manipulated.

Hope this helps!