iidestiny/laravel-filesystem-oss
Aliyun OSS filesystem driver for Laravel, letting you use the Storage facade to upload, download, delete, and manage files on Alibaba Cloud Object Storage Service with support for custom domains, signed URLs, and streaming.
Install the driver and let Laravel auto-discover the service provider (Laravel 5.5+):
composer require iidestiny/laravel-filesystem-oss
On older Laravel, add Iidestiny\LaravelFilesystemOss\OssServiceProvider::class to config/app.php (check the README for the exact namespace in your major version).
Add a disk in config/filesystems.php:
'oss' => [
'driver' => 'oss',
'access_id' => env('OSS_ACCESS_ID'),
'access_key' => env('OSS_ACCESS_KEY'),
'bucket' => env('OSS_BUCKET'),
'endpoint' => env('OSS_ENDPOINT'), // host only, e.g. oss-cn-hangzhou.aliyuncs.com
'cdn_domain' => env('OSS_CDN_DOMAIN'), // optional
'ssl' => true,
'is_cname' => false,
'prefix' => '',
'options' => [],
],
First use case — the 30-second smoke test:
Storage::disk('oss')->put('hello.txt', 'Hello OSS');
echo Storage::disk('oss')->url('hello.txt');
Where to look first:
config/filesystems.php — confirm driver => oss and the endpoint/bucket.OssAdapter for url(), temporaryUrl(), and how prefix/cdn_domain are applied. These drive 90% of day-to-day surprises.iidestiny/flysystem-oss (the underlying adapter) for OSS client options passed via the options array.Make OSS the default disk. Set 'default' => env('FILESYSTEM_DISK', 'oss') and use Storage::put/get/delete everywhere. Persist paths, not URLs, so CDN or endpoint changes don't invalidate the database.
Standard upload flow.
$path = $request->file('avatar')->store('avatars', 'oss'); // returns "avatars/uuid.ext"
// Later:
$url = Storage::disk('oss')->url($path);
Two disks for public vs private. Create oss-public (public-read bucket) and oss-private (default private ACL). Serve public assets with url(), and private files with temporaryUrl():
$signed = Storage::disk('oss-private')->temporaryUrl($path, now()->addMinutes(20));
CDN without losing the raw URL. cdn_domain rewrites every url() on that disk. If you need both, register two disks pointing at the same bucket — one with cdn_domain, one without.
Sandbox with prefix. Set 'prefix' => 'tenant-'.$tenant->id at runtime (via config([...]) before resolving the disk) to isolate tenants without tracking prefixes manually. Remember: paths you pass are relative; URLs include the prefix.
Streaming and large files. put() loads contents into memory. For uploads >10 MB use putFile, writeStream, or a queued job:
Storage::disk('oss')->writeStream("exports/{$id}.csv", $stream);
Image variants. Store the original, then build derived URLs yourself using OSS image processing (?x-oss-process=image/resize,w_400) rather than uploading every size. It keeps a single source of truth and cuts storage.
Direct browser uploads. The Laravel driver handles server-side I/O only. For direct-to-OSS uploads you need a signed POST policy — drop to OSS\OssClient for generatePostPolicy(), or hand-roll the policy and signature. Don't try to proxy multi-GB uploads through PHP.
Testing. Storage::fake('oss') swaps to a local disk, so path assertions work and no network is hit. It does not validate ACLs, signed URL correctness, or OSS error codes — cover those against a real test bucket in integration tests.
Migrating existing local files.
foreach (Storage::disk('local')->allFiles('uploads') as $file) {
Storage::disk('oss')->writeStream($file, Storage::disk('local')->readStream($file));
}
oss-cn-hangzhou.aliyuncs.com), not https://.... If is_cname => true, supply your custom domain. A scheme in endpoint is the most common cause of SignatureDoesNotMatch / InvalidBucketName.ssl => false produces http:// URLs. On an HTTPS app this triggers mixed-content blocks. Keep it true in production; the flag controls the synthesized URL scheme, not just the transport.url() on a private bucket returns 403. Bucket ACL is configured in the OSS console, not by this package. Use temporaryUrl() for private objects or flip the bucket ACL.['visibility' => 'public'] in put() may be ignored if the bucket blocks public ACLs. Verify in the console.application/octet-stream for unknown extensions, causing browser downloads. Set content type explicitly: Storage::disk('oss')->put($path, $body, ['ContentType' => 'image/png']). OSS SDK option keys are ContentType, CacheControl, ContentDisposition.RequestTimeTooSkewed if the server's clock drifts. Keep NTP running; also remember OSS signs in UTC, so now()->addMinutes(10) is safe but hardcoded local dates are not.-internal.aliyuncs.com is faster/cheaper from ECS but produces URLs unreachable to the public. If your app runs in a VPC but serves browsers, use the public endpoint for the signing disk and the internal one only for server-to-server work — or sign on a public-endpoint disk.temporaryUrl() needs Flysystem support. This package implements it, but the TTL is capped (usually 7 days on OSS). If you see UnableToGenerateTemporaryUrl, you're on an old major or a non-OSS disk.Call to undefined method on the adapter. Check composer show iidestiny/flysystem-oss against your Laravel version before debugging deeper.php artisan config:clear — env() won't be re-read otherwise.url() does not always URL-encode. Slugify object keys (Str::slug() + hash) to avoid broken URLs and CDN cache misses.League\Flysystem\UnableToWriteFile / UnableToReadFile and inspect $e->getPrevious()->getMessage() — it carries the OSS code (NoSuchKey, AccessDenied, SignatureDoesNotMatch), which tells you instantly whether it's config, IAM, or a missing object.How can I help you explore Laravel packages today?