Skip to main content
An upload has three stages: transfer into Livewire temporary storage, permanent file storage when the form dehydrates, and database persistence of the file reference. A finished progress bar confirms the first stage. Reloading an unsaved playground/form does not save its state. To restore files after reload, save the model or relation and fill the form from that saved data. For a complete application example, follow A complete Post gallery: migrations, models, authorization, a create/edit page, custom stored filenames, named conversions and an optional SQL conversion index. The code is provided as application examples; FLEX does not install those tables. To display saved images on your website or return them in JSON, continue with Gallery images in your website and API. It explains when a native conversion index is useful and why Spatie normally does not need one.

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:
The string column stores a relative disk path, not the signed/public URL, temporary filename or file label. On edit, native hydration loads that path from the authorized record. Your Resource policies still control access to that record.

Several files in a JSON column

In Post:
In the form:
Use JSON columns for both arrays. 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

Assume Post::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:
Inside your form schema, alongside a title field:
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 a title 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.
For nested fields, give the component an explicit 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:
Saved paths are hydrated from the same model attribute. A change of class does not copy files, rewrite database paths or regenerate old conversions. Keep existing signing/CORS settings. Review old low-level 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.
Combine this with the gallery path authorization above when 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

Native conversion manifests retain the original and owned variants. Existing unconverted originals fall back to the original; they are not automatically reprocessed on edit. Use 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

Attach the form to an authorized 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

You control the basename. Use a unique, trusted name and the inspected extension; do not insert a path or unsanitized customer filename. A fixed name such as 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.

Verify persistence before deployment

Save a record, reload its edit form, verify file order/names and open an original. Test a second record and all three collection keys so files cannot cross owners/collections. Test removal plus a failed save separately from successful cleanup. A successful upload in the playground demonstrates transfer and UI, not your application’s database mapping.
Last modified on October 17, 2026