Skip to content

fix: include file extensions in accept attribute for better cross-browser compatibility#1269

Closed
TheAbMehta wants to merge 1 commit intopingdotgg:mainfrom
TheAbMehta:fix/file-type-extensions-accept
Closed

fix: include file extensions in accept attribute for better cross-browser compatibility#1269
TheAbMehta wants to merge 1 commit intopingdotgg:mainfrom
TheAbMehta:fix/file-type-extensions-accept

Conversation

@TheAbMehta
Copy link

@TheAbMehta TheAbMehta commented Feb 21, 2026

Summary

Fixes #1157. Also addresses #949 and #267.

Problem: On Windows/Chrome, certain MIME types like application/java-archive are not properly mapped to file extensions in the native file picker. When the HTML <input accept="application/java-archive"> attribute contains only the MIME type, the file picker shows "All Files (.)" instead of filtering to .jar files.

Fix: generateClientDropzoneAccept() now populates the extension arrays using the existing @uploadthing/mime-types extension data via the lazy getExtensions() getter. For example:

// Before
{ "application/java-archive": [] }

// After
{ "application/java-archive": [".jar", ".war", ".ear"] }

When acceptPropAsAcceptAttr() flattens the AcceptProp, it already handles extensions (the isExt() check), so the resulting accept attribute becomes "application/java-archive,.jar,.war,.ear" — which Windows/Chrome can properly filter.

Changes

  • packages/shared/src/component-utils.ts: Import getExtensions from @uploadthing/mime-types and use it in generateClientDropzoneAccept() to look up file extensions for each MIME type. Handles comma-joined strings from generic types (e.g. "image") by splitting and collecting extensions for each sub-type.
  • packages/shared/test/component-utils.test.ts: Added 6 tests for generateClientDropzoneAccept covering specific MIME types, the pdf shorthand, blob type, generic types like image, unknown MIME types, and multiple file types.

Notes

  • Uses the lazy getExtensions() getter rather than importing the full application module directly, keeping bundle impact minimal
  • No changes to dropzone-utils.ts — the existing acceptPropAsAcceptAttr() already handles extensions correctly
  • All existing tests continue to pass

Disclosure

This PR was authored with the assistance of Claude (LLM) to help understand the codebase and structure the implementation and description.

…wser compatibility

Populate the extension arrays in generateClientDropzoneAccept() using
the existing @uploadthing/mime-types extension data. This ensures the
HTML <input accept> attribute includes both MIME types and their
corresponding file extensions (e.g. .jar, .war, .ear), which fixes
file picker filtering on Windows/Chrome for types like
application/java-archive.

Closes #1157
@changeset-bot
Copy link

changeset-bot bot commented Feb 21, 2026

⚠️ No Changeset found

Latest commit: 2eb4eac

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Feb 21, 2026

@TheAbMehta is attempting to deploy a commit to the Ping Labs Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Walkthrough

Modified generateClientDropzoneAccept utility function to return file extensions mapped from MIME types using the @uploadthing/mime-types library, replacing previous empty array returns. Added comprehensive test coverage for the updated behavior.

Changes

Cohort / File(s) Summary
MIME Type Extension Mapping
packages/shared/src/component-utils.ts
Added import of getExtensions from @uploadthing/mime-types. Modified generateClientDropzoneAccept() to expand MIME types into corresponding file extensions (dot-prefixed) instead of empty arrays, handling comma-separated subtypes.
Test Coverage
packages/shared/test/component-utils.test.ts
Added test suite for generateClientDropzoneAccept covering specific MIME type mappings, shorthand handling (pdf), generic types (image), unknown types, blob handling, and multiple type scenarios.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding file extensions to the accept attribute for cross-browser compatibility.
Linked Issues check ✅ Passed The PR successfully addresses issue #1157 by modifying generateClientDropzoneAccept() to map MIME types to file extensions, enabling proper file filtering on Windows/Chrome.
Out of Scope Changes check ✅ Passed All changes (importing getExtensions, updating generateClientDropzoneAccept logic, and adding comprehensive tests) are directly related to fixing the MIME-to-extension mapping issue.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link

greptile-apps bot commented Feb 21, 2026

Greptile Summary

This PR fixes a cross-browser compatibility issue where Windows/Chrome doesn't properly map certain MIME types (like application/java-archive) to file extensions in the native file picker. The fix populates extension arrays in generateClientDropzoneAccept() by looking up extensions from @uploadthing/mime-types using the lazy getExtensions() getter.

