awcodes/filament-curator
A media picker plugin for FilamentPHP.
v5.3.2
409K downloads/mo
#4308 most downloaded on Packagist
awcodes/filament-curator
What this package is like to depend on
Last release 9 days ago
14 Aug 2026
Ships fairly regularly
a new release about every 2 weeks
Rarely documented
notes for 10 of 240 stable releases
Nothing withdrawn
no release was ever pulled
4 years old
261 releases · first in 2022
47 releases in the last 12 months
see the full history below
Release timeline
261 releases · Apr 2022 to Aug 2026Releases
latest 60 of 261-
v5.3.214 Aug 2026Release notes
Open source →Security release
Fixes an SVG sanitization bypass and unvalidated curation paths. See GHSA-3xm3-q2fj-x8rq for the full advisory.
- SVG sanitization could be bypassed by renaming the file. Sanitization was gated on the client-supplied filename extension while acceptance and the served content type were decided from the file's contents, so SVG markup uploaded as
payload.txtwas stored unsanitized. Sanitizing now keys off the detected type as well as the extension, in both the uploader andCuratorUtils::importMedia, and the serving layer pinsContent-Typefrom the stored extension instead of the sniffed bytes. - The curation modal wrote to a client-supplied path.
saveCuration()consumed the crop payload unvalidated, so a key such as../../othercould overwrite a sibling file inside the storage disk. The payload is now validated before anything is written. curator:sanitize-svgsnow selects on the detected type as well as the extension, so rows stored under a spoofed filename are no longer invisible to it.
After upgrading
Re-scan stored media to clean up anything already on disk:
php artisan curator:sanitize-svgs --dry-run # report only php artisan curator:sanitize-svgsThe serving-layer change already prevents affected rows from rendering as documents, so this is cleanup rather than the primary fix.
Also in this release
- Removed the unused
exifform view. It was not referenced anywhere in the package and could not render as shipped, since it requested an Alpine component that is never registered. If you published views withvendor:publish --tag=curator-views, your local copy is untouched by this removal and still contains a raw{!! !!}echo of EXIF metadata — escape or delete it.
Reported by Afsana Alijabarova (@afa114).
- SVG sanitization could be bypassed by renaming the file. Sanitization was gated on the client-supplied filename extension while acceptance and the served content type were decided from the file's contents, so SVG markup uploaded as
-
v5.3.114 Aug 2026Release notes
Open source →What's Changed
- fix: align Media docblock with the nullable migration columns by @awcodes in #722
- fix: resolve string media keys in the glider component by @awcodes in #723
Full Changelog: v5.3.0...v5.3.1
-
v5.3.012 Aug 2026 -
v5.2.008 Aug 2026Release notes
Open source →Added
The media picker's search now matches each term on its own instead of looking for the whole input as a single substring. A file named
my-image.pngcould not previously be found by typing "my image".The search is split on whitespace, hyphens and underscores, and every term has to match — though any of the five searchable columns (
name,title,alt,caption,description) may be the one matching it. Somy-image.pngis now found by "my image", "my_image", "my-image", or "image my", whilemy-document.pngstays out of those results.LIKEwildcards typed into the search are now escaped, so searching for100%looks for a literal percent sign rather than matching everything. Worth knowing that_has always been an unescaped single-character wildcard, sophoto_8-sunsetmatchedphoto-8-sunsetby accident while a hyphen did nothing; that inconsistency is gone.Case sensitivity is still left to the database — Postgres
LIKEis case-sensitive where MySQL and SQLite are not. That is unchanged from previous versions.Thanks to @amywestlake for reporting this and proposing the original approach in #623.
Fixed
Search results are now ordered by the panel's sort direction, matching the unfiltered list. There was no
ORDER BYat all before, so the 50-row limit truncated an arbitrary slice of matches.GliderFallback's setters were typed to require a value, while every property except the name is optional and every getter already returnednull. Building a fallback from a conditional expression — the usual reason to have one — threw aTypeErrorin a service provider before the application could boot:GliderFallback::make('logo') ->alt(config('app.name')) ->source(filled(setting('logo')) ? $logo->url : Vite::image('logo.webp'))
alt(),height(),source(),type()andwidth()now all acceptnull.A registered fallback that ends up with no source still cannot be rendered, but it now names itself —
The [logo] glider fallback does not have a source.— instead of reporting the media item as invalid, which pointed at the wrong thing.Thanks again to @battulga0719 for reporting this in #718.
Upgrading
No changes are required for ordinary use. Two notes:
- If you extended
GliderFallbackand overrode any of the setters with the narrowerstring/intsignature, widen it to?string/?intto match the parent. - Typing
0into the picker search previously reset the list to show everything. It now searches for "0"; whitespace-only input is what falls back to the unfiltered list.
- If you extended
-
v5.1.505 Aug 2026Release notes
Open source →Security
Curator's upload components fell back to a default list of accepted file types that included
text/html,application/xhtml+xml,text/javascript,application/xmlandapplication/octet-stream. An authenticated user with permission to upload media could upload an HTML file containing a<script>tag. The file was stored unmodified and later served from the application's own origin, executing the script with the session of whoever opened it.This affected the Media resource form,
MultiUploadAction,CuratorPickerand the rich editor's attachment flow, which all share the same default. Applications that already setacceptedFileTypes()themselves — globally or per field — were never affected, because the vulnerable list was only used as a fallback.The default is now
MimeType::defaults(): the full list minus types that are effectively executable content.MimeType::toArray()is unchanged, so code referencing the enum directly still works. As defense in depth, media served through Curator's own route now sendsX-Content-Type-Options: nosniff, and restricted types are forced toContent-Disposition: attachment.If your application genuinely needs to host these types, opt back in explicitly:
use Awcodes\Curator\Enums\MimeType; Curator::acceptedFileTypes([...MimeType::defaults(), 'text/html']);
Note that Curator only sanitizes SVG uploads. Anything else you allow is stored and served verbatim, and with the default
publicdisk it is also reachable through thestoragesymlink, where the new response headers do not apply. Serve deliberately-allowed executable types from a private disk.The 3.x line is not affected — its accepted types come from
config('curator.accepted_file_types'), whose default has always been limited to images and PDFs.Fixed
Glider fallbacks could not be used at all. Several faults compounded:
GliderFallback::make()passed the name through the container, but the class has no constructor, so Laravel discarded it. The name stayednullandgetName()threw aTypeError, meaning a fallback built exactly as documented failed before it could be registered.- The
<x-curator-glider>component rejected anullmedia item, which is the main reason to configure a fallback in the first place. It now acceptsnull. handleInt()checked the raw id rather than the looked-up record, so the fallback branch never ran and a missing record produced "Attempt to read property path on null" instead. The documented<x-curator-glider :media="1" fallback="thumbnail"/>could not work.
Fallback resolution now happens in one place, so a
nullmedia item, an id that does not resolve, and a blank string all reach it. An unregistered fallback name, or one with no source, no longer dereferencesnull.Also fixed while in there:
GliderFallback's optional getters were typed non-nullable while every property defaults tonull, so a partially configured fallback threw. They are nullable now.GliderFallback::isPreviewable()calledCurator::isResizable(), reporting svg sources as not previewable.
Thanks to @battulga0719 for reporting the fallback issues in #717.
-
v5.1.425 Jul 2026Release notes
Open source →Fixed
- The media picker breadcrumb's root Disk label and the Selected Files heading in the selection modal were hardcoded in English and ignored the active locale. Both now resolve through the translation layer. The breadcrumb reuses the existing
curator::views.details.diskkey, so it is already translated in every shipped locale.
Localization
Adds a new
attach_curator_media.modal.selected_fileskey underresources/lang/en/views.php. Other locales fall back to English until translated — contributions welcome. - The media picker breadcrumb's root Disk label and the Selected Files heading in the selection modal were hardcoded in English and ignored the active locale. Both now resolve through the translation layer. The breadcrumb reuses the existing
-
v5.1.325 Jul 2026Nothing published for this version
-
v5.1.206 Jul 2026Nothing published for this version
-
v5.1.130 Jun 2026Nothing published for this version
-
v5.1.028 Jun 2026Nothing published for this version
-
v5.0.808 Jun 2026Nothing published for this version
-
v5.0.713 Apr 2026Nothing published for this version
-
v5.0.626 Mar 2026Nothing published for this version
-
v5.0.513 Mar 2026Nothing published for this version
-
v5.0.409 Mar 2026Nothing published for this version
-
v5.0.305 Mar 2026Nothing published for this version
-
v5.0.228 Jan 2026Nothing published for this version
-
v5.0.124 Jan 2026Nothing published for this version
-
v5.0.019 Jan 2026Nothing published for this version
-
v4.2.114 Aug 2026Release notes
Open source →Security release
Fixes an SVG sanitization bypass and unvalidated curation paths. See GHSA-3xm3-q2fj-x8rq for the full advisory.
- SVG sanitization could be bypassed by renaming the file. Sanitization was gated on the client-supplied filename extension while acceptance and the served content type were decided from the file's contents, so SVG markup uploaded as
payload.txtwas stored unsanitized. Sanitizing now keys off the detected type as well as the extension, in both the uploader andCuratorUtils::importMedia, and the serving layer pinsContent-Typefrom the stored extension instead of the sniffed bytes. - The curation modal wrote to a client-supplied path.
saveCuration()consumed the crop payload unvalidated, so a key such as../../othercould overwrite a sibling file inside the storage disk. The payload is now validated before anything is written. curator:sanitize-svgsnow selects on the detected type as well as the extension, so rows stored under a spoofed filename are no longer invisible to it.
After upgrading
Re-scan stored media to clean up anything already on disk:
php artisan curator:sanitize-svgs --dry-run # report only php artisan curator:sanitize-svgsThe serving-layer change already prevents affected rows from rendering as documents, so this is cleanup rather than the primary fix.
Also in this release
- Removed the unused
exifform view. It was not referenced anywhere in the package and could not render as shipped, since it requested an Alpine component that is never registered. If you published views withvendor:publish --tag=curator-views, your local copy is untouched by this removal and still contains a raw{!! !!}echo of EXIF metadata — escape or delete it.
Reported by Afsana Alijabarova (@afa114).
- SVG sanitization could be bypassed by renaming the file. Sanitization was gated on the client-supplied filename extension while acceptance and the served content type were decided from the file's contents, so SVG markup uploaded as
-
v4.2.008 Aug 2026Release notes
Open source →Added
The media picker's search now matches each term on its own instead of looking for the whole input as a single substring. A file named
my-image.pngcould not previously be found by typing "my image".The search is split on whitespace, hyphens and underscores, and every term has to match — though any of the five searchable columns (
name,title,alt,caption,description) may be the one matching it. Somy-image.pngis now found by "my image", "my_image", "my-image", or "image my", whilemy-document.pngstays out of those results.LIKEwildcards typed into the search are now escaped, so searching for100%looks for a literal percent sign rather than matching everything. Worth knowing that_has always been an unescaped single-character wildcard, sophoto_8-sunsetmatchedphoto-8-sunsetby accident while a hyphen did nothing; that inconsistency is gone.Case sensitivity is still left to the database — Postgres
LIKEis case-sensitive where MySQL and SQLite are not. That is unchanged from previous versions.Thanks to @amywestlake for reporting this and proposing the original approach in #623.
Fixed
Search results are now ordered by the panel's sort direction, matching the unfiltered list. There was no
ORDER BYat all before, so the 50-row limit truncated an arbitrary slice of matches.GliderFallback's setters were typed to require a value, while every property except the name is optional and every getter already returnednull. Building a fallback from a conditional expression — the usual reason to have one — threw aTypeErrorin a service provider before the application could boot:GliderFallback::make('logo') ->alt(config('app.name')) ->source(filled(setting('logo')) ? $logo->url : Vite::image('logo.webp'))
alt(),height(),source(),type()andwidth()now all acceptnull.A registered fallback that ends up with no source still cannot be rendered, but it now names itself —
The [logo] glider fallback does not have a source.— instead of reporting the media item as invalid, which pointed at the wrong thing.Thanks again to @battulga0719 for reporting this in #718.
Upgrading
No changes are required for ordinary use. Two notes:
- If you extended
GliderFallbackand overrode any of the setters with the narrowerstring/intsignature, widen it to?string/?intto match the parent. - Typing
0into the picker search previously reset the list to show everything. It now searches for "0"; whitespace-only input is what falls back to the unfiltered list.
- If you extended
-
v4.1.505 Aug 2026Release notes
Open source →Security
Curator's upload components fell back to a default list of accepted file types that included
text/html,application/xhtml+xml,text/javascript,application/xmlandapplication/octet-stream. An authenticated user with permission to upload media could upload an HTML file containing a<script>tag. The file was stored unmodified and later served from the application's own origin, executing the script with the session of whoever opened it.This affected the Media resource form,
MultiUploadAction,CuratorPickerand the rich editor's attachment flow, which all share the same default. Applications that already setacceptedFileTypes()themselves — globally or per field — were never affected, because the vulnerable list was only used as a fallback.The default is now
MimeType::defaults(): the full list minus types that are effectively executable content.MimeType::toArray()is unchanged, so code referencing the enum directly still works. As defense in depth, media served through Curator's own route now sendsX-Content-Type-Options: nosniff, and restricted types are forced toContent-Disposition: attachment.If your application genuinely needs to host these types, opt back in explicitly:
use Awcodes\Curator\Enums\MimeType; Curator::acceptedFileTypes([...MimeType::defaults(), 'text/html']);
Note that Curator only sanitizes SVG uploads. Anything else you allow is stored and served verbatim, and with the default
publicdisk it is also reachable through thestoragesymlink, where the new response headers do not apply. Serve deliberately-allowed executable types from a private disk.The 3.x line is not affected — its accepted types come from
config('curator.accepted_file_types'), whose default has always been limited to images and PDFs.Fixed
Glider fallbacks could not be used at all. Several faults compounded:
GliderFallback::make()passed the name through the container, but the class has no constructor, so Laravel discarded it. The name stayednullandgetName()threw aTypeError, meaning a fallback built exactly as documented failed before it could be registered.- The
<x-curator-glider>component rejected anullmedia item, which is the main reason to configure a fallback in the first place. It now acceptsnull. handleInt()checked the raw id rather than the looked-up record, so the fallback branch never ran and a missing record produced "Attempt to read property path on null" instead. The documented<x-curator-glider :media="1" fallback="thumbnail"/>could not work.
Fallback resolution now happens in one place, so a
nullmedia item, an id that does not resolve, and a blank string all reach it. An unregistered fallback name, or one with no source, no longer dereferencesnull.Also fixed while in there:
GliderFallback's optional getters were typed non-nullable while every property defaults tonull, so a partially configured fallback threw. They are nullable now.GliderFallback::isPreviewable()calledCurator::isResizable(), reporting svg sources as not previewable.
Thanks to @battulga0719 for reporting the fallback issues in #717.
-
v4.1.425 Jul 2026Nothing published for this version
-
v4.1.325 Jul 2026Nothing published for this version
-
v4.1.206 Jul 2026Nothing published for this version
-
v4.1.130 Jun 2026Nothing published for this version
-
v4.1.028 Jun 2026Nothing published for this version
-
v4.0.808 Jun 2026Nothing published for this version
-
v4.0.713 Apr 2026Nothing published for this version
-
v4.0.626 Mar 2026Nothing published for this version
-
v4.0.513 Mar 2026Nothing published for this version
-
v4.0.405 Mar 2026Nothing published for this version
-
v4.0.328 Jan 2026Nothing published for this version
-
v4.0.225 Jan 2026Nothing published for this version
-
v4.0.124 Jan 2026Nothing published for this version
-
v4.0.022 Dec 2025Nothing published for this version
-
v4.0.0-alpha.722 Dec 2025 pre-releaseNothing published for this version
-
v4.0.0-alpha.614 Dec 2025 pre-releaseNothing published for this version
-
v4.0.0-alpha.512 Dec 2025 pre-releaseNothing published for this version
-
v4.0.0-alpha.412 Dec 2025 pre-releaseNothing published for this version
-
v4.0.0-alpha.325 Nov 2025 pre-releaseNothing published for this version
-
v4.0.0-alpha.213 Nov 2025 pre-releaseNothing published for this version
-
v4.0.0-alpha.128 Oct 2025 pre-releaseNothing published for this version
-
v3.7.1114 Aug 2026Release notes
Open source →Security release
See GHSA-3xm3-q2fj-x8rq for the full advisory.
- Media streamed from disk was typed by sniffing its contents. SVG markup stored under another extension came back as
image/svg+xmlunder an inline disposition and could render as a document in the application's origin. The serving layer now pinsContent-Typefrom the stored extension, forces a download when the extension is unknown and the sniff would render as a document, and sendsX-Content-Type-Options: nosniffthroughout. - The curation modal wrote to a client-supplied path.
saveCuration()consumed the crop payload unvalidated, so a key such as../../othercould overwrite a sibling file inside the storage disk. The payload is now validated before anything is written. curator:sanitize-svgscould never finish on larger libraries. Its two selection criteria were or'd at the top level, sochunkById'sand id > ?bound to only one of them and every type-matched row was handed back on each pass. Any library with more than 100 SVG records looped until the command was killed. If a scan appeared to hang on an earlier version, re-run it.
The upload-side half of the advisory does not affect 3.x: its uploader already decided whether to sanitize from the detected type rather than the filename.
After upgrading
php artisan curator:sanitize-svgs --dry-run # report only php artisan curator:sanitize-svgsReported by Afsana Alijabarova (@afa114).
- Media streamed from disk was typed by sniffing its contents. SVG markup stored under another extension came back as
-
v3.7.1025 Jul 2026Nothing published for this version
-
v3.7.906 Jul 2026Nothing published for this version
-
v3.7.830 Jun 2026Nothing published for this version
-
v3.7.706 Jun 2025Nothing published for this version
-
v3.7.629 May 2025Nothing published for this version
-
v3.7.525 May 2025Nothing published for this version
-
v3.7.419 May 2025Nothing published for this version
-
v3.7.322 Mar 2025Nothing published for this version
-
v3.7.201 Mar 2025Nothing published for this version
-
v3.7.128 Feb 2025Nothing published for this version
-
v3.7.027 Feb 2025Nothing published for this version
-
v3.6.1825 Feb 2025Nothing published for this version
-
v3.6.1528 Dec 2024Nothing published for this version
-
v3.6.1403 Dec 2024Nothing published for this version
-
v3.6.1326 Nov 2024Nothing published for this version
-
v3.6.1211 Nov 2024Nothing published for this version