Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Spray Media Laravel Package

sarkis-sh/spray-media

Spray Media is a Laravel package for managing uploaded media and attaching files to Eloquent models. It offers a simple foundation for storing, retrieving, and organizing images and documents, with configurable storage and straightforward model integration.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

  • Build vs. buy on the upload pipeline. This release covers the parts of file upload that are easy to underestimate and expensive to get right: presigned direct-to-storage uploads, server-side verification of real size and MIME type from file bytes, one-time encrypted completion tokens, and pruning of abandoned uploads. That is typically several weeks of engineering plus ongoing security review; adopting it converts a build item into a configuration item.
  • Infrastructure cost and scalability roadmap. serving.mode = redirect moves file egress off PHP entirely — the app only validates signature/expiry and returns a 302. Combined with direct uploads, the application tier stops carrying file bytes in either direction. This is the difference between "scale the app servers" and "scale the bucket/CDN," and it makes bandwidth a storage-vendor line item rather than a compute line item. Pairing with Cloudflare R2 (zero egress) is an explicit, supported path and materially changes the unit economics of a media-heavy product.
  • Monetization and plan-tier gating. DirectUploadPolicyInterface is the commercially interesting piece: a policy can refuse an upload, choose the destination folder, attach attributes to the record, and apply per-kind size and type limits. That maps directly onto free/pro/enterprise quotas, per-workspace storage pools, and feature-specific constraints (e.g. avatars capped at 2 MB, attachments at 50 MB, video at 2 GB) without forking the upload endpoint.
  • Trust & safety and compliance posture. Object storage does not enforce declared size or type on a signed upload. Step 3 re-checks real size and real type from the file's first bytes and deletes failures. That is a defensible answer to "how do you stop someone uploading an executable renamed to .png?" and to content-type spoofing findings in security reviews. A moderation or AV-scanning stage slots naturally into the "once the file has arrived" refusal hook.
  • Mobile and large-file UX. Client-to-bucket uploads avoid the double hop through the app server, so large files on poor connections are faster and less likely to time out mid-request. Fewer failed uploads is a directly measurable activation and retention input for any product where uploading is part of onboarding or core use.
  • Operational hygiene as a default. spray-media:prune-pending cleans up never-completed uploads on an hourly schedule. Without this, abandoned direct uploads silently accumulate as paid storage; with it, orphan cost is bounded and predictable.
  • Scoping near-term roadmap work. Because the feature set is real and shipped, the roadmap question shifts from "when do we build direct upload?" to "which plan tiers get which limits, and do we need scanning before we launch?" — higher-leverage product questions.

When to Consider This Package

Adopt when most of the following hold:

  • You are on Laravel 11+ with an S3-compatible disk (AWS S3, Cloudflare R2, MinIO) — required for direct uploads specifically, since temporary upload URLs depend on it. Plain redirect serving needs the S3 adapter (league/flysystem-aws-s3-v3) but is otherwise undemanding.
  • File size or volume is a real constraint: users upload tens of megabytes, uploads are frequent, or egress is a visible line on the cloud bill.
  • You need per-tenant or per-plan upload rules and would otherwise be writing branching logic inside a controller.
  • You want a reviewable security story for uploads — signature and expiry checks, byte-level type verification, one-time tokens, server-side size confirmation — without owning that code.
  • You are standardizing on object storage anyway and want the application to stop proxying bytes.
  • Rollout risk matters: everything here is off by default, there are no breaking changes and no new migration, so it can ship behind env flags and be enabled disk-by-disk, endpoint-by-endpoint.

Look elsewhere when:

  • You need media processing, not media transport: transcoding, adaptive streaming, image resizing/transformation, DRM, or watermarking. This package stores and serves; it does not transform. A dedicated media platform is the right buy there, and it can sit behind this.
  • You need built-in malware scanning or content moderation. The policy hooks let you call out to a scanner, but the scanner is yours to source.
  • You are on Laravel 10 or earlier, or on a storage backend that cannot issue temporary upload URLs — you can still use ordinary uploads and redirect serving on object storage, but the direct-upload half of this release is unavailable.
  • Files live on local disk only and will stay there. Direct uploads are moot, and redirect mode falls back to streaming through PHP, so the main benefit evaporates.
  • You require a fully managed vendor SLA with support contracts. This is a package, not a platform; you own the integration and the on-call.
  • Your volumes are so small that a simple multipart form post is genuinely sufficient. The added flow — initiate, PUT, complete, prune — is real complexity and should be justified by real load.

How to Pitch It (Stakeholders)

For executives — one paragraph. This release lets us take file uploads and downloads off our own servers. Files go directly from the user's device into cloud storage, and downloads redirect to short-lived storage links, so our application servers no longer carry file traffic in either direction. The practical effects are lower and more predictable infrastructure cost (especially paired with zero-egress storage like Cloudflare R2), faster and more reliable uploads for customers on weak connections, and a stronger security posture because every upload is verified against its declared size and actual file type before we accept it. It also gives us the mechanism to enforce storage limits by plan tier, which is a prerequisite for packaging storage as a paid feature. Critically, this is low-risk to adopt: it ships switched off, requires no database migration, and does not break anything currently in production, so we can turn it on incrementally and measure.

For engineering — the short version. Direct-to-disk uploads in three calls (initiate → PUT to signed URL → complete), with completion validating real byte size and real MIME type from magic bytes, deleting failures, and writing nothing to the database until step 3 passes. Tokens are encrypted and single-use. DirectUploadPolicyInterface and bindings.upload_url_signer are the two swap points if we need custom admission rules or a different signing scheme — a token only completes through the policy that issued it. Redirect serving keeps Content-Type and inline/attachment disposition. New env flags (SPRAY_MEDIA_DISK, SPRAY_MEDIA_SERVE_MODE, SPRAY_MEDIA_DIRECT_UPLOADS) are off by default; published configs only need new bindings keys if we intend to override, and the serving / direct_uploads defaults come from the package. Needs league/flysystem-aws-s3-v3 and an S3 disk on Laravel 11+. Action item: schedule spray-media:prune-pending hourly before enabling direct uploads — that is the one operational step that is easy to forget and costly to skip.

For design and support — what changes for users. Uploads get faster and less failure-prone on large files and poor networks, with no UI change strictly required. If we enable per-plan limits through a policy, users will start seeing clear, server-enforced reasons a file was rejected rather than silent failures — worth designing error states for.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels