diff --git a/apps/api/plane/settings/common.py b/apps/api/plane/settings/common.py
index d90ee10f891..e9df54fd834 100644
--- a/apps/api/plane/settings/common.py
+++ b/apps/api/plane/settings/common.py
@@ -402,6 +402,7 @@
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain",
"text/markdown",
+ "text/mdx",
"application/rtf",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.text",
@@ -469,8 +470,6 @@
"application/x-sql",
# Gzip
"application/x-gzip",
- # Markdown
- "text/markdown",
]
# Seed directory path
diff --git a/apps/web/core/components/icons/attachment/attachment-icon.tsx b/apps/web/core/components/icons/attachment/attachment-icon.tsx
index dcbf652cd80..1ac6c2a033b 100644
--- a/apps/web/core/components/icons/attachment/attachment-icon.tsx
+++ b/apps/web/core/components/icons/attachment/attachment-icon.tsx
@@ -47,6 +47,9 @@ export const getFileIcon = (fileType: string, size: number = 28) => {
case "js":
return ;
case "txt":
+ case "md":
+ case "markdown":
+ case "mdx":
return ;
case "svg":
return ;
diff --git a/packages/editor/src/core/constants/config.ts b/packages/editor/src/core/constants/config.ts
index 64508e63e0a..9e126fa2a7e 100644
--- a/packages/editor/src/core/constants/config.ts
+++ b/packages/editor/src/core/constants/config.ts
@@ -33,6 +33,7 @@ export const ACCEPTED_ATTACHMENT_MIME_TYPES = [
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain",
"text/markdown",
+ "text/mdx",
"application/rtf",
"audio/mpeg",
"audio/wav",
diff --git a/packages/services/src/file/helper.ts b/packages/services/src/file/helper.ts
index b8e96283986..8f3f5b8ee97 100644
--- a/packages/services/src/file/helper.ts
+++ b/packages/services/src/file/helper.ts
@@ -10,6 +10,28 @@ import { fileTypeFromBuffer } from "file-type";
import type { TFileMetaDataLite, TFileSignedURLResponse } from "@plane/types";
import { DANGEROUS_EXTENSIONS } from "@plane/constants";
+/**
+ * @description Map of file extensions to MIME types for plain-text formats that
+ * file-type signature detection cannot identify (no magic bytes).
+ */
+const EXTENSION_MIME_TYPE_MAP: Record = {
+ md: "text/markdown",
+ markdown: "text/markdown",
+ mdx: "text/mdx",
+};
+
+/**
+ * @description Resolve a MIME type from the file extension for known text formats.
+ * @param {string} filename
+ * @returns {string} MIME type if extension is known, empty string otherwise
+ */
+const detectMimeTypeFromExtension = (filename: string): string => {
+ const parts = filename.split(".");
+ if (parts.length < 2) return "";
+ const extension = parts[parts.length - 1]?.toLowerCase() || "";
+ return EXTENSION_MIME_TYPE_MAP[extension] || "";
+};
+
/**
* @description Filename validation - checks for double extensions and dangerous patterns
* @param {string} filename
@@ -103,6 +125,12 @@ const validateAndDetectFileType = async (file: File): Promise => {
console.warn("Error detecting file type from signature:", _error);
}
+ // Plain-text formats (markdown, mdx, …) have no magic bytes — fall back to extension.
+ const extensionType = detectMimeTypeFromExtension(file.name);
+ if (extensionType) {
+ return extensionType;
+ }
+
// fallback for unknown files
return "";
};