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
- Provide both left and top together: { input: overlay, left: 100, top: 50 }.
- If you only need axis-independent placement, drop both left and top and use gravity instead (e.g., gravity: 'center').
- 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
- Always set left and top as a pair, or omit both and use gravity.
- When offsets are conditional, build a partial object and spread it only if both axes are known.
- Centralize overlay construction in a helper that enforces the pair invariant.
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
- Invalid input
- Recursive join is unsupported
- Expected at least two images to join
- 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/b9e659978520c574.
Report an issue: GitHub.