Recipe — File Uploads
Goal: Let users upload files on a form and retrieve them on the model — with proper temp-file handling, max-count limits, and type filtering.
Time: ~5 minutes.
Pick the Right Mechanism
Modularous offers three file options — choose based on your use case:
| Mechanism | When | Complexity |
|---|---|---|
Filepond (HasFileponds) | Direct 1:N uploads (attachments, avatars) | Simplest |
Files (HasFiles) | Shared file library reused across records | Medium |
Media (HasImages) | Images with cropping / role / locale variants | Richest |
This recipe covers Filepond, which is right for 90% of cases. For the others, see Files and Media.
1. Add the trait to your model
use Unusualify\Modularous\Entities\Traits\HasFileponds;
class Ticket extends Model
{
use HasFileponds;
}This installs a morphMany(Filepond::class, 'filepondable') relation and the accessors fileponds(), getFileponds(), hasFilepond().
2. Add the trait to your repository
use Unusualify\Modularous\Repositories\Traits\FilepondsTrait;
class TicketRepository extends Repository
{
use FilepondsTrait;
}FilepondsTrait handles moving files from the temporary uploads table to permanent storage on save, and reverting on rollback.
3. Declare the input in your hydrate
public function getInputs(): array
{
return [
['type' => 'text', 'name' => 'title'],
[
'type' => 'filepond',
'name' => 'attachments',
'max' => 5,
'maxFileSize' => '10MB',
'acceptedExtensions' => ['pdf', 'doc', 'docx', 'png', 'jpg'],
],
];
}See input-filepond for every option.
4. Use the files at runtime
Listing
$ticket = Ticket::find(1);
// All fileponds regardless of role
$ticket->fileponds;
// Fileponds scoped to a named role
$ticket->getFileponds('attachments');
// Boolean
if ($ticket->hasFilepond('attachments')) {
// ...
}In Blade / email templates
@foreach ($ticket->getFileponds('attachments') as $file)
<a href="{{ $file->url }}" download>{{ $file->name }}</a>
@endforeachIn Vue / API responses
Files are automatically serialized when the model is appended with fileponds:
// In your resource or repository
$invoice->append(['fileponds']);5. Verify
- Open the create form — you should see a drag-and-drop Filepond input.
- Drop a file — a temporary row appears in
temporary_fileponds. - Submit the form — the row moves to
filepondsand the temp row is deleted. - Re-open the record — the file shows up attached.
Variations
Single-file avatar upload
[
'type' => 'filepond',
'name' => 'avatar',
'max' => 1,
'acceptedExtensions' => ['png', 'jpg', 'jpeg', 'webp'],
]Or use the avatar preset input input-filepond-avatar for the circular crop UI. See input-filepond-avatar.
Document-only with strict size limit
[
'type' => 'filepond',
'name' => 'documents',
'max' => 10,
'maxFileSize' => '5MB',
'acceptedExtensions' => ['pdf', 'doc', 'docx', 'xls', 'xlsx'],
]Cropable images with roles
Use HasImages + input-image instead. See Files and Media — Media / Images.
Housekeeping
Temporary Filepond rows that never get saved accumulate over time. Schedule the cleanup command:
php artisan modularous:flush:filepondOr add it to app/Console/Kernel.php:
$schedule->command('modularous:flush:filepond')->daily();See flush:filepond.
Next Steps
- File Storage with Filepond — storage mechanics and database layout
- Files and Media — the full triple pattern
- Uploader component — the Vue upload widget