From 9891b8576e8449a070283bb949422b7ce8964dcb Mon Sep 17 00:00:00 2001 From: hossein-webdev <237538677+hossein-webdev@users.noreply.github.com> Date: Wed, 26 Aug 2026 07:29:27 -0700 Subject: [PATCH] fix(media): restore AVIF to the default upload allowlist #2250 replaced the bare "image/" prefix match in GLOBAL_UPLOAD_ALLOWLIST with an explicit enumeration of safe raster types, so that image/svg+xml would stop being accepted by default. image/avif was covered by the old prefix match but was not carried into the enumeration, so AVIF uploads started failing with "File type not allowed". Both media serving paths already treat image/avif as safe to render inline, so the upload gate was the only place that disagreed about AVIF. Restoring it there is what unblocks the upload; the extension shorthand and the admin picker's accept filter are the other two places a .avif file gets turned away before it reaches that gate. SVG stays excluded. Closes #2602 --- .changeset/avif-media-uploads.md | 10 ++++++++++ docs/src/content/docs/guides/media-library.mdx | 2 +- packages/admin/src/components/AllowedTypesEditor.tsx | 5 ++++- packages/admin/src/components/MediaUploadDialog.tsx | 10 ++++++++-- packages/admin/src/lib/mime-utils.ts | 1 + packages/core/src/api/handlers/media-allowlist.ts | 1 + packages/core/src/media/mime.ts | 1 + packages/core/tests/unit/media/media-allowlist.test.ts | 4 ++++ packages/core/tests/unit/media/mime.test.ts | 1 + 9 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 .changeset/avif-media-uploads.md diff --git a/.changeset/avif-media-uploads.md b/.changeset/avif-media-uploads.md new file mode 100644 index 0000000000..3564cb8fbd --- /dev/null +++ b/.changeset/avif-media-uploads.md @@ -0,0 +1,10 @@ +--- +"emdash": patch +"@emdash-cms/admin": patch +--- + +Fixes AVIF images being rejected with "File type not allowed" on upload. `image/avif` is back in the default media allowlist alongside PNG, JPEG, GIF, and WebP, so editors can upload `.avif` files again from the media library and from image fields that use the default allowlist. + +The admin file picker now offers `.avif` files and renders their thumbnails, the built-in "Images" preset in a field's allowed-types editor includes AVIF, and `.avif` works as extension shorthand in a field's `allowedMimeTypes`. + +SVG stays excluded from the default allowlist. diff --git a/docs/src/content/docs/guides/media-library.mdx b/docs/src/content/docs/guides/media-library.mdx index 77f2189c10..ab7e5f5537 100644 --- a/docs/src/content/docs/guides/media-library.mdx +++ b/docs/src/content/docs/guides/media-library.mdx @@ -40,7 +40,7 @@ EmDash accepts these file types by default: | Category | Extensions | | --------- | ----------------------------------------------- | -| Images | `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp` | +| Images | `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.avif` | | Documents | `.pdf` | | Video | `.mp4`, `.webm`, `.mov` | | Audio | `.mp3`, `.wav`, `.ogg` | diff --git a/packages/admin/src/components/AllowedTypesEditor.tsx b/packages/admin/src/components/AllowedTypesEditor.tsx index 577c8b5279..9822265e01 100644 --- a/packages/admin/src/components/AllowedTypesEditor.tsx +++ b/packages/admin/src/components/AllowedTypesEditor.tsx @@ -12,7 +12,10 @@ interface Preset { } const PRESETS: ReadonlyArray = [ - { key: "images", mimeTypes: ["image/png", "image/jpeg", "image/gif", "image/webp"] }, + { + key: "images", + mimeTypes: ["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"], + }, { key: "pdf", mimeTypes: ["application/pdf"] }, { key: "documents", diff --git a/packages/admin/src/components/MediaUploadDialog.tsx b/packages/admin/src/components/MediaUploadDialog.tsx index f9aa445d5d..a5e5242afc 100644 --- a/packages/admin/src/components/MediaUploadDialog.tsx +++ b/packages/admin/src/components/MediaUploadDialog.tsx @@ -19,13 +19,19 @@ import * as React from "react"; import { formatFileSize } from "../lib/media-utils.js"; export const LOCAL_MEDIA_UPLOAD_ACCEPT = - "image/png,image/jpeg,image/gif,image/webp,video/*,audio/*,application/pdf"; + "image/png,image/jpeg,image/gif,image/webp,image/avif,video/*,audio/*,application/pdf"; const DEFAULT_CONCURRENCY = 3; const MAX_CONCURRENCY = 6; const MAX_VISIBLE_ROWS = 100; const MAX_PREVIEW_BYTES = 8 * 1024 * 1024; -const PREVIEW_MIME_TYPES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]); +const PREVIEW_MIME_TYPES = new Set([ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "image/avif", +]); type UploadStatus = "queued" | "uploading" | "complete" | "failed"; diff --git a/packages/admin/src/lib/mime-utils.ts b/packages/admin/src/lib/mime-utils.ts index 96fdae0ca1..d3e714dc38 100644 --- a/packages/admin/src/lib/mime-utils.ts +++ b/packages/admin/src/lib/mime-utils.ts @@ -5,6 +5,7 @@ export const EXTENSION_TO_MIME: Readonly> = { ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", + ".avif": "image/avif", ".svg": "image/svg+xml", ".mp3": "audio/mpeg", ".wav": "audio/wav", diff --git a/packages/core/src/api/handlers/media-allowlist.ts b/packages/core/src/api/handlers/media-allowlist.ts index 9c7c06f3f0..0c049ac362 100644 --- a/packages/core/src/api/handlers/media-allowlist.ts +++ b/packages/core/src/api/handlers/media-allowlist.ts @@ -21,6 +21,7 @@ export const GLOBAL_UPLOAD_ALLOWLIST: readonly string[] = [ "image/jpeg", "image/gif", "image/webp", + "image/avif", "video/", "audio/", "application/pdf", diff --git a/packages/core/src/media/mime.ts b/packages/core/src/media/mime.ts index 495e9f62c5..aedb8a8678 100644 --- a/packages/core/src/media/mime.ts +++ b/packages/core/src/media/mime.ts @@ -23,6 +23,7 @@ export const EXTENSION_TO_MIME: Readonly> = { ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", + ".avif": "image/avif", ".svg": "image/svg+xml", ".mp3": "audio/mpeg", ".wav": "audio/wav", diff --git a/packages/core/tests/unit/media/media-allowlist.test.ts b/packages/core/tests/unit/media/media-allowlist.test.ts index 7f7a9243c3..65957ca6c6 100644 --- a/packages/core/tests/unit/media/media-allowlist.test.ts +++ b/packages/core/tests/unit/media/media-allowlist.test.ts @@ -15,6 +15,10 @@ describe("GLOBAL_UPLOAD_ALLOWLIST", () => { expect(matchesMimeAllowlist("image/webp", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true); }); + it("allows image/avif, which the media routes already serve inline", () => { + expect(matchesMimeAllowlist("image/avif", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true); + }); + it("still allows video, audio, and pdf", () => { expect(matchesMimeAllowlist("video/mp4", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true); expect(matchesMimeAllowlist("audio/mpeg", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true); diff --git a/packages/core/tests/unit/media/mime.test.ts b/packages/core/tests/unit/media/mime.test.ts index 4fd247a92f..71c6d3afc1 100644 --- a/packages/core/tests/unit/media/mime.test.ts +++ b/packages/core/tests/unit/media/mime.test.ts @@ -76,6 +76,7 @@ describe("expandExtensionShorthand", () => { it("expands known dot-extensions", () => { expect(expandExtensionShorthand(".pdf")).toBe("application/pdf"); expect(expandExtensionShorthand(".PDF")).toBe("application/pdf"); + expect(expandExtensionShorthand(".avif")).toBe("image/avif"); expect(expandExtensionShorthand(".docx")).toBe( "application/vnd.openxmlformats-officedocument.wordprocessingml.document", );