Key changes:

  • Modified generateClientDropzoneAccept() to look up and include file extensions for each MIME type (e.g., application/java-archive now includes [".jar", ".war", ".ear"])
  • Correctly handles comma-joined MIME type strings from generic types like "image" by splitting and collecting extensions for each sub-type
  • Uses lazy getExtensions() getter to minimize bundle impact
  • Added 6 comprehensive tests covering specific MIME types, shorthands, blob type, generic types, unknown MIME types, and multiple file types

Implementation flow:

  1. When fileTypes = ["image"], generateMimeTypes() returns ["image/*, image/png, image/jpeg, ..."]
  2. The new code splits this comma-joined string into individual MIME types
  3. For each MIME type, it looks up extensions (e.g., image/png["png"])
  4. Extensions are prefixed with . and flattened into the result
  5. acceptPropAsAcceptAttr() (unchanged) already handles extensions correctly, producing accept attributes like "application/java-archive,.jar,.war,.ear"

The implementation is clean, well-tested, and directly addresses the reported issues.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is straightforward, well-tested with 6 new comprehensive tests, and uses existing lazy-loaded utilities to minimize bundle impact. The logic correctly handles all edge cases including comma-joined MIME types, unknown types (fallback to empty array), and wildcards. No breaking changes, maintains backward compatibility.
  • No files require special attention

Important Files Changed

Filename Overview
packages/shared/src/component-utils.ts Added extension lookup using getExtensions() for better cross-browser file picker compatibility, handles comma-joined MIME types correctly
packages/shared/test/component-utils.test.ts Comprehensive test coverage for generateClientDropzoneAccept with 6 new tests covering edge cases and expected behavior

