Skip to content

fix: resolve 404/500 error when downloading files containing hash and percent character in filename#15823

Closed
andershermansen wants to merge 13 commits into
payloadcms:mainfrom
andershermansen:issue-15798-filename-with-hash-alternative
Closed

fix: resolve 404/500 error when downloading files containing hash and percent character in filename#15823
andershermansen wants to merge 13 commits into
payloadcms:mainfrom
andershermansen:issue-15798-filename-with-hash-alternative

Conversation

@andershermansen

@andershermansen andershermansen commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

What?

Fixes file downloads returning 404/500 for filenames containing # and % when served through the REST API.

Why?

A request for file named document%20#123.pdf will be sent as /api/media/file/document%2520%23123.pdf

% => %25
# => %23

Next.js catch-all [...slug] params decodes URL-encoded segments before passing them to route handlers. So the slug will have segments ['api', 'media', 'file', 'document%20#123.pdf']. In payload next handler these decoded segments are prepended with / and joined with /, meaning path is sent as /api/media/file/document%20#123.pdf to handleEndpoints for further processing.

path-to-regexp is used inside handleEndpoints for endpoint matching.

There are two issues here:

  1. The version of path-to-regexp used in payload uses /#? as default delimiter
  2. path-to-regexp decodes the elements in the path with decodURIComponent

Because of issue 1 /file/:filename will not match /file/document%20#123.pdf because it has 3 segments ['file', 'document%20', '123.pdf'] instead of 2 since hash is also considered a separator.

When issue 1 is fixed the matching will be be there, but because of issue 2 the parsed :filename param will be set to document #123.pdf. That is double decoded, resulting in an error that the file is not found on disk.

How?

For issue 1 use only / as delimiter in path-to-regexp match call, so that paths are only separated by / as expected. There are no # and ? used as fragment or query separator to think about here, as any of those will be stripped away already, we are only matching pathname part of url.

For issue 2 add a new optional parameter pathEncoding to be able to inform that the path provided is already decoded (e.g. Next.js decoded catch-all slugs) while keeping the handleEndpoints method backwards compatible since this is an exported method and other callers might pass uriencoded paths and expect it to work.

Adds a test that creates a file with % and # in the filename and verifies it can be served via REST.

Fixes #15798

@andershermansen

Copy link
Copy Markdown
Contributor Author

In newer path-to-regexp version the default delimiter is actually changed from /#? to just / in commit pillarjs/path-to-regexp@3574a3e

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes REST route matching so download endpoints can serve uploaded files whose filenames include # (and similar reserved characters) when routed through Next.js catch-all params / path-to-regexp.

Changes:

  • Adjust path-to-regexp matching in handleEndpoints to use delimiter: '/' and conditional decoding based on whether an override path was provided.
  • Add an integration test that uploads a file with # in the filename and asserts it can be fetched via the REST file endpoint.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
packages/payload/src/utilities/handleEndpoints.ts Updates endpoint matching options to allow # / ? within :param matches and avoid double-decoding for pre-decoded paths.
test/uploads/int.spec.ts Adds an integration test covering file serving when filenames include a hash character.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/payload/src/utilities/handleEndpoints.ts Outdated
Comment thread test/uploads/int.spec.ts
Comment thread test/uploads/int.spec.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/payload/src/utilities/handleEndpoints.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/payload/src/utilities/handleEndpoints.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/uploads/int.spec.ts Outdated
@andershermansen andershermansen changed the title fix: resolve 404 for filenames containing hash character fix: resolve 404 for filenames containing hash and percent character Mar 6, 2026
@andershermansen andershermansen changed the title fix: resolve 404 for filenames containing hash and percent character fix: resolve 404/500 error when downloading files containing hash and percent character in filename Mar 6, 2026
@andershermansen

Copy link
Copy Markdown
Contributor Author

File names with % also had issues because of the double uri decoding. Updated the PR description to be more clear about the two different issues and updated the test case to also test this scenario.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@andershermansen

Copy link
Copy Markdown
Contributor Author

Alternative solution in #15799

@paulpopus

Copy link
Copy Markdown
Contributor

Closing in favour of #15799

@paulpopus paulpopus closed this Apr 2, 2026
@andershermansen andershermansen deleted the issue-15798-filename-with-hash-alternative branch April 2, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

REST API returns 404 for filenames containing # (hash character)

4 participants