PHPackages                             sarkis-sh/spray-media - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. sarkis-sh/spray-media

ActiveLibrary[File &amp; Storage](/categories/file-storage)

sarkis-sh/spray-media
=====================

Secure uploads, signed URLs, and flexible media delivery for Laravel.

v1.2.0(4mo ago)211MITPHPPHP ^8.1

Since Jan 4Pushed 4mo agoCompare

[ Source](https://github.com/sarkis-sh/spray-media)[ Packagist](https://packagist.org/packages/sarkis-sh/spray-media)[ RSS](/packages/sarkis-sh-spray-media/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (3)Dependencies (3)Versions (4)Used By (0)

Spray Media — Secure, Signed, Developer-First Media for Laravel
===============================================================

[](#spray-media--secure-signed-developer-first-media-for-laravel)

[![Latest Version](https://camo.githubusercontent.com/e27c46ddc1cca44dcc807963097173b3c62eeec171da2f4969397cceba56a377/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7361726b69732d73682f73707261792d6d656469612e737667)](https://packagist.org/packages/sarkis-sh/spray-media)[![Laravel](https://camo.githubusercontent.com/5cd2760dfdd7c2a185418a2e99d80b17ccc70873779470975fa14190c7408d83/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31302f31312f31322d4646324432302e737667)](https://laravel.com)[![PHP](https://camo.githubusercontent.com/7d71b59ec3aa918b52f007e0b847cb62f0f386f380ad40e8130424eb8b25e372/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253545382e312d3737374242342e737667)](https://www.php.net/)[![License](https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667)](LICENSE)[![Tests](https://camo.githubusercontent.com/99b8d477db5c9027876a70db6b408559e176803bd40dd07b74ff6299772caa84/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d706870756e69742d677265656e2e737667)](#testing)

Spray Media gives you signed links, a hardened upload flow, and swappable building blocks so you can ship secure media delivery (view/download) quickly and confidently.

Table of Contents
-----------------

[](#table-of-contents)

- [Why Spray Media](#why-spray-media)
- [Requirements](#requirements)
- [Install](#install)
- [Configuration Essentials](#configuration-essentials)
- [Default Routes](#default-routes)
    - [Update filename endpoint](#update-filename-endpoint)
- [Database Schema](#database-schema)
- [Quickstart (3 Steps)](#quickstart-3-steps)
- [Helpers &amp; Resource](#helpers--resource)
- [How It Works](#how-it-works)
- [Security Notes](#security-notes)
- [Performance](#performance)
- [Extensibility](#extensibility)
- [Translations](#translations)
- [Testing](#testing)
- [License](#license)

Why Spray Media
---------------

[](#why-spray-media)

- 🔒 HMAC-signed URLs with optional expiration and embedded metadata.
- 📥 HTTP upload endpoint with size/MIME controls, custom rules, and automatic filename sanitizing.
- 📄 Inline or attachment responses with Cache-Control, ETag, and 304 support.
- ⚡ Helpers, Resource, and Facade ready to drop into APIs or views.
- 🔧 Fully swappable components: uploader, storage, URL generator, validator, file server, repository, response adapter.
- 🌐 Built-in translations (en/ar), configurable routes, and ready migrations.

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- Laravel 10, 11, or 12

Install
-------

[](#install)

```
composer require sarkis-sh/spray-media

# publish assets (config, migrations, lang)
php artisan vendor:publish --provider="SprayMedia\Providers\SprayMediaServiceProvider" --tag=config
php artisan vendor:publish --provider="SprayMedia\Providers\SprayMediaServiceProvider" --tag=migrations
php artisan vendor:publish --provider="SprayMedia\Providers\SprayMediaServiceProvider" --tag=lang

php artisan migrate
```

Configuration Essentials
------------------------

[](#configuration-essentials)

Full config lives in [src/Config/media.php](src/Config/media.php). Key options:

KeyENVDefaultPurpose`disk``SPRAY_MEDIA_DISK``local`Filesystem disk from `config/filesystems.php``base_dir``SPRAY_MEDIA_BASE_DIR``uploads`Root folder inside the disk`upload.max_kb``SPRAY_MEDIA_MAX_UPLOAD_KB``51200`Max size (KB)`upload.mimetypes` / `upload.mimes``SPRAY_MEDIA_MIMETYPES` / `SPRAY_MEDIA_MIMES``image/jpeg,image/png,application/pdf`Allowed types`route.prefix``SPRAY_MEDIA_ROUTE_PREFIX``api/media-items`Route group prefix`route.path``SPRAY_MEDIA_ROUTE_PATH``secure`Signed serve endpoint path`route.middleware_public``SPRAY_MEDIA_ROUTE_MIDDLEWARE_PUBLIC``api`Middleware for serve endpoint`route.middleware_admin``SPRAY_MEDIA_ROUTE_MIDDLEWARE_ADMIN``api`Middleware for upload/CRUD`hmac.secret``SPRAY_MEDIA_HMAC_SECRET``APP_KEY`HMAC key`hmac.algorithm``SPRAY_MEDIA_HMAC_ALGO``sha256`HMAC algo`hmac.default_expiration_minutes``SPRAY_MEDIA_DEFAULT_EXPIRATION_MINUTES``60`Default link TTL`performance.cache_control``SPRAY_MEDIA_CACHE_CONTROL``private, max-age=3600`Cache-Control header`performance.enable_etag``SPRAY_MEDIA_ENABLE_ETAG``true`Add ETagDefault Routes
--------------

[](#default-routes)

Defined in [src/Routes/api.php](src/Routes/api.php) (prefix/path/middleware are configurable):

HTTPPathDescriptionMiddlewareGET`/api/media-items/secure`Serve signed file (view/download)`route.middleware_public`POST`/api/media-items`Upload file + create record`route.middleware_admin`PUT`/api/media-items/{id}/update-filename`Update filename only`route.middleware_admin`DELETE`/api/media-items/{id}`Delete file + record`route.middleware_admin`### Update filename endpoint

[](#update-filename-endpoint)

- Path: `PUT /api/media-items/{id}/update-filename`
- Body (JSON or form):
    - `new_file_name` (string, required) — the desired base name (extension stays the same)

Example:

```
curl -X PUT http://your-app.test/api/media-items/123/update-filename \
	-H "Content-Type: application/json" \
	-d '{"new_file_name": "project-spec-v2"}'
```

Database Schema
---------------

[](#database-schema)

Migration: [src/Database/Migrations/0001\_01\_01\_000003\_create\_media\_items\_table.php](src/Database/Migrations/0001_01_01_000003_create_media_items_table.php)

- Columns: `path`, `disk`, `formatted_filename`, `filename`, `extension`, `mime_type`, `size`, timestamps.

Quickstart (3 Steps)
--------------------

[](#quickstart-3-steps)

1. Upload via HTTP

```
curl -X POST http://your-app.test/api/media-items \
	-F "file=@/path/to/file.png" \
	-F "custom_filename=My File"
```

Validation applies `max/mime/mimetypes/custom_rules` from config.

Sample success payload (see [src/Infrastructure/Http/DefaultResponseAdapter.php](src/Infrastructure/Http/DefaultResponseAdapter.php)):

```
{
	"result": "success",
	"message": "...",
	"model": {
		"id": 1,
		"filename": "my-file",
		"formatted_filename": "my-file.png",
		"extension": "png",
		"mime_type": "image/png",
		"size": 12345,
		"url": "https://.../secure?data=...&signature=...",
		"expires_at": 1700000000
	},
	"error_list": [],
	"code": 201
}
```

2. Generate a signed URL (inline or download)

```
use SprayMedia\Domain\Enums\MediaAction;
use SprayMedia\Facades\SprayMedia;

$url = SprayMedia::generateProtectedUrl($mediaItem, MediaAction::VIEW, [
		'expiration_minutes' => 30,
		'metadata' => ['user_id' => 5],
]);

$download = media_item_generate_protected_download_url($mediaItem);
$temporary = media_item_generate_protected_temporary_url($mediaItem, 15);
```

Links carry `data` (base64 JSON) + `signature` (HMAC). Pass `expiration_minutes => null` for non-expiring links.

3. Serve the file Hit the signed URL. The package validates signature/expiry, sets headers based on `action` (inline/attachment), and emits Cache-Control/ETag.

Helpers &amp; Resource
----------------------

[](#helpers--resource)

- `media_item_upload_file($file, ?$dir, ?$disk)` returns storage metadata only.
- `media_item_upload_and_create($file, $attributes = [])` uploads and creates the record.
- `media_item_generate_protected_url($media, MediaAction::VIEW, $options = [])` signed URL helper.
- `media_item_with_signed_url($media, $action, $options)` and `media_item_collection_with_signed_url(...)` wrap in Resource with `url` and `expires_at` set ([src/Http/Resources/MediaItemResource.php](src/Http/Resources/MediaItemResource.php)).
- `media_item_get_absolute_path($media)` returns filesystem path.

How It Works
------------

[](#how-it-works)

1. Upload: [LocalFileUploader](src/Infrastructure/Storage/LocalFileUploader.php) stores to configured disk/base\_dir and sanitizes filenames via [FilenameSanitizer](src/Application/FilenameSanitizer.php).
2. Persist: [MediaItemManager](src/Application/MediaItemManager.php) persists metadata through the repository binding.
3. Sign: [HmacMediaItemUrlGenerator](src/Infrastructure/Url/HmacMediaItemUrlGenerator.php) builds payload, base64 encodes, signs with HMAC.
4. Validate: [HmacPayloadValidator](src/Infrastructure/Validation/HmacPayloadValidator.php) checks presence, signature (hash\_equals), payload JSON, action, and expiry.
5. Serve: [LocalFileServer](src/Infrastructure/Serving/LocalFileServer.php) streams inline/attachment, applies Cache-Control/ETag, returns 304 when ETag matches.

Security Notes
--------------

[](#security-notes)

- Rotate `hmac.secret` per environment; keep it distinct from `APP_KEY` when possible.
- Choose a strong `hmac.algorithm` (sha256+). Links can be expiring or non-expiring per call.
- Filenames are sanitized to a slug to avoid header/FS issues.
- Validator rejects missing/invalid signatures, malformed payloads, wrong actions, or expired links.

Performance
-----------

[](#performance)

- If `expires_at` is present, Cache-Control uses the remaining lifetime automatically; otherwise uses the configured header.
- ETag is based on `id` + `updated_at` to enable 304 responses.

Extensibility
-------------

[](#extensibility)

- Swap any contract via `spray-media.bindings` in [src/Config/media.php](src/Config/media.php): repository, uploader, url\_generator, payload\_validator, file\_server, response\_adapter.
- Override model/resource via `spray-media.model` and `spray-media.resource` to expose custom fields or relations.
- Routes, names, and middleware are fully configurable for API compatibility.

Translations
------------

[](#translations)

- English and Arabic strings ship with the package; publish lang to customize.

Testing
-------

[](#testing)

```
./vendor/bin/phpunit
```

License
-------

[](#license)

MIT

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance74

Regular maintenance activity

Popularity8

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~17 days

Total

3

Last Release

140d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/103902380?v=4)[Sarkis Shalhoub](/maintainers/sarkis-sh)[@sarkis-sh](https://github.com/sarkis-sh)

---

Top Contributors

[![sarkis-sh](https://avatars.githubusercontent.com/u/103902380?v=4)](https://github.com/sarkis-sh "sarkis-sh (3 commits)")

---

Tags

apiattachmentsdownloadfile-serverfilesfilesystemhmaclaravellibrarymediapackageprotectedsecuresigned-urlsstoragetranslationsuploadvalidationapilaravelvalidationtranslationspackagefilesstoragemediasecureuploaddownloadhmacattachmentsprotectedsigned-urlsfile server

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/sarkis-sh-spray-media/health.svg)

```
[![Health](https://phpackages.com/badges/sarkis-sh-spray-media/health.svg)](https://phpackages.com/packages/sarkis-sh-spray-media)
```

###  Alternatives

[zgldh/laravel-upload-manager

Upload, validate, storage, manage by API for Laravel 5/6/7/8/9

775.6k2](/packages/zgldh-laravel-upload-manager)[erlandmuchasaj/laravel-file-uploader

A simple package to help you easily upload files to your laravel project.

139.0k](/packages/erlandmuchasaj-laravel-file-uploader)[gaspertrix/laravel-backpack-dropzone-field

Add Dropzone support for Laravel Backpack

172.2k](/packages/gaspertrix-laravel-backpack-dropzone-field)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
