Skip to content

feat!: add support for file transformer adapters - #18410

Open
r1tsuu wants to merge 8 commits into
feat/file-transformersfrom
feat/file-transformers-core
Open

r1tsuu wants to merge 8 commits into
feat/file-transformersfrom
feat/file-transformers-core

Conversation

@r1tsuu

@r1tsuu r1tsuu commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Note

Part 2 of 3 in a stacked PR. See #17827 for the full description, breaking changes and migration.

  1. feat!: add dynamic image resizing and support for file transformers adapters #17827 — tests, fixtures, test configuration and generated test types
  2. This PR — Payload core, storage adapters, codemod, templates and documentation
  3. feat: add sharp file transformer with dynamic image resizing #18411 — @payloadcms/transformer-sharp, its README, workspace registration and lockfile

Scope

  • payload: upload.transformers pipeline, dynamic file requests behind access.read / req.fileTransform, generatePayloadFileURL, removal of the built-in Sharp processing and Sharp-specific config/types
  • @payloadcms/ui: upload and file manager updates
  • Storage adapters and plugin-cloud-storage: internal transform file-handler operation
  • @payloadcms/codemod: migrate-sharp-to-transformer
  • create-payload-app and templates: migrated to sharpTransformer
  • Docs: upload/transformers.mdx, upload and storage adapter docs, v4 migration guide

@socket-security

socket-security Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​payloadcms/​transformer-sharp@​3.82.1N/AN/AN/AN/AN/A

View full report

return
}

const alreadyImported = existing

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This check treats import { sharpTransformer as st } as an available sharpTransformer binding. The codemod later emits sharpTransformer(...), which is undefined. Use the local alias or add an unaliased import before generating the call.

// Remove `sharp` before extracting collection entries — later removals
// shift node positions, and ts-morph node references taken before a
// sibling removal can go stale.
sharpProp?.remove()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The codemod removes sharp and collection image settings before it confirms that it can add sharpTransformer. Non-inline transformer settings and upload spreads then produce output without image processing. Remove the old settings only after the replacement call is registered.

Comment thread packages/payload/src/config/build.ts Outdated
* @returns Built and sanitized Payload Config
*/
export async function buildConfig(config: Config): Promise<SanitizedConfig> {
assertNoLegacySharpConfig({ config })

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This check runs before plugins. A plugin can add removed Sharp settings, and Payload then starts without processing them. Run this validation after plugins and before transformer initialisation.

uploadConfig.formatOptions ||
uploadConfig.trimOptions ||
uploadConfig.constructorOptions ||
uploadConfig.hasImageAdjustments ||

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This check does not consider registered transformFile transformers. Direct-to-cloud uploads can provide no bytes or only header bytes, so custom transformers do not run. Add a transformer content requirement or fetch the full file for each matching transformer.

return finalizeFileResponse({ collection, req, response: currentResponse! })
}
}
} catch (err) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If a transformer gets the source and then throws, this path leaves the response body open. Repeated failures can retain file handles or storage connections. Cancel unused response bodies and close the underlying stream in this error path.

* The local binding `sharpTransformer` is imported under in this file, honoring an alias such as
* `import { sharpTransformer as st }`. Falls back to `sharpTransformer` when it isn't imported yet.
*/
function getSharpTransformerLocalName(sourceFile: SourceFile): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The fallback name matches unrelated local functions named sharpTransformer. The codemod then changes their imageSizes properties to variants. Resolve the imported symbol and choose a collision-free name before changing calls. Add tests for local name collisions.

req,
uploadReference,
useCompositePrefixes = false,
}: GetFileArgs): Promise<Response> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This transform path reuses a Web stream that does not implement cancel() or destroy file.createReadStream(). A failed transform can leave the GCS download active. Retain the Node stream, destroy it on cancellation, connect req.signal, and add a cancellation test.

see the main file must not be combined with Sharp for image uploads.
</Banner>

Transformers only run on uploads whose full bytes are available. When a client uploads directly to cloud storage (`clientUploads`) on a collection with `disableLocalStorage`, Payload downloads the whole file only when `upload.mimeTypes` restricts the allowed types, the request carries a crop or resize edit, the file is an animated image, or the collection has Sharp `variants` or image adjustments (`resizeOptions`, `formatOptions`, and so on). Otherwise the upload is saved without running any transformer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Eligible custom transformFile stages now require full content for direct client uploads. This paragraph omits that case and says no transformer runs otherwise. Update it to match hasTransformFileStages.

const VARIANTS_KEY = 'variants'

const IDENTIFIER_PATTERN = /^[A-Z_$][\w$]*$/i
const RESERVED_WORDS = new Set([

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this really needed? Seems a bit excessive

This branch has not been deployed

No deployments
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.

2 participants