Last reviewed commit: 2eb4eac

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/shared/src/component-utils.ts`:
- Around line 61-65: The exts array built from subTypes and extensionsMap may
contain duplicate file extensions; update the exts computation (referencing the
exts variable, subTypes, and extensionsMap) to deduplicate results before
returning/using them — e.g., collect all mapped extensions (the `.${ext}`
values) and remove duplicates (use a Set or Array.filter uniqueness) so the
final exts contains each extension only once.
- Line 10: The import of getExtensions from "@uploadthing/mime-types" pulls in
the entire mimes DB (via the mimes constant and populateMaps) and negates the
documented tree-shaking; either update the comment to state this intentional
tradeoff or refactor to import only the specific category modules needed (e.g.,
import sub-paths like "@uploadthing/mime-types/image" or
"@uploadthing/mime-types/application") and update calls that use
getExtensions/populateMaps accordingly so only required MIME category modules
are bundled.

In `@packages/shared/test/component-utils.test.ts`:
- Around line 46-51: The test is brittle because it asserts exact ordering of
extension arrays returned by generateClientDropzoneAccept; change assertions to
check presence regardless of order by using Jest matchers like
expect(result).toEqual({ "application/java-archive":
expect.arrayContaining([".jar", ".war", ".ear"]) }) or
expect(result).toHaveProperty("application/java-archive",
expect.arrayContaining([...])) in the tests that currently use toEqual
(including the other failing cases around the same tests), so the tests assert
inclusion not order and remain stable against `@uploadthing/mime-types` data
changes.

* We don't need all the types, and `/application`
* entrypoint is ~7k gzip which we can shave off
*/
import { getExtensions } from "@uploadthing/mime-types";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, explore the repository structure to locate the file
find . -type f -name "component-utils.ts" | head -5

Repository: pingdotgg/uploadthing

Length of output: 105


🏁 Script executed:

# List the files in packages/shared/src to understand context
ls -la packages/shared/src/ 2>/dev/null | head -20

Repository: pingdotgg/uploadthing

Length of output: 790


🏁 Script executed:

# Read the component-utils.ts file to see the imports and context
cat -n packages/shared/src/component-utils.ts | head -100

Repository: pingdotgg/uploadthing

Length of output: 4166


🏁 Script executed:

# Check the `@uploadthing/mime-types` package structure
find node_modules/@uploadthing/mime-types -type f \( -name "*.js" -o -name "*.d.ts" -o -name "*.json" \) 2>/dev/null | grep -E "(index|package)" | head -20

Repository: pingdotgg/uploadthing

Length of output: 47


🏁 Script executed:

# Look at the main entry point
cat node_modules/@uploadthing/mime-types/package.json 2>/dev/null | head -30

Repository: pingdotgg/uploadthing

Length of output: 47


🏁 Script executed:

# Check the main entry file
cat node_modules/@uploadthing/mime-types/dist/index.d.ts 2>/dev/null | head -50

Repository: pingdotgg/uploadthing

Length of output: 47


🏁 Script executed:

# Check the actual implementation of the main entry
cat node_modules/@uploadthing/mime-types/dist/index.js 2>/dev/null | head -100

Repository: pingdotgg/uploadthing

Length of output: 47


🏁 Script executed:

# Check if mime-types is part of this monorepo
find . -type d -name "mime-types" 2>/dev/null | grep -v node_modules | head -10

Repository: pingdotgg/uploadthing

Length of output: 86


🏁 Script executed:

# Check the root package.json and workspace structure
cat package.json 2>/dev/null | head -50

Repository: pingdotgg/uploadthing

Length of output: 1833


🏁 Script executed:

# Look at packages directory structure
ls -la packages/ 2>/dev/null

Repository: pingdotgg/uploadthing

Length of output: 746


🏁 Script executed:

# Search for any references to mime-types in the codebase
rg "mime-types" --type json | head -20

Repository: pingdotgg/uploadthing

Length of output: 510


🏁 Script executed:

# Explore the mime-types package structure
ls -la packages/mime-types/

Repository: pingdotgg/uploadthing

Length of output: 805


🏁 Script executed:

# Check the source directory
ls -la packages/mime-types/src/ 2>/dev/null

Repository: pingdotgg/uploadthing

Length of output: 595


🏁 Script executed:

# Read the main entry file
cat packages/mime-types/src/index.ts 2>/dev/null

Repository: pingdotgg/uploadthing

Length of output: 3142


🏁 Script executed:

# Check for getExtensions function
rg "getExtensions" packages/mime-types/src/ -A 10

Repository: pingdotgg/uploadthing

Length of output: 732


🏁 Script executed:

# Check the size of application.ts
wc -l packages/mime-types/src/application.ts

Repository: pingdotgg/uploadthing

Length of output: 108


🏁 Script executed:

# Look at the start of application.ts to understand its scope
head -50 packages/mime-types/src/application.ts

Repository: pingdotgg/uploadthing

Length of output: 1124


🏁 Script executed:

# Check the granular entry points to understand their scope
wc -l packages/mime-types/src/{audio,image,text,video}.ts

Repository: pingdotgg/uploadthing

Length of output: 231


🏁 Script executed:

# See what happens when getExtensions is called with all categories
grep -n "application" packages/mime-types/src/index.ts

Repository: pingdotgg/uploadthing

Length of output: 276


🏁 Script executed:

# Check if there's a way to build extensions from just the granular imports
# Let's see what the structure of each category looks like
head -20 packages/mime-types/src/audio.ts

Repository: pingdotgg/uploadthing

Length of output: 456


🏁 Script executed:

# Check if the component-utils.ts actually uses application/* types
grep -n "application" packages/shared/src/component-utils.ts

Repository: pingdotgg/uploadthing

Length of output: 170


🏁 Script executed:

# Let's verify if bundler can tree-shake the mimes constant even with the main entry import
# Check the complete mimes object construction in index.ts
sed -n '15,30p' packages/mime-types/src/index.ts

Repository: pingdotgg/uploadthing

Length of output: 367


🏁 Script executed:

# Check if there are any other uses of getExtensions in the codebase
rg "getExtensions" --type ts --type tsx -A 2 -B 2

Repository: pingdotgg/uploadthing

Length of output: 92


🏁 Script executed:

# Check the actual package.json exports to understand entry points
cat packages/mime-types/package.json

Repository: pingdotgg/uploadthing

Length of output: 2493


🏁 Script executed:

# Check what's in misc.ts
wc -l packages/mime-types/src/misc.ts && head -20 packages/mime-types/src/misc.ts

Repository: pingdotgg/uploadthing

Length of output: 473


🏁 Script executed:

# Verify how this is actually built/bundled - look for build output
find packages/mime-types/dist -type f 2>/dev/null | head -10

Repository: pingdotgg/uploadthing

Length of output: 47


🏁 Script executed:

# Check what application types are actually needed by generateClientDropzoneAccept
# Let's see how generateMimeTypes uses the file types
sed -n '29,51p' packages/shared/src/component-utils.ts

Repository: pingdotgg/uploadthing

Length of output: 1037


getExtensions from the main entry pulls the full MIME database into the bundle, contradicting the documented tree-shaking strategy.

The file's comment (lines 5–9) explicitly describes using granular sub-path imports (/audio, /image, etc.) specifically to avoid the ~7 kB gzip cost of the full database. However, the import on line 10 (import { getExtensions } from "@uploadthing/mime-types") pulls from the main entry point, which creates a single mimes constant that combines all categories—including application.ts (2,653 lines), video.ts, text.ts, audio.ts, image.ts, and misc.ts.

The getExtensions() function calls populateMaps(), which iterates through the entire mimes object. Because the bundler cannot distinguish which MIME types will be looked up at runtime, it must include the full object statically—the lazy initialization only defers the CPU cost of building the map at runtime, not the bundle inclusion cost. The complete database is compiled into the bundle regardless.

This is a real regression: application.ts alone is 2,653 lines. If application MIME types (like application/pdf for the "pdf" type) are necessary, consider either:

  1. Updating the comment to reflect the intentional tradeoff, or
  2. Refactoring to import only the specific category modules needed at the point of use.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/component-utils.ts` at line 10, The import of
