PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Calculation\Exception
#VALUE!
#VALUE!
Error message
#VALUE!
What it means
LookupBase::validateIndexLookup() (LookupBase.php:32-34) rejects VLOOKUP/HLOOKUP index_number < 1 with #VALUE!, matching Excel: column and row indices are 1-based, so 0 and negatives are out of domain even though they are numeric.
Source
Thrown at src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php:33
}
/**
* @param mixed[] $lookupArray
* @param float|int|string $index_number number >= 1
*/
protected static function validateIndexLookup(array $lookupArray, $index_number): int
{
// index_number must be a number greater than or equal to 1.
// Excel results are inconsistent when index is non-numeric.
// VLOOKUP(whatever, whatever, SQRT(-1)) yields NUM error, but
// VLOOKUP(whatever, whatever, cellref) yields REF error
// when cellref is '=SQRT(-1)'. So just try our best here.
// Similar results if string (literal yields VALUE, cellRef REF).
if (!is_numeric($index_number)) {
throw new Exception(ExcelError::throwError($index_number));
}
if ($index_number < 1) {
throw new Exception(ExcelError::VALUE());
}
// index_number must be less than or equal to the number of columns in lookupArray
if (empty($lookupArray)) {
throw new Exception(ExcelError::REF());
}
return (int) $index_number;
}
protected static function checkMatch(
bool $bothNumeric,
bool $bothNotNumeric,
bool $notExactMatch,
int $rowKey,
string $cellDataLower,
string $lookupLower,
?int $rowNumberView on GitHub (pinned to 65b080eef4)
Solutions
- Use 1-based indices - the first column of table_array is 1
- Map 0-based logic explicitly: $excelIndex = $phpIndex + 1
- Validate before calling: reject or clamp $indexNumber = max(1, (int) $indexNumber)
- Unit-test boundary values 0, 1 and the column count
Example fix
// before $result = VLookup::lookup($key, $table, $phpColumnIndex); // 0-based -> '#VALUE!' at 0 // after: translate the 0-based PHP index to Excel's 1-based index $result = VLookup::lookup($key, $table, $phpColumnIndex + 1);
Defensive patterns
Strategy: validation
Validate before calling
$indexNumber = (int) $indexNumber;
if ($indexNumber < 1) {
throw new InvalidArgumentException('lookup index is 1-based; got ' . $indexNumber);
} Type guard
function isOneBasedIndex(mixed $value): bool
{
return is_numeric($value) && (float) $value >= 1.0;
} Try / catch
$result = HLookup::lookup($key, $table, $indexNumber);
if ($result === '#VALUE!' && is_numeric($indexNumber) && (float) $indexNumber < 1.0) {
// 0-based or negative index slipped through
} Prevention
- Translate 0-based PHP column indexes with +1 before calling
- Test lookups with index values 0, 1 and count(columns)
- Validate user-entered column numbers against the table's actual width
When it happens
Trigger: =VLOOKUP(x, tbl, 0, FALSE); HLOOKUP with an index computed by a 0-based PHP loop; negative index from an offset subtraction (e.g. $col - 1 where $col is 0).
Common situations: Developers carrying 0-based array indexing habits into spreadsheet lookups; index derived as count()-1 by mistake; blanks coerced to 0.
Related errors
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/d5fbcae20291e461.
Report an issue: GitHub.