PHPackages                             fluxfiles/laravel - 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. fluxfiles/laravel

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

fluxfiles/laravel
=================

Laravel adapter for FluxFiles file manager

v0.2.26(3w ago)132MITPHPPHP ^8.1

Since May 30Pushed 1mo agoCompare

[ Source](https://github.com/thai-pc/fluxfiles-laravel)[ Packagist](https://packagist.org/packages/fluxfiles/laravel)[ Docs](https://github.com/thai-pc/fluxfiles)[ RSS](/packages/fluxfiles-laravel/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (6)Dependencies (12)Versions (42)Used By (0)

FluxFiles for Laravel
=====================

[](#fluxfiles-for-laravel)

Laravel adapter for [FluxFiles](https://github.com/thai-pc/fluxfiles) — a standalone, embeddable file manager with multi-storage support (Local, AWS S3, Cloudflare R2).

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

[](#requirements)

- PHP &gt;= 8.1 (matches `fluxfiles/fluxfiles`)
- Laravel 10, 11, or 12

Installation
------------

[](#installation)

```
composer require fluxfiles/laravel
```

Publish the config file:

```
php artisan vendor:publish --tag=fluxfiles-config
```

Add to your `.env`:

```
FLUXFILES_SECRET=your-random-32-char-secret
```

For the default local disk, expose Laravel public storage once:

```
php artisan storage:link
```

The default `local` disk writes to `storage/app/public/fluxfiles/uploads` and returns URLs under `/storage/fluxfiles/uploads`.

### Cloud storage (optional)

[](#cloud-storage-optional)

The `s3` and `r2` disks read these from `.env` (see `config/fluxfiles.php`):

```
# AWS S3 — or any S3-compatible (set AWS_ENDPOINT for MinIO / DO Spaces)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=ap-southeast-1
AWS_BUCKET=
AWS_ENDPOINT=                 # empty = native AWS S3
AWS_VISIBILITY=private        # 'public' = direct object URLs
AWS_PUBLIC_URL=               # CDN / custom domain for a public disk

# Cloudflare R2
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_ACCOUNT_ID=
R2_BUCKET=
R2_VISIBILITY=private
R2_PUBLIC_URL=                # r2.dev or custom domain for a public bucket
```

Modes
-----

[](#modes)

ModeDescription`proxy` (default)FluxFiles API runs through Laravel routes — no separate server needed`standalone`FluxFiles runs on its own server; Laravel only generates tokens and embeds the iframe> **Proxy mode:** exclude the route prefix from CSRF or uploads/deletes return **419** — see [CSRF exclusion](#csrf-exclusion-proxy-mode-only).

Set mode in `.env`:

```
FLUXFILES_MODE=proxy
# or
FLUXFILES_MODE=standalone
FLUXFILES_ENDPOINT=https://your-fluxfiles-server.com
```

Usage
-----

[](#usage)

### Blade Component

[](#blade-component)

```

```

### Generate Token

[](#generate-token)

```
use FluxFiles\Laravel\FluxFilesFacade as FluxFiles;

// For the current authenticated user
$token = FluxFiles::tokenForUser();

// With custom overrides
$token = FluxFiles::token(auth()->id(), [
    'perms'      => ['read', 'write'],
    'disks'      => ['local', 's3'],
    'prefix'     => 'user-123/',
    'max_upload'  => 20,    // MB — per uploaded file
    'max_storage' => 1000,  // MB — total quota per prefix (0 = unlimited)
    'max_files'   => 0,     // max files per prefix (0 = unlimited)
    'allowed_ext' => ['jpg', 'png', 'pdf'], // lowercase, no dot; null = all safe
    'ttl'         => 7200,  // seconds — token lifetime (7200 = 2 hours)
]);
```

### Enable Import from URL

[](#enable-import-from-url)

Import-from-URL is **off by default**. There's nothing to install or configure server-side per tenant — enabling it is a token claim. Add the import claims to the override array:

```
$token = FluxFiles::token(auth()->id(), [
    'perms'                => ['read', 'write'],
    'allow_url_import'     => true,                 // required — turns the feature on
    'max_import_mb'        => 20,                   // optional — cap per import (MB)
    'import_url_allowlist' => ['*.unsplash.com'],   // optional — restrict source hosts
    // 'import_path' => 'imports', 'import_rate_limit' => 10, 'import_concurrency' => 3
]);
```

The core then accepts `POST /api/fm/import-url` (`{ "url": "…", "path": "…" }`) for that token — SSRF-guarded and sharing the quota/dedup/variants pipeline. Server-wide defaults (when a claim is omitted) come from `FLUXFILES_IMPORT_*`env vars on the core service.

### Per-tenant configuration

[](#per-tenant-configuration)

FluxFiles is stateless — **the token is the per-tenant config.** There's no config file or table per customer; you mint a different token, and FluxFiles enforces its claims server-side. Drive the values from the tenant's plan:

```
use FluxFiles\Laravel\FluxFilesFacade as FluxFiles;

$tenant = auth()->user()->tenant;        // your own model

$claims = match ($tenant->plan) {
    // Free — 5 MB/file, images only, 500 MB quota, 200 files
    'free' => [
        'disks'       => ['local'],
        'max_upload'  => 5,
        'allowed_ext' => ['jpg', 'jpeg', 'png', 'webp'],
        'max_storage' => 500,
        'max_files'   => 200,
    ],
    // Pro — 100 MB/file, any safe type, 50 GB quota, unlimited files
    'pro' => [
        'disks'       => ['s3'],
        'max_upload'  => 100,
        'allowed_ext' => null,
        'max_storage' => 51200,
        'max_files'   => 0,
        'ai_auto_tag' => true,             // AI captions/tags on upload
        'rate_read'   => 240,              // higher API rate limit
        'variants'    => ['large' => 2560], // bigger preview variant
    ],
};

$token = FluxFiles::token(auth()->id(), [
    'prefix' => "tenant_{$tenant->id}/",   // isolates each tenant's files
    'perms'  => ['read', 'write', 'delete'],
    ...$claims,
]);
```

Always derive `prefix` from the **authenticated** tenant server-side — never from client input. For full isolation, give each tenant their **own bucket** with BYOB (`byob_disks`). See the root README's [Multi-tenant](https://github.com/thai-pc/fluxfiles#multi-tenant) section for the full claim list, and [Permissions](#permissions) for the storage each tenant's `_fluxfiles/` index needs.

> The `ai_auto_tag`, `rate_read` / `rate_write` and `variants` overrides are enforced by **core ≥ 0.2.8** — on an older core they're simply ignored (the adapter still issues a valid token, it just won't crash). Run `composer update fluxfiles/fluxfiles` to pick them up.

### Blade Directives

[](#blade-directives)

```

FluxFiles.open({
    endpoint: '@fluxfilesEndpoint',
    token: '@fluxfilesToken',
    disk: 'local',
    mode: 'picker'
});

```

### Facade Methods

[](#facade-methods)

```
use FluxFiles\Laravel\FluxFilesFacade as FluxFiles;

FluxFiles::token($user, $overrides);   // Generate JWT token
FluxFiles::tokenForUser($overrides);   // Token for auth user
FluxFiles::endpoint();                  // Get FluxFiles URL
FluxFiles::iframeSrc();                 // Get iframe source URL
FluxFiles::sdkUrl();                    // Get SDK script URL
```

Configuration
-------------

[](#configuration)

After publishing, edit `config/fluxfiles.php`:

```
return [
    'secret'     => env('FLUXFILES_SECRET'),
    'mode'       => env('FLUXFILES_MODE', 'proxy'),
    'endpoint'   => env('FLUXFILES_ENDPOINT'),

    'route_prefix' => 'api/fm',
    'middleware'    => ['web', 'auth'],

    'disks' => [
        'local' => [...],
        's3'    => [...],
        'r2'    => [...],
    ],

    'defaults' => [
        'perms'       => ['read', 'write', 'delete'],
        'disks'       => ['local'],
        'prefix'      => '',
        'max_upload'  => 10,    // MB
        'allowed_ext' => null,  // null = allow all
        'max_storage' => 0,     // 0 = unlimited
        'ttl'         => 3600,  // seconds
    ],

    'locale'      => env('FLUXFILES_LOCALE', ''),
    'ai_provider' => env('FLUXFILES_AI_PROVIDER', ''),
    'ai_api_key'  => env('FLUXFILES_AI_API_KEY', ''),
    'ai_model'    => env('FLUXFILES_AI_MODEL', ''),
    'ai_auto_tag' => env('FLUXFILES_AI_AUTO_TAG', false),
];
```

### CSRF exclusion (proxy mode only)

[](#csrf-exclusion-proxy-mode-only)

In proxy mode the routes run under the **`web`** middleware group (needed for the session-based `auth` bridge), which includes Laravel's CSRF protection. The FluxFiles SDK authenticates every call with an `Authorization: Bearer `header — **not** a Laravel CSRF token — so the mutating routes (`upload`, `delete`, `move`, `rename`, …) return **419 Page Expired** until you exclude the FluxFiles route prefix from CSRF. Use the value of `config('fluxfiles.route_prefix')`(default `api/fm`) with a `/*` wildcard.

**The file/location differs by Laravel version:**

**Laravel 11 / 12** — there is no `VerifyCsrfToken.php`; configure it in `bootstrap/app.php`:

```
->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'api/fm/*',   // = config('fluxfiles.route_prefix') . '/*'
    ]);
})
```

**Laravel 9 / 10** — add it to the `$except` array in `app/Http/Middleware/VerifyCsrfToken.php`:

```
protected $except = [
    'api/fm/*',   // = config('fluxfiles.route_prefix') . '/*'
];
```

> **Standalone mode** is unaffected — the core runs as its own server with its own Origin-based CSRF check, and Laravel only mints tokens / embeds the iframe.

Permissions
-----------

[](#permissions)

FluxFiles keeps **all of its state on disk** (no database). Two locations must be writable by the user PHP-FPM runs as (usually `www-data`):

PathHoldsCreated when`/_fluxfiles/`search index, folder index, file locks, audit log, trash manifest, metadata sidecarsfirst write to that disk (upload / `mkdir` / …)`config('fluxfiles.storage_path')` (default `storage/fluxfiles/`)proxy-mode rate-limiter counter (`rate_limit.json`)first request — see the dedicated section belowThe `_fluxfiles/` directory lives **inside the disk root** you configure (e.g. `public_path('uploads')` → `public/uploads/_fluxfiles/`). PHP creates it on the first write, so the safest rule is: **let PHP create it — don't pre-create it as `root` or your deploy user.**

```
# Make the uploads tree writable by the web server user (adjust www-data to your
# PHP-FPM user: `ps aux | grep php-fpm | grep -v root`)
sudo chown -R www-data:www-data /path/to/public/uploads
sudo chmod -R u+rwX,g+rwX /path/to/public/uploads
```

> **Symptom → fix.** A `500` with `fopen(.../_fluxfiles/index.lock): Permission denied` (or, on **core ≥ 0.2.7**, a clean `storage_not_writable` error naming the path) means the web server user can't write `_fluxfiles/`. Almost always the directory was pre-created by a different user — `chown` it back to the PHP-FPM user (or delete it and let PHP recreate it):
>
> ```
> sudo chown -R www-data:www-data /path/to/public/uploads/_fluxfiles
> ```

If the disk root is **inside `public/`**, make sure `_fluxfiles/` is not served: its contents are internal (the index can reveal file names). For nginx:

```
location ~ /_fluxfiles/ { deny all; }
```

> On **S3 / R2** disks there is nothing to chmod — `_fluxfiles/` lives in the bucket as regular objects, governed by your IAM policy (`s3:PutObject` etc.). Run the **Bucket Doctor** to verify those grants.

Deployment &amp; permissions (`rate_limit.json`)
------------------------------------------------

[](#deployment--permissions-rate_limitjson)

In **proxy mode** the rate limiter keeps its counter in a JSON file at `config('fluxfiles.storage_path')` — by default `storage/fluxfiles/rate_limit.json`(override with `FLUXFILES_STORAGE_PATH`). PHP creates the directory `0755` and the file **`0600`** automatically on the first request.

What you need on the server:

- The directory must be **writable by the user PHP-FPM runs as** (usually `www-data`). Laravel's `storage/` already requires this, so the standard deploy perms cover it:

    ```
    sudo chown -R www-data:www-data storage bootstrap/cache
    sudo chmod -R 775 storage bootstrap/cache
    ```
- **Let PHP create `rate_limit.json` itself.** It's chmod-ed to `0600` (owner only), so it must be **owned by the PHP-FPM user**. If a deploy script or `root`pre-creates it as another user, PHP-FPM can't read it and every request fails with `500 "Rate limiter unavailable"`. Fix:

    ```
    sudo chown www-data:www-data storage/fluxfiles/rate_limit.json   # or just delete it; PHP recreates it
    ```
- **No web-server rule needed.** Unlike the standalone core, this file lives under Laravel's `storage/` (outside the `public/` web root), so it is never served. Keep the `0600` mode — don't loosen it.
- **Read-only / immutable deploys** (e.g. containers): point the path at a writable volume —

    ```
    FLUXFILES_STORAGE_PATH=/var/lib/fluxfiles
    ```

    ```
    sudo install -d -o www-data -g www-data -m 775 /var/lib/fluxfiles
    ```

> Standalone mode (running the core server directly) puts the file at `packages/core/storage/rate_limit.json` instead — there it **is** under the web root, so block it at the web server (`location /storage/rate_limit.json { deny all; }`).

Using an existing upload directory
----------------------------------

[](#using-an-existing-upload-directory)

If your app already has a directory tree like `public/uploads/user_1/`, `public/uploads/user_2/` (populated before FluxFiles was installed), you can point FluxFiles at it — existing files show up immediately, and a one-shot Artisan command makes them searchable.

### 1. Point the `local` disk at your existing path

[](#1-point-the-local-disk-at-your-existing-path)

In `config/fluxfiles.php`:

```
'disks' => [
    'local' => [
        'driver' => 'local',
        'root'   => public_path('uploads'),     // where your files already live
        'url'    => '/uploads',                 // URL prefix for preview links
    ],
],
```

### 2. Scope each user to their own sub-folder via the `prefix` claim

[](#2-scope-each-user-to-their-own-sub-folder-via-the-prefix-claim)

Always derive the prefix server-side from the authenticated user — never trust client input:

```
use FluxFiles\Laravel\FluxFilesFacade as FluxFiles;

$token = FluxFiles::tokenForUser([
    'prefix' => 'user_' . auth()->id() . '/',
    'disks'  => ['local'],
    'perms'  => ['read', 'write', 'delete'],
]);
```

With `prefix = 'user_1/'`, all API paths are transparently scoped to `public/uploads/user_1/`. User 1 cannot see or touch `user_2/`.

### 3. Filesystem permissions

[](#3-filesystem-permissions)

Make `public/uploads` writable by the PHP process (upload / mkdir / delete):

```
chown -R www-data:www-data public/uploads
chmod -R u+rwX public/uploads
```

This also covers the `_fluxfiles/` index directory FluxFiles writes inside the root — see [Permissions](#permissions) for the common `_fluxfiles/index.lock`permission-denied symptom and fix.

### 4. Seed metadata + folder index for pre-existing content

[](#4-seed-metadata--folder-index-for-pre-existing-content)

Listing existing files works out of the box. Preview links load only when the disk `url` matches a path your web server actually serves. **Search** relies on the FluxFiles metadata index (`_fluxfiles/index.json`) and the directory index (`_fluxfiles/dirs.json`), which are only written when content is created through the API. To make pre-existing files and folders searchable, run the included Artisan command once:

```
# Dry run first — report what would be indexed, no writes
php artisan fluxfiles:seed --disk=local --dry-run

# Apply
php artisan fluxfiles:seed --disk=local

# Generate thumbnails for existing images
php artisan fluxfiles:seed --disk=local --variants

# Add hashes for duplicate detection too
php artisan fluxfiles:seed --disk=local --hash --variants

# Only a sub-tree
php artisan fluxfiles:seed --disk=local --path=user_1

# Force re-index (overwrite any existing metadata)
php artisan fluxfiles:seed --disk=local --overwrite
```

What it does:

- Walks the disk recursively (skipping `_fluxfiles/`, `_variants/`, and `*.meta.json`).
- For each **file**: creates a metadata record with `title` derived from the filename so search can find it. Metadata writes are skipped for files that already have metadata unless `--overwrite` is passed, but `--hash` and `--variants` can still fill in missing hashes/thumbnails.
- For each **folder**: tracks it in `_fluxfiles/dirs.json` so folder search (`/api/fm/search-folders`) can return it.

After seeding, both file and folder search work for the existing tree.

### 5. Notes &amp; gotchas

[](#5-notes--gotchas)

- FluxFiles auto-creates `public/uploads/_fluxfiles/` (metadata index and audit log) and `public/uploads/_variants/` (image thumbnails). These are hidden from the UI — do not delete them. If you use FTP/rsync/backup tools, add them to your ignore list.
- `url = '/uploads'` must match how your web server serves `public/`. Preview links are built as `{url}/{key}` — e.g. file key `user_1/avatar.jpg` → `/uploads/user_1/avatar.jpg`.
- Files uploaded **before** seeding won't have an `uploaded_by` metadata field. If you later enable `owner_only`, legacy files fall through gracefully (all users can act on them) until the next time someone edits them through the UI.
- For S3/R2 disks with an existing bucket, the same seed command works — pass `--disk=s3` (or `--disk=r2`). Listing is slower because it pages the bucket remotely.

Features
--------

[](#features)

- **Blade component** `` with auto token generation
- **Blade directives** `@fluxfilesToken` and `@fluxfilesEndpoint`
- **Facade** `FluxFiles::token()` for programmatic token generation
- **Proxy mode** — serve FluxFiles API through Laravel routes
- **Standalone mode** — connect to a separate FluxFiles server
- **Auto-discovery** — ServiceProvider and Facade register automatically
- **16 languages** — en, vi, zh, ja, ko, fr, de, es, ar, pt, it, ru, th, hi, tr, nl

License
-------

[](#license)

MIT — see [LICENSE](LICENSE) for details.

Links
-----

[](#links)

- [FluxFiles](https://github.com/thai-pc/fluxfiles) — Main repository
- [Documentation](https://github.com/thai-pc/fluxfiles#laravel) — Full docs
- [Issues](https://github.com/thai-pc/fluxfiles/issues) — Bug reports

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance91

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

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 ~2 days

Total

41

Last Release

27d ago

PHP version history (2 changes)v1.22.0PHP &gt;=7.4

v1.26.4PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/32d0caeb5c686e7417b8c55fd0f36faff14a9e726c663bff70b84d23193a5db1?d=identicon)[thai-pc](/maintainers/thai-pc)

---

Tags

laravels3uploadfile managerr2fluxfiles

### Embed Badge

![Health badge](/badges/fluxfiles-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/fluxfiles-laravel/health.svg)](https://phpackages.com/packages/fluxfiles-laravel)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k30.2M151](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M136](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M133](/packages/roots-acorn)[hasinhayder/tyro-dashboard

Tyro Dashboard - Beautiful admin dashboard for managing Tyro roles, privileges, users, and settings

5443.8k](/packages/hasinhayder-tyro-dashboard)[moonshine/moonshine

Laravel administration panel

1.3k253.1k86](/packages/moonshine-moonshine)

PHPackages © 2026

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