lovell/sharp · error · Error
Recursive join is unsupported
Error message
Recursive join is unsupported
What it means
Thrown when sharp is asked to join (stack/tile) an array of images while a join is already in progress on the same instance (this.options.joining is already true). Sharp sets a 'joining' flag when it begins processing an array input and recursively builds input descriptors for each element; if one of those elements is itself an array that triggers another join, the nested join cannot be represented and is rejected.
Source
Thrown at lib/input.mjs:99
inputDescriptor.buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create
inputOptions = input;
if (_inputOptionsFromObject(inputOptions)) {
// Stream with options
inputDescriptor.buffer = [];
}
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream without options
inputDescriptor.buffer = [];
} else if (Array.isArray(input)) {
if (input.length > 1) {
// Join images together
if (!this.options.joining) {
this.options.joining = true;
this.options.join = input.map(i => this._createInputDescriptor(i));
} else {
throw new Error('Recursive join is unsupported');
}
} else {
throw new Error('Expected at least two images to join');
}
} else {
throw new Error(`Unsupported input '${input}' of type ${typeof input}${
is.defined(inputOptions) ? ` when also providing options of type ${typeof inputOptions}` : ''
}`);
}
if (is.object(inputOptions)) {
// failOn
if (is.defined(inputOptions.failOn)) {
if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
inputDescriptor.failOn = inputOptions.failOn;
} else {
throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
}
}View on GitHub (pinned to 56676c6918)
Solutions
- Flatten the input array to a single level before passing it: sharp(images.flat()).
- Restructure so each join is its own sharp instance rather than nesting arrays in one call.
- For grid layouts, join row-by-row into intermediate buffers, then join the row results in a second pass.
Example fix
// before
sharp([[a, b], [c, d]], { join: { acro... } })
// after
sharp([a, b, c, d].flat(), { join: { ... } }) Defensive patterns
Strategy: validation
Validate before calling
function flattenJoinInput(maybeNested) {
if (!Array.isArray(maybeNested)) return maybeNested;
const flat = maybeNested.flat(Infinity);
if (flat.length < 2) throw new Error('Join requires at least two images');
return flat;
}
sharp(flattenJoinInput(maybeNested), { join: {} }); Type guard
function isFlatImageArray(input) {
return Array.isArray(input) && input.length >= 2 && input.every(i => !Array.isArray(i));
} Prevention
- Always flatten nested arrays before passing them as join input.
- Keep join inputs as a single-level list built by a helper.
- Do not reuse a sharp instance that is already joining.
When it happens
Trigger: Passing a nested array as input to sharp: sharp([[imgA, imgB], [imgC, imgD]]). Also when reusing a sharp instance that is mid-join, or building join inputs where one element resolves to another array via _createInputDescriptor. The flag this.options.joining being truthy on re-entry is the exact trigger.
Common situations: Attempted grid/tile joins where the developer passes a 2D array hoping for rows-and-columns layout (sharp only supports flat 1D joins). Dynamically composing join lists where flattening was forgotten.
Related errors
- Expected at least two images to join
- Expected input to be an array of images to join
- Invalid input
- Unsupported input '${input}' of type ${typeof input}${is.def
- Expected width, height and channels for raw pixel input
AI-assisted analysis of lovell/sharp@56676c6918 (2026-08-13).
Data as JSON: /api/errors/0ecdc69458e297bb.
Report an issue: GitHub.