Filament Sanchaya

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

⚠️
Required — not optional

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

KeyDefaultDescription
modelSanchayaFileEloquent class for file records.
attachment_modelSanchayaAttachmentEloquent class for pivot.
policySanchayaFilePolicyGate policy controlling file operations.
soft_deletestrueWhether deletes are recoverable.
allowed_disksnullList of visible disks; null allows all configured disks.
default_diskpublicDisk shown when the manager opens.
file.max_file_size10240Upload size limit in KB.
file.accepted_file_types[]Accepted upload MIME types; empty allows all types.
storage_quotanullPer-disk upload quota in bytes, based on active indexed files; null means unlimited.
actions.*.enabledtrueEnable or disable a manager action. preview is UI-only; the other actions can use custom classes.
actions.*.labelAction labelText shown for the action in buttons, menus, and notifications.
actions.*.iconHeroicon nameIcon shown for the action in the file manager.
actions.*.classAction classOverride the service class resolved for download, create_folder, rename, move, copy, or delete.
sanchaya_pickerSingle selectionDefault 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.

Authorization & Policy

Sanchaya ships with SanchayaFilePolicy, which permits authenticated users by default. Register a restrictive policy for production access control.

💡
Custom Policy

The service provider calls Gate::policy() during boot — but only if you haven't already registered one.

// config/filament-sanchaya.php
'policy' => \App\Policies\MyFilePolicy::class,

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.

AreaFeatures & Capabilities
ToolbarDisk switcher, live debounced search, MIME group filter (Images, Videos, Audio, Documents), Date range filter, and Grid / List view mode toggles.
SidebarFull recursive folder tree navigation with instant subtree expanding and Trash status count badge.
Detail PanelLive preview, file dimensions, MIME type, size, disk path, copyable public URL, and editable Metadata & SEO (Alt Text, Title, Caption, Description).
Bulk ActionsSelect 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.
💡
Bulk Move & Copy

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

MethodDescription
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

MethodDescription
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 / AccessorTypeDescription
$file->alt_textstringImage alternative text for accessibility & SEO.
$file->titlestringHuman-friendly title attribute.
$file->captionstringMedia caption for galleries.
$file->descriptionstringLong-form description / notes.
$file->getMetadata($key, $default)mixedAccess metadata using dot-notation (e.g. $file->getMetadata('exif.camera')).
$file->setMetadata($key, $value)SanchayaFileSet custom metadata using dot-notation; call save() to persist it.

Core Accessors & Scopes

Accessor / ScopeTypeDescription
$file->display_namestringOriginal upload name or formatted folder name.
$file->url?stringResolved storage URL.
$file->preview_url?stringAccessible public or temporary file URL when the disk supports one.
$file->human_sizestringFormatted size string (e.g., "3.2 MB").
$file->is_image / is_videoboolMIME type helpers.
SanchayaFile::query()->folders()QueryFilter only folders.
SanchayaFile::query()->files()QueryFilter only files.
SanchayaFile::query()->onDisk($disk)QueryFilter by filesystem disk.
SanchayaFile::query()->ofMimeGroup('image')QueryFilter by MIME group.

Database Schema

sanchaya_files

ColumnTypeNotes
idbigint unsignedPrimary key
parent_idbigint unsigned (nullable)Self-referencing parent folder foreign key
typestringfile or folder
diskstringFilesystem disk name
pathtextFull relative path on disk
file_namestringPhysical file name on disk
original_namestringOriginal user-facing file name
extensionstring (nullable)File extension (lowercase)
mime_typestring (nullable)Detected MIME type
sizebigint unsignedSize in bytes (0 for folders)
metadatajson (nullable)JSON object storing alt text, SEO, and custom attributes
deleted_attimestamp (nullable)Soft delete timestamp

sanchaya_attachments

ColumnTypeNotes
idbigint unsignedPrimary key
sanchaya_file_idbigint unsignedForeign key referencing sanchaya_files.id
attachable_typestringPolymorphic model class name
attachable_idunsignedBigIntegerPolymorphic model ID created by morphs()
groupstring (nullable)Slot/category name (e.g., 'avatar', 'gallery')
orderintegerSort 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.

ActionClassDescription
CreateFolderCreateFolderActionCreates a folder record and builds hierarchical paths.
RenameRenameActionRenames files on disk and cascades path updates to all child files/folders.
MoveMoveActionMoves files or folders to another folder or disk, transfers file contents, and updates descendant paths.
CopyCopyActionDeep copies files or entire folder subtrees with conflict resolution naming.
DeleteDeleteActionHandles soft deletion, cascade restoration, and permanent file/storage purging.
DownloadDownloadActionStreams 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 keyDefault classMethods used by the manager
actions.download.classDownloadActionexecute($file), bulk($items)
actions.create_folder.classCreateFolderActionexecute($name, $disk, $parentId)
actions.rename.classRenameActionexecute($item, $newName)
actions.move.classMoveActionexecute($item, $destinationId, $destinationDisk)
actions.copy.classCopyActionexecute($item, $destinationId, $destinationDisk)
actions.delete.classDeleteActionexecute($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

🚨
Broken Styles or Layout

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).

⚠️
403 Forbidden

The active Gate policy is returning false. Debug with Gate::inspect() or configure your policy in config/filament-sanchaya.php.

ℹ️
Missing Disks

Confirm your disk is defined in config/filesystems.php and listed in allowed_disks (or leave allowed_disks => null to allow all configured disks).