php elephant sticker
Photo by RealToughCandy.com on Pexels.com

[Complete Practical Guide] Laravel File Upload & Delivery — Storage/S3, Presigned URLs, Image Optimization, PDFs/Video, Virus Scanning, Authorization, Caching, and Accessible Alternative Text

What you’ll learn in this article (key points)

  • The basics of Laravel Storage (Flysystem) and how to design for local/S3/CDN
  • Upload security (MIME validation, size limits, extension spoofing defenses, EXIF removal, virus scanning)
  • Secure delivery with presigned URLs (expiration, Content-Disposition, when to use private vs public)
  • Image variant generation (resize/thumbnails/WebP/AVIF), srcset, lazy loading, and CLS prevention
  • PDF/Office delivery policy (HTML alternatives, summaries, tagged PDFs)
  • Video delivery (how to think about HLS/DASH, captions/WebVTT, avoiding autoplay)
  • Caching (ETag/Cache-Control) and CDN cost optimization
  • Media accessibility (alt text, captions, subtitles, transcripts, and admin workflow)

Intended readers (who benefits?)

  • Beginner–intermediate Laravel engineers: You want to handle images/files safely while protecting performance.
  • Tech leads in SaaS/media/e-commerce: You want to standardize a media platform assuming S3/CDN.
  • Editors/designers: You want to operationalize alt text and subtitles.
  • QA/accessibility owners: You want to guarantee “understandable for everyone,” even for media.

Accessibility level: ★★★★★

Covers alt text, captions, subtitles, transcripts, screen-reader and keyboard operation, motion reduction, and non-color-dependent state signaling—both implementation and operations.


1. Introduction: File Features Sit Between “Convenient” and “Dangerous”

Uploading images, PDFs, and videos greatly improves UX. But it’s also an area prone to security incidents (extension spoofing, malware, data leaks) and performance problems (heavy images, transfer costs, cache inconsistency). Media also exposes accessibility gaps: without alt text or captions, information simply doesn’t reach some users.

Laravel enables an end-to-end approach—from Storage to authorization, signed URLs, queues, and HTTP responses. This guide organizes a practical “pattern” for a file platform that is safe, fast, and understandable to everyone.


2. Storage Design: Separate public and private

2.1 Core policy

  • Default to private storage: anything requiring access control must be private
  • Only “safe-to-publish variants” should be public
  • Keep originals non-public; distribute via presigned URLs or through the app

On S3, it’s convenient to separate by bucket (or prefix), for example:

  • private/ (non-public)
  • public/ (CDN delivery)

3. Storage Configuration: Prepare for S3 + CDN

FILESYSTEM_DISK=s3
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=example-bucket
AWS_URL=https://cdn.example.com
// Save (private)
$path = Storage::disk('s3')->putFile('private/uploads', $request->file('file'), 'private');

// Temporary distribution (10 minutes)
$url = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(10), [
  'ResponseContentDisposition' => 'inline',
]);
  • Setting AWS_URL to your CDN makes Storage::url() return CDN URLs.
  • For private distribution, temporaryUrl() is convenient.

4. Upload Security: Pair Validation with Sanitization

4.1 Validation (MIME + size)

$request->validate([
  'file' => ['required','file','mimetypes:image/jpeg,image/png,image/webp,application/pdf','max:8192'],
]);
  • Validate MIME, not extension.
  • max is in KB.

4.2 Defending against extension spoofing

  • Use mimetypes plus “real content” checks via fileinfo
  • Use random storage names after upload (don’t trust the original filename)

4.3 Removing EXIF (location metadata)

Leaving location data in photos can cause information leaks. It’s safer to strip EXIF during image processing (often done alongside resizing).

4.4 Virus scanning

  • Enqueue scanning as a job immediately after upload (async)
  • If malicious, quarantine and notify users with safe wording
  • Don’t make externally accessible until scanning completes (best practice)

5. Manage Metadata in the DB: Don’t “just store files”

Storing only in the filesystem makes operations hard—manage metadata in the DB.

Example media table fields (conceptual):

  • disk
  • path
  • mime
  • size
  • variants (thumbnails, etc.)
  • meta (alt/caption/lang, etc.)
  • owner_id / tenant_id
  • visibility (private/public)

This enables:

  • authorization
  • regeneration
  • multilingual alt text
  • auditing

6. Image Optimization: Generate variants in jobs

Originals are often large, so generate display-friendly variants:

  • widths: 320/640/1280
  • formats: AVIF/WebP/JPEG
  • thumbnails for lists
  • fix width/height to prevent CLS

Variant generation is slow if done synchronously—push it to queues.

dispatch(new GenerateImageVariants($mediaId))->onQueue('media');

7. Responsive Delivery: picture + srcset

