lovell/sharp · error · Error

Expected input to be an array of images to join

Error message

Expected input to be an array of images to join

What it means

Thrown when inputOptions.join is provided (join configuration) but this.options.join is not set — meaning there is no array of images to apply the join options to. Join options only make sense alongside a join input; supplying them without a join input is a contract violation. The guard is the else of `if (is.defined(this.options.join))`.

Source

Thrown at lib/input.mjs:515

        if (is.defined(inputOptions.join.background)) {
          inputDescriptor.joinBackground = this._getBackgroundColourOption(inputOptions.join.background);
        }
        if (is.defined(inputOptions.join.halign)) {
          if (is.string(inputOptions.join.halign) && is.string(this.constructor.align[inputOptions.join.halign])) {
            inputDescriptor.joinHalign = this.constructor.align[inputOptions.join.halign];
          } else {
            throw is.invalidParameterError('join.halign', 'valid alignment', inputOptions.join.halign);
          }
        }
        if (is.defined(inputOptions.join.valign)) {
          if (is.string(inputOptions.join.valign) && is.string(this.constructor.align[inputOptions.join.valign])) {
            inputDescriptor.joinValign = this.constructor.align[inputOptions.join.valign];
          } else {
            throw is.invalidParameterError('join.valign', 'valid alignment', inputOptions.join.valign);
          }
        }
      } else {
        throw new Error('Expected input to be an array of images to join');
      }
    }
  } else if (is.defined(inputOptions)) {
    throw new Error(`Invalid input options ${inputOptions}`);
  }
  return inputDescriptor;
}

/**
 * Handle incoming Buffer chunk on Writable Stream.
 * @private
 * @param {Buffer} chunk
 * @param {string} encoding - unused
 * @param {Function} callback
 */
function _write (chunk, _encoding, callback) {
  if (Array.isArray(this.options.input.buffer)) {
    if (is.buffer(chunk)) {

View on GitHub (pinned to 56676c6918)

Solutions

  1. Only include inputOptions.join when the input is an array of images: sharp([a, b], { join: { ... } }).
  2. Conditionally add join options only when images.length >= 2.
  3. Strip join options for single-image pipelines.

Example fix

// before
sharp(singleBuffer, { join: { animated: true } })

// after
sharp([a, b], { join: { animated: true } })
Defensive patterns

Strategy: validation

Validate before calling

function maybeJoin(input, joinOpts) {
  if (joinOpts && !Array.isArray(input)) {
    throw new Error('join options require an array input of >= 2 images');
  }
  const opts = Array.isArray(input) && input.length >= 2 ? { join: joinOpts } : {};
  return sharp(input, opts);
}

Type guard

function joinOptionsApplicable(input, options) {
  return !(options && options.join !== undefined) || (Array.isArray(input) && input.length >= 2);
}

Prevention

When it happens

Trigger: sharp(buffer, { join: { animated: true } }) — passing a single buffer input plus join options. sharp('file.png', { join: { halign: 'center' } }) with no array input. Building options from a template that always includes join even when the input is a single image.

Common situations: Reusing an options object across single-image and multi-image code paths. Misunderstanding that join options belong to the array-input form. Auto-generating options where join was conditionally populated for one case but applied to another.

Related errors


AI-assisted analysis of lovell/sharp@56676c6918 (2026-08-13). Data as JSON: /api/errors/107b5fbb75115aee. Report an issue: GitHub.