Choose the database shape
Imports used below:
One file in a model column
Keep the column and field names identical. A normal Filament Resource create/edit page saves the dehydrated path with its model data:Several files in a JSON column
InPost:
photo_names is an optional path-to-original-name map for display. The main array remains a list of paths. The standard Resource lifecycle accepts stored media after the saved record references it. Cleanup choices are independent of this persistence mapping.
Your own gallery table: three galleries on one post
AssumePost::gallery() is a hasMany(Gallery::class) relation. The gallery table has post_id, collection, disk, path, original_name and sort_order. Add a unique constraint on (post_id, collection, disk, path) and an index on (post_id, collection, sort_order). Authorize the post before mounting/saving; scope the relation to the current tenant where appropriate.
Use three different field names and collection keys. These are virtual form values; do not add them to the posts table just to run this recipe:
Post and Gallery above are your application models. allowFilePathUsing authorizes existing relation paths; mediaReferenceUsing proves that the database saved a newly stored path. They serve different purposes. The example leaves physical-file deletion to your retention workflow so removing an item from an unsaved form cannot destroy the previous saved gallery.
Restore the same three galleries on edit
Configure the form with->model($post)->statePath('data'), where data is a public array. Fill only from the authorized post relation:
Save files and relation rows together
The following is a custom Livewire form save method. Its schema contains the fields above and atitle input. $this->record is an authorized existing Post or null; $this->data is the form state. On create, set the form model to Post::class until the new post exists. Use your application’s additional required columns and policy checks in the indicated places.
key() and resolve its actual schema key rather than assuming the root field name. Completion must use the mounted field instance. It waits for the database transaction to commit and verifies each saved relation reference. Do not replace FLEX’s saveRelationshipsUsing() just to persist gallery rows: replacing that hook also replaces its acceptance work.
This example creates gallery entries on successful form save. Creating rows as soon as a file finishes temporary transfer needs an explicit draft/pending-record workflow, ownership and abandoned-draft cleanup. Stored alone does not prove the post transaction committed. Use events and custom save acceptance for those workflows.
A Resource can use the same mapping in its create/update data hooks and model handlers: remove virtual gallery values from the attributes sent to Post, write the scoped relation rows in the save transaction, and complete the mounted upload fields after those rows exist. Never mass-assign the entire browser state into a gallery model.
Keep an existing files table
You do not have to migrate its schema to Spatie. Adapt the relation recipe:
Group files by disk/visibility; one disk field is not a mixed-disk picker. Do not feed full legacy URLs or numeric gallery IDs into a path-based field. Resolve IDs to authorized relative paths on the server and map saved paths back to your table. A separate migration is needed if the old database only contains URLs with no storage ownership/path mapping.
Replace native Filament FileUpload
If the existing field already saves Laravel paths, change the class import and preserve its field name,multiple(), disk, directory, visibility, casts and original-name companion column:
saveUploadedFileUsing()/getUploadedFileUsing() overrides individually; replacing those callbacks can bypass the Media OS storage or read bridge. Prefer the dedicated options in these pages.
For a native Spatie field, replace it with FlexSpatieMediaLibraryFileUpload, retaining the authorized record, collection, disks, filters and model conversion names. Existing media UUIDs/relationships remain Spatie-owned; do not convert them into disk paths.
Use thumbnails generated by an older system
Keep the original’s relative path as field state.previewUrlUsing() overrides only the saved-file thumbnail after the original passes the bridge’s ownership/read checks. Its named arguments are file (disk path or Spatie UUID), defaultUrl, component and record. Return null to retain the normal preview; pass null to the setter to remove the override.
photos is a virtual field instead of a model column. The example requires your legacy thumbnail_path column. Return a trusted application-relative or HTTP(S) URL; active schemes, credentials and malformed URLs reject. Do not use a browser-supplied thumbnail path. For private storage, resolve a short-lived signed URL through your authorized application delivery service instead of making it public.
The list/grid thumbnail uses that small image. The lightbox defaults to the original through Media OS. Choose another named large conversion with fileViewerConversion('large'), or resolve a trusted legacy large URL with fileViewerUrlUsing(). The image editor, Open and Download retain the original. Store the large original path as state; do not put the thumbnail path there. The original must remain available on its declared disk. This resolver does not import old conversions into FLEX manifests, regenerate images or delete old thumbnail files. Your old conversion-retention policy remains yours.
Generate conversions for new uploads
previewUrlUsing() for existing external derivatives, or a deliberate migration job. For Spatie, retain model registerMediaConversions() and use previewConversion('thumb'); do not also enable a second disk conversion engine. Image and queue configuration.
Several Spatie galleries
Post implementing Spatie HasMedia/InteractsWithMedia. Native relationship persistence handles create/edit records; each collection remains independent. Register collection disks and conversions on your model. Complete Spatie setup.
Choose your own stored filename
invoice.pdf is possible only when the destination is unique/empty: Media OS refuses to overwrite existing files. Changing this generator affects new files; existing paths and legacy names still load unchanged. Filename and collision rules.