<picture>
  <source type="image/avif" srcset="{{ $v['avif'] }}">
  <source type="image/webp" srcset="{{ $v['webp'] }}">
  <img
    src="{{ $v['w640'] }}"
    srcset="{{ $v['w320'] }} 320w, {{ $v['w640'] }} 640w, {{ $v['w1280'] }} 1280w"
    sizes="(max-width: 640px) 90vw, 640px"
    alt="{{ $media->meta['alt'] ?? '' }}"
    width="640" height="400"
    loading="lazy" decoding="async"
  >
</picture>
  • alt is required (decorative images use empty alt="")
  • Specifying width/height reduces CLS
  • loading="lazy" reduces transfer cost

8. Presigned URLs and Safe Downloads

8.1 Signed routes

If access control is required, the app should mediate distribution.

Route::get('/files/{media}', [FileController::class,'show'])
  ->middleware(['auth','signed'])
  ->name('files.show');
$url = URL::temporarySignedRoute('files.show', now()->addMinutes(10), ['media'=>$media->id]);

8.2 Content-Disposition

  • inline: render in browser (images/PDF)
  • attachment: download (sensitive files)

9. Caching: CDN + hashed filenames is the strongest combo

If public variants include a content hash in the filename:

  • long-term caching (1 year)
  • instant updates when content changes (URL changes)

Example: thumb.5f2a9c.webp
Cache-Control: public, max-age=31536000, immutable

For dynamic delivery, use ETag/304.


10. PDFs and Documents: Alternatives and Summaries Matter

  • Don’t rely on PDF only; provide key information in HTML too
  • Use tagged PDFs when possible (heading structure, alt text)
  • For long PDFs, provide a summary and table of contents
  • Include file type and size on download links (e.g., “PDF 2.3MB”)

Accessibility improves significantly when there is an alternative path for users who can’t open or read PDFs easily.


11. Video: Make captions (VTT) and transcripts standard

Because video carries lots of information, it won’t reach users without captions. Standardize at least:

  • <track kind="captions" src="...vtt" srclang="ja">
  • A transcript (text version of audio)
  • Avoid autoplay; require user action
  • Respect prefers-reduced-motion and suppress hero videos where appropriate

12. Alt Text Operations: Quality Is Determined by Editorial Workflow

You can’t fill alt text with technology alone—use an operational pattern:

  • Always show an alt input field at upload time
  • Only allow empty alt when explicitly marked as “decorative”
  • For text-in-image, put the same content in alt (if long, put it in body text instead)
  • For multilingual sites, store per-language alt like alt[ja] / alt[en]

13. Testing: Protect uploads and authorization

  • Use Storage Fake to validate uploads
  • Confirm signed URLs expire and are rejected
  • Confirm other users cannot access (authorization)
Storage::fake('s3');

$res = $this->post('/media', [
  'file' => UploadedFile::fake()->image('a.jpg', 800, 600),
]);
$res->assertCreated();

14. Common Pitfalls and Avoidance

  • Putting originals in public
    • Fix: keep originals private; publish variants only
  • Trusting accept attributes
    • Fix: validate MIME and size on the server
  • Leaving alt empty
    • Fix: require input (except decorative)
  • Heavy images and slow pages
    • Fix: variants + srcset + long-term caching
  • Autoplay video with sound
    • Fix: user-initiated playback + captions + transcripts
  • Queue stops and variants stop generating
    • Fix: Horizon monitoring + failed-job notifications
  • Cache not working, costs rise
    • Fix: hashed names + immutable

15. Checklist (Shareable)

Security

  • [ ] Validate MIME + size; prevent extension spoofing
  • [ ] Strip EXIF; virus scan (async)
  • [ ] Keep originals private; distribute via presigned URLs

Performance

  • [ ] Queue variant generation (resize/AVIF/WebP)
  • [ ] picture/srcset, width/height, lazy loading
  • [ ] CDN + hashed filenames + long-term caching

Operations

  • [ ] Media metadata management (owner/tenant/variants/meta)
  • [ ] Monitor queue/scheduler; notify on failures
  • [ ] Deletion and lifecycle (clean old variants)

Accessibility

  • [ ] Alt text operations (required; decorative is empty)
  • [ ] Captions, subtitles (VTT), transcripts
  • [ ] PDF alternatives (HTML/summary/size display)

Testing

  • [ ] Validate uploads with Storage::fake
  • [ ] Signed URL expiry and authorization
  • [ ] Deny access by other users

16. Conclusion

Laravel’s file features become remarkably stable when designed consistently around Storage: store, distribute, optimize, authorize, and operate as a single system. Keep originals private and publish only derived variants. Enforce strict upload validation and sanitization, generate variants asynchronously with queues, and make delivery fast with CDN + long-term caching. At the same time, manage accessibility information such as alt text and captions as metadata, and secure quality via editorial workflow. Files are convenient—but they’re also incident-prone. That’s exactly why it pays to build the “pattern” early and grow safely from there.


Reference Links

By greeden

Leave a Reply

Your email address will not be published. Required fields are marked *

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)