From 86c6b69a548fe73544de01eafdcd5c97840e49d4 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Sun, 3 May 2026 00:01:24 +0530 Subject: [PATCH] feat: support .md and .mdx file uploads Plain-text files have no magic bytes, so the file-type signature detector returned an empty MIME type and the backend rejected them. Add an extension-based fallback for markdown/mdx, allow text/mdx in the API and editor allow-lists, and pick a file icon for these extensions. --- apps/api/plane/settings/common.py | 3 +- .../icons/attachment/attachment-icon.tsx | 3 ++ packages/editor/src/core/constants/config.ts | 1 + packages/services/src/file/helper.ts | 28 +++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) 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 ""; };