lovell/sharp · error · Error

Expected both left and top to be set

Error message

Expected both left and top to be set

What it means

Thrown by sharp's composite() when an overlay image specifies a positional offset using only one of the 'left' or 'top' coordinates. The library requires offsets to be a complete (x, y) pair because the native compositor positions the overlay via a single 2D offset; providing only one axis is ambiguous and cannot be rendered. The guard explicitly compares the defined-ness of both fields and rejects any mismatch.

Source

Thrown at lib/composite.mjs:180

        throw is.invalidParameterError('tile', 'boolean', image.tile);
      }
    }
    if (is.defined(image.left)) {
      if (is.integer(image.left) && is.inRange(image.left, -100000000, 100000000)) {
        composite.left = image.left;
      } else {
        throw is.invalidParameterError('left', 'integer between -100000000 and 100000000', image.left);
      }
    }
    if (is.defined(image.top)) {
      if (is.integer(image.top) && is.inRange(image.top, -100000000, 100000000)) {
        composite.top = image.top;
      } else {
        throw is.invalidParameterError('top', 'integer between -100000000 and 100000000', image.top);
      }
    }
    if (is.defined(image.top) !== is.defined(image.left)) {
      throw new Error('Expected both left and top to be set');
    } else {
      composite.hasOffset = is.integer(image.top) && is.integer(image.left);
    }
    if (is.defined(image.gravity)) {
      if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
        composite.gravity = image.gravity;
      } else if (is.string(image.gravity) && is.integer(this.constructor.gravity[image.gravity])) {
        composite.gravity = this.constructor.gravity[image.gravity];
      } else {
        throw is.invalidParameterError('gravity', 'valid gravity', image.gravity);
      }
    }
    if (is.defined(image.premultiplied)) {
      if (is.bool(image.premultiplied)) {
        composite.premultiplied = image.premultiplied;
      } else {
        throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
      }

View on GitHub (pinned to 56676c6918)

Solutions

  1. Provide both left and top together: { input: overlay, left: 100, top: 50 }.
  2. If you only need axis-independent placement, drop both left and top and use gravity instead (e.g., gravity: 'center').
  3. When offsets are conditional, set or omit them as a pair: const offset = useOffset ? { left: x, top: y } : {} then spread offset into the overlay object.

Example fix

// before
sharp(bg).composite([{ input: overlay, left: 100 }]).png().toBuffer()

// after
sharp(bg).composite([{ input: overlay, left: 100, top: 50 }]).png().toBuffer()
Defensive patterns

Strategy: validation

Validate before calling

function validateCompositeOffset(overlay) {
  const hasLeft = overlay.left !== undefined;
  const hasTop = overlay.top !== undefined;
  if (hasLeft !== hasTop) {
    throw new Error(`Composite offset requires both left and top; got left=${overlay.left}, top=${overlay.top}`);
  }
  if (hasLeft && (!Number.isInteger(overlay.left) || !Number.isInteger(overlay.top))) {
    throw new Error('left and top must be integers');
  }
}
// before: sharp(bg).composite([overlay])
// validate each overlay:
overlays.forEach(validateCompositeOffset);

Type guard

function hasCompleteOffset(overlay) {
  const l = overlay.left, t = overlay.top;
  return (l === undefined && t === undefined) || (Number.isInteger(l) && Number.isInteger(t));
}

Prevention

When it happens

Trigger: Calling sharp(image).composite([{ input: overlay, left: 100 }]) without a top value, or passing { input: overlay, top: 50 } without left. The check `is.defined(image.top) !== is.defined(image.left)` fires whenever exactly one of the two is provided (including when one is explicitly undefined and the other is a number).

Common situations: Developers copy a partial example or compute only the horizontal offset (e.g., centering horizontally but forgetting vertical). Also occurs when conditionally setting offsets via spread: `{ ...(cond ? { left } : {}) }` leaving the other axis unset. Confusion with the 'gravity' option, which is the alternative anchor-based positioning that needs neither left nor top.

Related errors


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