# Allowed avatar flair image file types

**URL:** https://meta.discourse.org/t/allowed-avatar-flair-image-file-types/223565
**Category:** Bug
**Created:** [April 7, 2022, 3:37am UTC](https://meta.discourse.org/t/allowed-avatar-flair-image-file-types/223565 "2022-04-07T03:37:38Z")
**Posts on this page:** 1
**Showing post:** 2

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [October 30, 2025, 2:55am UTC](https://meta.discourse.org/t/allowed-avatar-flair-image-file-types/223565/2 "2025-10-30T02:55:22Z")

</div>

Years later… this is actually a bit of a tricky one. We can do [something like this](https://github.com/discourse/discourse/commit/ea5927e3ff15616fc7df91ddcd361e6379a12582).

```diff
commit ea5927e3ff15616fc7df91ddcd361e6379a12582
Author: Sam Saffron <sam.saffron@gmail.com>
Date: Thu Oct 30 13:51:02 2025 +1100

    FEATURE: filter uploads correctly in uppy uploader and image / avatar uploader

diff --git a/frontend/discourse/admin/components/images-uploader.gjs b/frontend/discourse/admin/components/images-uploader.gjs
index 18a857d581..978edaac30 100644
--- a/frontend/discourse/admin/components/images-uploader.gjs
+++ b/frontend/discourse/admin/components/images-uploader.gjs
@@ -2,13 +2,18 @@
 import Component from "@ember/component";
 import { getOwner } from "@ember/owner";
 import didInsert from "@ember/render-modifiers/modifiers/did-insert";
+import { service } from "@ember/service";
 import { tagName } from "@ember-decorators/component";
 import icon from "discourse/helpers/d-icon";
+import { acceptedImageFormats } from "discourse/lib/uploads";
 import UppyUpload from "discourse/lib/uppy/uppy-upload";
 import { i18n } from "discourse-i18n";
 
 @tagName("span")
 export default class ImagesUploader extends Component {
+ @service currentUser;
+ @service siteSettings;
+
   uppyUpload = new UppyUpload(getOwner(this), {
     id: "images-uploader",
     type: "avatar",
@@ -28,6 +33,10 @@ export default class ImagesUploader extends Component {
     return this.uploadingOrProcessing ? i18n("uploading") : i18n("upload");
   }
 
+ get acceptedFormats() {
+ return acceptedImageFormats(this.currentUser?.staff, this.siteSettings);
+ }
+
   <template>
     <label
       class="btn"
@@ -40,7 +49,7 @@ export default class ImagesUploader extends Component {
         class="hidden-upload-field"
         disabled={{this.uppyUpload.uploading}}
         type="file"
- accept="image/*"
+ accept={{this.acceptedFormats}}
         multiple
       />
     </label>
diff --git a/frontend/discourse/app/components/avatar-uploader.gjs b/frontend/discourse/app/components/avatar-uploader.gjs
index d3bf8fc341..0f77c9e06a 100644
--- a/frontend/discourse/app/components/avatar-uploader.gjs
+++ b/frontend/discourse/app/components/avatar-uploader.gjs
@@ -3,15 +3,20 @@ import Component from "@ember/component";
 import { action } from "@ember/object";
 import { getOwner } from "@ember/owner";
 import didInsert from "@ember/render-modifiers/modifiers/did-insert";
+import { service } from "@ember/service";
 import { isBlank } from "@ember/utils";
 import { tagName } from "@ember-decorators/component";
 import DButton from "discourse/components/d-button";
 import discourseComputed from "discourse/lib/decorators";
+import { acceptedImageFormats } from "discourse/lib/uploads";
 import UppyUpload from "discourse/lib/uppy/uppy-upload";
 import { i18n } from "discourse-i18n";
 
 @tagName("span")
 export default class AvatarUploader extends Component {
+ @service currentUser;
+ @service siteSettings;
+
   uppyUpload = new UppyUpload(getOwner(this), {
     id: "avatar-uploader",
     type: "avatar",
@@ -34,6 +39,11 @@ export default class AvatarUploader extends Component {
 
   imageIsNotASquare = false;
 
+ @discourseComputed()
+ acceptedFormats() {
+ return acceptedImageFormats(this.currentUser?.staff, this.siteSettings);
+ }
+
   @discourseComputed("uppyUpload.uploading", "uploadedAvatarId")
   customAvatarUploaded() {
     return !this.uppyUpload.uploading && !isBlank(this.uploadedAvatarId);
@@ -58,7 +68,7 @@ export default class AvatarUploader extends Component {
       class="hidden-upload-field"
       disabled={{this.uploading}}
       type="file"
- accept="image/*"
+ accept={{this.acceptedFormats}}
       aria-hidden="true"
     />
     <DButton
diff --git a/frontend/discourse/app/components/uppy-image-uploader.gjs b/frontend/discourse/app/components/uppy-image-uploader.gjs
index 6a94ae6cb8..28bf264e8a 100644
--- a/frontend/discourse/app/components/uppy-image-uploader.gjs
+++ b/frontend/discourse/app/components/uppy-image-uploader.gjs
@@ -15,7 +15,12 @@ import concatClass from "discourse/helpers/concat-class";
 import icon from "discourse/helpers/d-icon";
 import { getURLWithCDN } from "discourse/lib/get-url";
 import lightbox from "discourse/lib/lightbox";
-import { authorizesOneOrMoreExtensions, isVideo } from "discourse/lib/uploads";
+import {
+ acceptedImageFormats,
+ acceptedVideoFormats,
+ authorizesOneOrMoreExtensions,
+ isVideo,
+} from "discourse/lib/uploads";
 import UppyUpload from "discourse/lib/uppy/uppy-upload";
 import { i18n } from "discourse-i18n";
 
@@ -138,7 +143,20 @@ export default class UppyImageUploader extends Component {
   }
 
   get acceptedFormats() {
- return this.args.allowVideo ? "image/*,video/*" : "image/*";
+ const imageFormats = acceptedImageFormats(
+ this.currentUser?.staff,
+ this.siteSettings
+ );
+
+ if (this.args.allowVideo) {
+ const videoFormats = acceptedVideoFormats(
+ this.currentUser?.staff,
+ this.siteSettings
+ );
+ return videoFormats ? `${imageFormats},${videoFormats}` : imageFormats;
+ }
+
+ return imageFormats;
   }
 
   get isVideoFile() {
diff --git a/frontend/discourse/app/lib/uploads.js b/frontend/discourse/app/lib/uploads.js
index a49005f6a8..da38f2abf4 100644
--- a/frontend/discourse/app/lib/uploads.js
+++ b/frontend/discourse/app/lib/uploads.js
@@ -178,6 +178,17 @@ function imagesExtensions(staff, siteSettings) {
   return exts;
 }
 
+function videosExtensions(staff, siteSettings) {
+ let exts = extensions(siteSettings).filter((ext) => isVideo(`.${ext}`));
+ if (staff) {
+ const staffExts = staffExtensions(siteSettings).filter((ext) =>
+ isVideo(`.${ext}`)
+ );
+ exts = exts.concat(staffExts);
+ }
+ return exts;
+}
+
 function isAuthorizedFile(fileName, staff, siteSettings) {
   if (
     staff &&
@@ -215,6 +226,16 @@ function authorizedImagesExtensions(staff, siteSettings) {
     : imagesExtensions(staff, siteSettings).join(", ");
 }
 
+export function acceptedImageFormats(staff, siteSettings) {
+ const exts = imagesExtensions(staff, siteSettings);
+ return exts.map((ext) => `.${ext}`).join(",");
+}
+
+export function acceptedVideoFormats(staff, siteSettings) {
+ const exts = videosExtensions(staff, siteSettings);
+ return exts.map((ext) => `.${ext}`).join(",");
+}
+
 export function authorizesAllExtensions(staff, siteSettings) {
   return (
     siteSettings.authorized_extensions.includes("*") ||

```

One slight downside is that our client side image compression can also sometimes handle format conversion. So potentially this would block some image types we don’t want to block.

I am mixed on if we want to fix this or not, but do see this as having high impact if you configured your site (for example) only to allow png.

cc @martin

---

_[View the full topic](https://meta.discourse.org/t/allowed-avatar-flair-image-file-types/223565)._