getExtensions from "@uploadthing/mime-types" pulls in the entire mimes DB (via
the mimes constant and populateMaps) and negates the documented tree-shaking;
either update the comment to state this intentional tradeoff or refactor to
import only the specific category modules needed (e.g., import sub-paths like
"@uploadthing/mime-types/image" or "@uploadthing/mime-types/application") and
update calls that use getExtensions/populateMaps accordingly so only required
MIME category modules are bundled.

Comment on lines +61 to +65
const exts = subTypes.flatMap((mime) => {
const mimeExts =
extensionsMap[mime as keyof typeof extensionsMap] ?? [];
return mimeExts.map((ext) => `.${ext}`);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Possible duplicate extensions for generic types — consider deduplicating.

When processing a generic type like "image", generateMimeTypes returns a comma-joined string containing many specific subtypes (image/png, image/jpeg, image/jpg, …). Two different MIME types can legitimately share the same extension (e.g., both image/jpeg and image/pjpeg map to .jpg/.jpeg). The current flatMap accumulates all extensions without deduplication, so the resulting array can contain duplicates. While browsers tolerate this in the accept attribute, it's unclean.

♻️ Suggested fix
-      return [type, exts];
+      return [type, [...new Set(exts)]];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/component-utils.ts` around lines 61 - 65, The exts array
built from subTypes and extensionsMap may contain duplicate file extensions;
update the exts computation (referencing the exts variable, subTypes, and
extensionsMap) to deduplicate results before returning/using them — e.g.,
collect all mapped extensions (the `.${ext}` values) and remove duplicates (use
a Set or Array.filter uniqueness) so the final exts contains each extension only
once.

Comment on lines +46 to +51
it("includes file extensions for specific MIME types", () => {
const result = generateClientDropzoneAccept(["application/java-archive"]);
expect(result).toEqual({
"application/java-archive": [".jar", ".war", ".ear"],
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

toEqual with ordered extension arrays is brittle against MIME-database updates.

The tests at lines 46–51, 55–57, and 89–94 assert the exact ordered content of extension arrays (e.g., [".jar", ".war", ".ear"]). The order is determined by @uploadthing/mime-types's internal data, which could change on any package bump (new extension added, order changed). This would silently break these tests without any functional regression.

Prefer expect.arrayContaining(...) so the tests verify presence, not order:

♻️ Proposed fix
-    expect(result).toEqual({
-      "application/java-archive": [".jar", ".war", ".ear"],
-    });
+    expect(result["application/java-archive"]).toEqual(
+      expect.arrayContaining([".jar", ".war", ".ear"]),
+    );
+    expect(result["application/java-archive"]).toHaveLength(3);
-    expect(result).toEqual({
-      "application/pdf": [".pdf"],
-    });
+    expect(result["application/pdf"]).toEqual(
+      expect.arrayContaining([".pdf"]),
+    );
-    expect(result["application/java-archive"]).toEqual([
-      ".jar",
-      ".war",
-      ".ear",
-    ]);
-    expect(result["application/pdf"]).toEqual([".pdf"]);
+    expect(result["application/java-archive"]).toEqual(
+      expect.arrayContaining([".jar", ".war", ".ear"]),
+    );
+    expect(result["application/pdf"]).toEqual(
+      expect.arrayContaining([".pdf"]),
+    );

Also applies to: 84-95

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/test/component-utils.test.ts` around lines 46 - 51, The test
is brittle because it asserts exact ordering of extension arrays returned by
generateClientDropzoneAccept; change assertions to check presence regardless of
order by using Jest matchers like expect(result).toEqual({
"application/java-archive": expect.arrayContaining([".jar", ".war", ".ear"]) })
or expect(result).toHaveProperty("application/java-archive",
expect.arrayContaining([...])) in the tests that currently use toEqual
(including the other failing cases around the same tests), so the tests assert
inclusion not order and remain stable against `@uploadthing/mime-types` data
changes.

@TheAbMehta TheAbMehta closed this Feb 22, 2026
@TheAbMehta TheAbMehta deleted the fix/file-type-extensions-accept branch February 22, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug]: uploadthing/react - Java-Archive(.jar) file upload type does not show up when choosing files

1 participant