PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Calculation\Exception
#VALUE!
#VALUE!
Error message
#VALUE!
What it means
Indirect::a1Format() (Indirect.php:22-33) validates INDIRECT's second argument (A1 vs R1C1): bool and numeric are accepted, null defaults to A1 mode, but any string throws #VALUE!, which INDIRECT() catches and returns as its result. Numeric strings such as "1" or "0" are rejected too - only real ints/floats/bools pass the is_string() check.
Source
Thrown at src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php:29
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Indirect
{
/**
* Determine whether cell address is in A1 (true) or R1C1 (false) format.
*
* @param mixed $a1fmt Expect bool Helpers::CELLADDRESS_USE_A1 or CELLADDRESS_USE_R1C1,
* but can be provided as numeric which is cast to bool
*/
private static function a1Format(mixed $a1fmt): bool
{
$a1fmt = Functions::flattenSingleValue($a1fmt);
if ($a1fmt === null) {
return Helpers::CELLADDRESS_USE_A1;
}
if (is_string($a1fmt)) {
throw new Exception(ExcelError::VALUE());
}
return (bool) $a1fmt;
}
/**
* Convert cellAddress to string, verify not null string.
*
* @param null|mixed[]|string $cellAddress
*/
private static function validateAddress(array|string|null $cellAddress): string
{
$cellAddress = Functions::flattenSingleValue($cellAddress);
if (!is_string($cellAddress) || !$cellAddress) {
throw new Exception(ExcelError::REF());
}
return $cellAddress;View on GitHub (pinned to 65b080eef4)
Solutions
- Pass a real bool: INDIRECT($addr, true) or Helpers::CELLADDRESS_USE_A1 / CELLADDRESS_USE_R1C1
- Cast string flags before use: (bool) $flag or filter_var($flag, FILTER_VALIDATE_BOOL)
- Omit the second argument entirely when A1 mode is intended (null defaults to A1)
Example fix
// before $a1 = $config['a1Mode']; // e.g. "false" from JSON -> '#VALUE!' // after $a1 = filter_var($config['a1Mode'] ?? true, FILTER_VALIDATE_BOOL); $result = Indirect::INDIRECT($address, $a1, $cell);
Defensive patterns
Strategy: validation
Validate before calling
$a1 = match (true) {
is_bool($a1fmt) => $a1fmt,
is_numeric($a1fmt) => (bool) $a1fmt,
default => true, // null/omitted defaults to A1
}; Type guard
function isValidA1Flag(mixed $value): bool
{
return $value === null || is_bool($value) || is_numeric($value);
} Try / catch
$result = Indirect::INDIRECT($address, $a1, $cell);
if ($result === '#VALUE!' && is_string($a1)) {
// second argument was a string; cast to bool and retry
} Prevention
- Serialize config flags as real booleans, not 'true'/'false' strings
- Numeric strings ('1'/'0') are rejected too - only real numbers/bools pass
- Omit the second argument when A1 mode is intended
When it happens
Trigger: =INDIRECT("A1", "TRUE") or INDIRECT(addr, "0"); an A1 flag read from a text cell containing 'TRUE'; JSON/config supplying "a1": "false" straight into the formula or call.
Common situations: Config-driven reference building where the flag arrives serialized as text; UI checkboxes serialized as 'true'/'false'; passing the Helper constants as strings instead of bools.
Related errors
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/b9b10df3d3be3002.
Report an issue: GitHub.