{"record":{"id":"b9e659978520c574","repo":"lovell/sharp","slug":"expected-both-left-and-top-to-be-set","errorCode":null,"errorMessage":"Expected both left and top to be set","messagePattern":"Expected both left and top to be set","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/composite.mjs","lineNumber":180,"sourceCode":"        throw is.invalidParameterError('tile', 'boolean', image.tile);\n      }\n    }\n    if (is.defined(image.left)) {\n      if (is.integer(image.left) && is.inRange(image.left, -100000000, 100000000)) {\n        composite.left = image.left;\n      } else {\n        throw is.invalidParameterError('left', 'integer between -100000000 and 100000000', image.left);\n      }\n    }\n    if (is.defined(image.top)) {\n      if (is.integer(image.top) && is.inRange(image.top, -100000000, 100000000)) {\n        composite.top = image.top;\n      } else {\n        throw is.invalidParameterError('top', 'integer between -100000000 and 100000000', image.top);\n      }\n    }\n    if (is.defined(image.top) !== is.defined(image.left)) {\n      throw new Error('Expected both left and top to be set');\n    } else {\n      composite.hasOffset = is.integer(image.top) && is.integer(image.left);\n    }\n    if (is.defined(image.gravity)) {\n      if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {\n        composite.gravity = image.gravity;\n      } else if (is.string(image.gravity) && is.integer(this.constructor.gravity[image.gravity])) {\n        composite.gravity = this.constructor.gravity[image.gravity];\n      } else {\n        throw is.invalidParameterError('gravity', 'valid gravity', image.gravity);\n      }\n    }\n    if (is.defined(image.premultiplied)) {\n      if (is.bool(image.premultiplied)) {\n        composite.premultiplied = image.premultiplied;\n      } else {\n        throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);\n      }","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/lovell/sharp/blob/56676c69180a32b468123e090f87bfee30539b49/lib/composite.mjs#L162-L198","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nsharp(bg).composite([{ input: overlay, left: 100 }]).png().toBuffer()\n\n// after\nsharp(bg).composite([{ input: overlay, left: 100, top: 50 }]).png().toBuffer()","handlingStrategy":"validation","validationCode":"function validateCompositeOffset(overlay) {\n  const hasLeft = overlay.left !== undefined;\n  const hasTop = overlay.top !== undefined;\n  if (hasLeft !== hasTop) {\n    throw new Error(`Composite offset requires both left and top; got left=${overlay.left}, top=${overlay.top}`);\n  }\n  if (hasLeft && (!Number.isInteger(overlay.left) || !Number.isInteger(overlay.top))) {\n    throw new Error('left and top must be integers');\n  }\n}\n// before: sharp(bg).composite([overlay])\n// validate each overlay:\noverlays.forEach(validateCompositeOffset);","typeGuard":"function hasCompleteOffset(overlay) {\n  const l = overlay.left, t = overlay.top;\n  return (l === undefined && t === undefined) || (Number.isInteger(l) && Number.isInteger(t));\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["composite","offset","validation","input-parameters"],"backgroundTag":null,"analyzedSha":"56676c69180a32b468123e090f87bfee30539b49","analyzedAt":"2026-08-13T04:44:31.201Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}