Filament Sanchaya
A file manager and media picker for Laravel and Filament.
In Nepali, Sanchaya (सञ्चय) means the act of gathering or amassing something valuable over time. This package follows that idea: your files are gathered in one place, indexed in your database, and managed with a familiar Filament experience.
Full File Manager
Folder tree, grid/list views, cursor pagination, toolbar with search & filters.
SanchayaPicker Field
Drop-in Filament form field for single or multi-file selection with group-based persistence.
Multi-Disk
Switch between any configured Laravel filesystem disk at runtime.
Authorization
Laravel Gate policy controls who can rename, move, copy, delete, or download.
Model Attachments
Polymorphic group-based attachments via the HasSanchayaFiles trait.
Requirements
- Filament
^5.0 - For filament v4 its not tested.
Installation
1. Install via Composer
composer require dp0/filament-sanchaya
2. Run the Interactive Installer
php artisan sanchaya:install
The installer will prompt you to:
- Publish
config/filament-sanchaya.php - Publish and run migrations
- Choose your default storage disk
3. Filament Custom Theme Requirement
Sanchaya uses custom styling and Tailwind CSS utility classes. Without a custom Filament theme, components and colors will not render correctly.
If you do not have a custom theme configured for your Filament panel, generate one:
php artisan make:filament-theme
Add the package's views directory to your custom theme's main CSS file (usually resources/css/filament/admin/theme.css) using the @source directive. Adjust the relative path if your theme is in another directory:
@source "../../../../vendor/dp0/filament-sanchaya/resources/views";
Then make sure your panel actually uses that theme by pointing viteTheme() at it in your panel provider — otherwise your theme (and the package styles) will never be loaded:
public function panel(Panel $panel): Panel
{
return $panel
->viteTheme('resources/css/filament/admin/theme.css')
->plugins([
SanchayaPlugin::make(),
]);
}
Finally, compile your assets using your asset bundler:
npm run dev # or npm run build
Manual Publishing
php artisan vendor:publish --tag=sanchaya-config
php artisan vendor:publish --tag=sanchaya-migrations
php artisan vendor:publish --tag=sanchaya-views
php artisan migrate
Configuration
Edit config/filament-sanchaya.php after publishing.
return [
// Eloquent model for file records
'model' => \DP0\Sanchaya\Models\SanchayaFile::class,
// Eloquent model for polymorphic attachments
'attachment_model' => \DP0\Sanchaya\Models\SanchayaAttachment::class,
// Gate policy for file operations. null = no authorization.
'policy' => \DP0\Sanchaya\Policies\SanchayaFilePolicy::class,
// true = soft deletes (recoverable); false = hard delete
'soft_deletes' => true,
'allowed_disks' => null,
'default_disk' => env('SANCHAYA_DEFAULT_DISK', 'public'),
'file' => [
'max_file_size' => 10240, // KB — 10 MB
'accepted_file_types' => [], // e.g. ['image/*']
],
'storage_quota' => null, // bytes; null = unlimited
'actions' => [
'download' => [
'enabled' => true,
'label' => 'Download',
'icon' => 'heroicon-m-arrow-down-tray',
'class' => \DP0\Sanchaya\Actions\DownloadAction::class,
],
'create_folder' => [
'enabled' => true,
'label' => 'Create Folder',
'icon' => 'heroicon-m-folder-plus',
'class' => \DP0\Sanchaya\Actions\CreateFolderAction::class,
],
'rename' => [
'enabled' => true,
'label' => 'Rename',
'icon' => 'heroicon-m-pencil',
'class' => \DP0\Sanchaya\Actions\RenameAction::class,
],
'move' => [
'enabled' => true,
'label' => 'Move',
'icon' => 'heroicon-m-arrow-right-circle',
'class' => \DP0\Sanchaya\Actions\MoveAction::class,
],
'copy' => [
'enabled' => true,
'label' => 'Copy',
'icon' => 'heroicon-m-document-duplicate',
'class' => \DP0\Sanchaya\Actions\CopyAction::class,
],
'delete' => [
'enabled' => true,
'label' => 'Delete',
'icon' => 'heroicon-m-trash',
'class' => \DP0\Sanchaya\Actions\DeleteAction::class,
],
],
'sanchaya_picker' => [
'multiple' => false,
'allowed_types' => [], // e.g. ['image']
'max_files' => null,
],
];
Config Reference
| Key | Default | Description |
|---|---|---|
model | SanchayaFile | Eloquent class for file records. |
attachment_model | SanchayaAttachment | Eloquent class for pivot. |
policy | SanchayaFilePolicy | Gate policy controlling file operations. |
soft_deletes | true | Whether deletes are recoverable. |
allowed_disks | null | List of visible disks; null allows all configured disks. |
default_disk | public | Disk shown when the manager opens. |
file.max_file_size | 10240 | Upload size limit in KB. |
file.accepted_file_types | [] | Accepted upload MIME types; empty allows all types. |
storage_quota | null | Per-disk upload quota in bytes, based on active indexed files; null means unlimited. |
actions.*.enabled | true | Enable or disable a manager action. preview is UI-only; the other actions can use custom classes. |
actions.*.label | Action label | Text shown for the action in buttons, menus, and notifications. |
actions.*.icon | Heroicon name | Icon shown for the action in the file manager. |
actions.*.class | Action class | Override the service class resolved for download, create_folder, rename, move, copy, or delete. |
sanchaya_picker | Single selection | Default multiple, allowed_types, and max_files values for picker fields. |
Plugin Registration
Register in your Filament panel provider:
use DP0\Sanchaya\SanchayaPlugin;
public function panel(Panel $panel): Panel
{
return $panel->plugins([
SanchayaPlugin::make(),
]);
}
Fluent Options
SanchayaPlugin::make()
->navigationLabel('Filament Sanchaya')
->navigationIcon('heroicon-o-folder')
->navigationGroup('Content')
->navigationSort(20);
The default sidebar label is Filament Sanchaya. Use navigationLabel('Media') if you want a shorter label, slug('media') to change the page path, or withoutNavigation() to hide its sidebar entry. The default slug is sanchaya.
File Manager & Bulk Actions
Register the plugin in your Filament panel to add the file manager. Its URL uses your panel's path followed by /sanchaya by default.
| Area | Features & Capabilities |
|---|---|
| Toolbar | Disk switcher, live debounced search, MIME group filter (Images, Videos, Audio, Documents), Date range filter, and Grid / List view mode toggles. |
| Sidebar | Full recursive folder tree navigation with instant subtree expanding and Trash status count badge. |
| Detail Panel | Live preview, file dimensions, MIME type, size, disk path, copyable public URL, and editable Metadata & SEO (Alt Text, Title, Caption, Description). |
| Bulk Actions | Select multiple files/folders via checkboxes to perform Bulk Move (with folder tree picker), Bulk Copy, Bulk Download (ZIP), or Bulk Delete. Moving a folder between disks updates nested file paths and transfers their contents. |
When selecting multiple items to move or copy, Sanchaya opens an interactive modal allowing you to choose any destination folder in the hierarchy or Root, preserving nested subfolder structures automatically.
Trash Bin & Soft Deletes
When soft_deletes is enabled (the default), deleting files or folders marks their database records as deleted and keeps file contents on disk for restoration. The Trash sidebar entry is hidden when soft deletes are disabled.
Key Capabilities
- Dedicated Bin View: Access the Trash Bin directly from the sidebar. Shows all soft-deleted items on the current disk.
- One-Click Restore: Restore individual items from the context dropdown or select multiple items and click
Restore Selected. - Permanent Deletion: Delete individual or selected trashed items to remove stored file contents and database records. Deleting a folder also removes its nested files.
- Empty Trash: Purge all trashed files and folders across the selected disk with a single click.
// config/filament-sanchaya.php
'soft_deletes' => true, // Set to false to immediately force-delete files from disk
Permanent deletion cannot be undone. With soft_deletes set to false, the ordinary Delete action is permanent.
SanchayaPicker Form Field
The SanchayaPicker form component lets users pick existing files or upload new files directly inside any Filament Resource Form or Schema.
Add HasSanchayaFiles to the form's model to persist the selected attachments automatically. Chunked uploads and image manipulation are planned for the next release.
use DP0\Sanchaya\Forms\Components\SanchayaPicker;
Single File Attachment
SanchayaPicker::make('avatar')
->label('Avatar Image')
->saveInGroup('avatar')
->allowedTypes(['image']);
Multiple Files Gallery
SanchayaPicker::make('gallery')
->label('Product Gallery')
->multiple()
->maxFiles(8)
->saveInGroup('gallery')
->allowedTypes(['image'])
->uploadMaxFileSize(5120); // 5 MB upload limit
Method Reference
| Method | Description |
|---|---|
multiple(bool $condition = true) | Enable multi-select mode. |
maxFiles(int $count) | Cap the maximum number of selectable files in multiple mode. |
allowedTypes(array $types) | Limit picker results by MIME group, such as ['image', 'video']. |
saveInGroup(string $group) | Polymorphic attachment group name for automatic relation sync. |
uploadMaxFileSize(int $kb) | Per-field upload size limit in KB. |
reorderable(bool $condition = true) | Allow reordering selected files in multiple mode. |
disk(string $disk) | Set the initial disk shown by this picker. |
uploadAcceptedFileTypes(array $types) | Override accepted upload MIME types for this picker. |
SanchayaColumn Table Column
Display polymorphic media files directly inside your Filament Table using SanchayaColumn. It automatically resolves images attached via HasSanchayaFiles.
use DP0\Sanchaya\Tables\Columns\SanchayaColumn;
Single Avatar Column
SanchayaColumn::make('avatar')
->label('User Avatar')
->sanchayaGroup('avatar')
->circular();
Stacked Multi-Image Gallery Column
SanchayaColumn::make('gallery')
->label('Images')
->sanchayaGroup('gallery')
->multiple()
->stacked()
->limit(3)
->limitedRemainingText();
Method Reference
| Method | Description |
|---|---|
sanchayaGroup(?string $group) | Attachment group to resolve from the record. Defaults to column name. |
multiple(bool $condition = true) | Display all attached images in the group. |
single() | Display only the first image in the group. |
circular(bool $condition = true) | Render images with rounded circular borders. |
stacked(bool $condition = true) | Stack multiple images with overlap. |
limit(int $limit) | Cap the number of visible avatars in the stack. |
limitedRemainingText() | Display "+N" badge for remaining hidden images. |
SanchayaEntry Infolist Entry
Render attached media inside your Filament Infolists with group resolution.
use DP0\Sanchaya\Infolists\Components\SanchayaEntry;
Single & Multiple Infolist Entries
SanchayaEntry::make('avatar')
->label('User Photo')
->group('avatar')
->circular();
SanchayaEntry::make('documents')
->label('Project Files')
->group('documents')
->multiple();
HasSanchayaFiles Trait
Add the HasSanchayaFiles trait to any Eloquent model to enable polymorphic, grouped media attachments without altering your table schemas.
use DP0\Sanchaya\Traits\HasSanchayaFiles;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasSanchayaFiles;
}
Reading Attachments
// Get Eloquent Collection of SanchayaFile in 'gallery' group
$gallery = $post->sanchayaFiles('gallery');
// Get first file in 'hero' group
$heroFile = $post->sanchayaFile('hero');
// Get public or temporary URL directly
$heroUrl = $post->sanchayaUrl('hero');
// Get array of file IDs
$ids = $post->sanchayaFileIds('gallery');
// Check if any files are attached
$hasAvatar = $post->hasSanchayaFiles('avatar');
Writing Attachments
// Attach single file
$post->attachSanchayaFile($fileId, 'avatar');
// Sync a group to an exact ordered list of IDs (removes missing, adds new)
$post->syncSanchayaFiles([12, 14, 19], 'gallery');
// Detach all files in a group
$post->detachSanchayaFiles('gallery');
// Detach a specific file
$post->detachSanchayaFile($fileId, 'gallery');
SanchayaFile & Metadata / SEO
The SanchayaFile Eloquent model represents every indexed file and directory in Sanchaya.
Metadata & SEO Properties
| Property / Accessor | Type | Description |
|---|---|---|
$file->alt_text | string | Image alternative text for accessibility & SEO. |
$file->title | string | Human-friendly title attribute. |
$file->caption | string | Media caption for galleries. |
$file->description | string | Long-form description / notes. |
$file->getMetadata($key, $default) | mixed | Access metadata using dot-notation (e.g. $file->getMetadata('exif.camera')). |
$file->setMetadata($key, $value) | SanchayaFile | Set custom metadata using dot-notation; call save() to persist it. |
Core Accessors & Scopes
| Accessor / Scope | Type | Description |
|---|---|---|
$file->display_name | string | Original upload name or formatted folder name. |
$file->url | ?string | Resolved storage URL. |
$file->preview_url | ?string | Accessible public or temporary file URL when the disk supports one. |
$file->human_size | string | Formatted size string (e.g., "3.2 MB"). |
$file->is_image / is_video | bool | MIME type helpers. |
SanchayaFile::query()->folders() | Query | Filter only folders. |
SanchayaFile::query()->files() | Query | Filter only files. |
SanchayaFile::query()->onDisk($disk) | Query | Filter by filesystem disk. |
SanchayaFile::query()->ofMimeGroup('image') | Query | Filter by MIME group. |
Database Schema
sanchaya_files
| Column | Type | Notes |
|---|---|---|
id | bigint unsigned | Primary key |
parent_id | bigint unsigned (nullable) | Self-referencing parent folder foreign key |
type | string | file or folder |
disk | string | Filesystem disk name |
path | text | Full relative path on disk |
file_name | string | Physical file name on disk |
original_name | string | Original user-facing file name |
extension | string (nullable) | File extension (lowercase) |
mime_type | string (nullable) | Detected MIME type |
size | bigint unsigned | Size in bytes (0 for folders) |
metadata | json (nullable) | JSON object storing alt text, SEO, and custom attributes |
deleted_at | timestamp (nullable) | Soft delete timestamp |
sanchaya_attachments
| Column | Type | Notes |
|---|---|---|
id | bigint unsigned | Primary key |
sanchaya_file_id | bigint unsigned | Foreign key referencing sanchaya_files.id |
attachable_type | string | Polymorphic model class name |
attachable_id | unsignedBigInteger | Polymorphic model ID created by morphs() |
group | string (nullable) | Slot/category name (e.g., 'avatar', 'gallery') |
order | integer | Sort position for multiple attachments |
Built-in Actions
Every file management operation in Sanchaya is isolated into a dedicated action class, making it completely unit-testable and extensible.
| Action | Class | Description |
|---|---|---|
CreateFolder | CreateFolderAction | Creates a folder record and builds hierarchical paths. |
Rename | RenameAction | Renames files on disk and cascades path updates to all child files/folders. |
Move | MoveAction | Moves files or folders to another folder or disk, transfers file contents, and updates descendant paths. |
Copy | CopyAction | Deep copies files or entire folder subtrees with conflict resolution naming. |
Delete | DeleteAction | Handles soft deletion, cascade restoration, and permanent file/storage purging. |
Download | DownloadAction | Streams single files inline/attachment or zips entire folders/bulk selections on the fly. |
Extensibility & Customization
Sanchaya resolves its main behavior from configuration, so production apps can replace the default Eloquent models, authorization policy, and file-operation action classes without editing package files.
Replace Models and Policy
Use custom models when you need extra casts, relationships, scopes, observers, or table names. Extend the package models so the file manager keeps the expected relationships and helpers.
// config/filament-sanchaya.php
'model' => \App\Models\MediaFile::class,
'attachment_model' => \App\Models\MediaAttachment::class,
'policy' => \App\Policies\MediaFilePolicy::class,
namespace App\Models;
use DP0\Sanchaya\Models\SanchayaFile;
class MediaFile extends SanchayaFile
{
protected $table = 'sanchaya_files';
protected $casts = [
'metadata' => 'array',
];
}
When both default models are replaced, publish and own the migrations in your application. Sanchaya only auto-loads package migrations for the default file or attachment models.
Override Action Classes
Each configured action is resolved from Laravel's container. Create a class with the same public method contract as the action you are replacing; extending the shipped class is the safest starting point.
// config/filament-sanchaya.php
'actions' => [
'delete' => [
'enabled' => true,
'label' => 'Delete',
'icon' => 'heroicon-m-trash',
'class' => \App\Actions\MyCustomDeleteAction::class,
],
],
namespace App\Actions;
use DP0\Sanchaya\Actions\DeleteAction;
use DP0\Sanchaya\Models\SanchayaFile;
class MyCustomDeleteAction extends DeleteAction
{
public function execute(SanchayaFile $item): bool
{
// Add audit logging, external cleanup, or business checks here.
return parent::execute($item);
}
}
| Config key | Default class | Methods used by the manager |
|---|---|---|
actions.download.class | DownloadAction | execute($file), bulk($items) |
actions.create_folder.class | CreateFolderAction | execute($name, $disk, $parentId) |
actions.rename.class | RenameAction | execute($item, $newName) |
actions.move.class | MoveAction | execute($item, $destinationId, $destinationDisk) |
actions.copy.class | CopyAction | execute($item, $destinationId, $destinationDisk) |
actions.delete.class | DeleteAction | execute($item), bulk($items), restore($item), forceDeleteTrashed($item) |
The preview action has configurable visibility, label, and icon, but it does not resolve a backend action class.
Automated Testing
Sanchaya includes an automated test suite covering actions, models, traits, form/table/infolist components, and Livewire browser interactions. Run these commands from a Laravel application that includes the package source and its test namespace; they are for package development, not consumer installation.
# Run all tests via PHPUnit or Artisan
php artisan test packages/dp0/filament-sanchaya/tests
# Run specific suite
php artisan test --filter=CreateFolderActionTest
Troubleshooting
If layouts appear unstyled or colors are missing, ensure you have a custom theme configured. Because Filament uses Tailwind CSS v4, you must add the package views directory to your custom theme's main CSS file using the @source directive: @source "../../../../vendor/dp0/filament-sanchaya/resources/views"; and rebuild your assets (npm run build).
The active Gate policy is returning false. Debug with Gate::inspect() or configure your policy in config/filament-sanchaya.php.
Confirm your disk is defined in config/filesystems.php and listed in allowed_disks (or leave allowed_disks => null to allow all configured disks).