unisharp/laravel-fileapi
UniSharp Laravel FileAPI provides a simple Laravel integration for handling file uploads and file management. It offers a convenient API for validating, storing, and retrieving uploaded files, helping add file features to Laravel apps with minimal setup.
This release is a maintenance + interoperability drop for an image upload/thumbnail package. Nothing here changes the public API shape, so an existing install can be upgraded with a composer update and, at most, a config re-publish.
Minimal steps:
composer require <vendor>/<package> (or bump to ^1.0.2).php artisan vendor:publish --tag=config
config/filesystems.php. Local is fine for the first run; the new GCS support means gcs is now a first-class target alongside s3.$path = imageUpload($request->file('avatar'))
->disk('public')
->thumbnails([200, 400])
->save('avatars');
Where to look first: the published config file (disks, quality, suffix, thumbnail sizes) and the package's README/CHANGELOG. The changelog is the fastest way to see which of the fixes below touch your current setup.
First real use case: a profile/avatar upload that produces a small set of derived sizes on public with storage:link in place. Once that works end to end, promoting to S3 or GCS is a config change, not a code change.
Thumbnails are now optional — make that explicit. 1.0.2 allows uploading without thumbnails. Prefer passing an empty set over relying on a global default, so intent is visible at the call site:
imageUpload($file)->thumbnails([])->save('documents');
This is the right pattern for "store the original, render sizes on demand" flows, and it halves write latency on cloud disks.
Suffix is your filename contract. With empty suffixes now permitted, you can produce deterministic names (user-42.jpg instead of user-42_thumb.jpg). Beware collisions: several sizes on the same disk will overwrite each other if the suffix is empty. If you go suffix-less, give each size its own directory:
->suffix('') // new in 1.0.2
->thumbnails([200]) // writes into thumbnails/ subfolder
Quality is a per-type decision. jpg at quality 100 was previously clamped/broken; it now works. Treat 100 as an exception for hero images and print exports, not a default — it can multiply file size several-fold for no perceptible gain. Keep 80–85 globally and override narrowly:
->quality(100)->save('heroes'); // only where it matters
Cloud disks: configure visibility once, in two places. The thumbnail-visibility fix means derived files now inherit the visibility you asked for, but the disk and the package must agree. On S3/GCS:
// config/filesystems.php
'gcs' => [
'driver' => 'gcs',
'project_id' => env('GOOGLE_CLOUD_PROJECT_ID'),
'bucket' => env('GOOGLE_CLOUD_STORAGE_BUCKET'),
'key_file_path' => env('GOOGLE_CLOUD_KEY_FILE'),
'visibility' => 'public',
],
Then pass the same visibility through the upload call so originals and thumbnails match. If you are on uniform bucket-level access in GCS, per-object ACLs are ignored entirely — use a public bucket or serve through signed URLs instead.
Wrap uploads in a service, not controllers. A thin ImageService that owns disk, quality, sizes, and suffix keeps the inevitable policy drift in one file. It also makes the "with thumbnails / without thumbnails" branch a one-liner rather than a controller if.
Queue anything with more than two sizes. Cloud round-trips for each derived image are slow; dispatch a job that uploads the original synchronously and generates variants after the response.
1.0.2 fixes frame loss during compression, but verify with a real multi-frame GIF at your configured quality. GIF is palette-based; quality settings intended for JPEG can still degrade it. Consider skipping compression for GIFs entirely and storing the original.jpg + quality 100 is a trap in disguise. The bug is fixed, but you will now actually get the huge file. Add a size guard before persisting.visibility set, so files default to private and your CDN 403s; (b) the bucket enforces uniform access, so the ACL request is silently dropped. Check the stored object's ACL, not just the returned path.suffix('') with per-size directories, or keep a suffix on all but the primary.config:cache, re-publish and re-cache. A stale cached config is the most common cause of "GCS isn't working" right after a version bump.key_file_path must be an absolute path readable by the PHP user; JSON blobs in env vars won't work. Ship the key as a secret mounted file, never in the repo.Storage::disk($d)->exists($path), 2) the disk's visibility config, 3) php artisan config:show filesystems, 4) package log channel. Ninety percent of issues land at step 1 or 3.How can I help you explore Laravel packages today?