guzzle/guzzle · error · GuzzleHttp\Exception\InvalidArgumentException
%s entries must be strings or stringable objects.
Error message
%s entries must be strings or stringable objects.
What it means
Thrown by normalizeCurlHeaderOptions() when an entry in CURLOPT_HTTPHEADER or CURLOPT_PROXYHEADER is neither a string nor an object implementing __toString(). cURL header arrays must be strings, so any other type (int, array, null, plain object) is rejected to avoid silent type coercion bugs.
Source
Thrown at src/Handler/CurlFactory.php:1569
#[\SensitiveParameter]
array &$conf
): void {
$options = [\CURLOPT_HTTPHEADER => 'CURLOPT_HTTPHEADER'];
if (\defined('CURLOPT_PROXYHEADER')) {
$options[(int) \constant('CURLOPT_PROXYHEADER')] = 'CURLOPT_PROXYHEADER';
}
foreach ($options as $option => $label) {
if (!\array_key_exists($option, $conf) || !\is_array($conf[$option])) {
continue;
}
$normalized = [];
foreach ($conf[$option] as $key => $entry) {
if (\is_object($entry) && \method_exists($entry, '__toString')) {
$entry = (string) $entry;
} elseif (!\is_string($entry)) {
throw new InvalidArgumentException(\sprintf('%s entries must be strings or stringable objects.', $label));
}
if (\strpbrk($entry, "\r\n") !== false) {
throw new InvalidArgumentException(\sprintf('%s entries must not contain a carriage return or line feed.', $label));
}
$normalized[$key] = $entry;
}
$conf[$option] = $normalized;
}
}
/**
* @param array<int|string, mixed> $conf
*/
private static function requiresFreshConnectionForAuthenticatedProxy(RequestInterface $request, string $proxy, array $conf): bool
{View on GitHub (pinned to 9b200fc580)
Solutions
- Cast each header entry to a string before adding it to CURLOPT_HTTPHEADER/CURLOPT_PROXYHEADER.
- Implement __toString() on value objects used as header entries.
- Filter the array to remove non-string entries before passing.
Example fix
// before ['curl' => [CURLOPT_HTTPHEADER => ['Accept: application/json', $config['headerValue']]]] // where $config['headerValue'] is null/int. // after ['curl' => [CURLOPT_HTTPHEADER => ['Accept: application/json', (string) $config['headerValue']]]]
Defensive patterns
Strategy: type-guard
Validate before calling
foreach ($options['curl'][CURLOPT_HTTPHEADER] ?? [] as $entry) {
if (!is_string($entry) && !(is_object($entry) && method_exists($entry, '__toString'))) {
throw new \InvalidArgumentException('CURLOPT_HTTPHEADER entries must be strings or stringable.');
}
} Type guard
/** @param mixed $entry */
function isStringableHeader($entry): bool {
return is_string($entry) || (is_object($entry) && method_exists($entry, '__toString'));
} Try / catch
try {
$client->get($url, $options);
} catch (\GuzzleHttp\Exception\InvalidArgumentException $e) {
// Cast or filter header entries to strings.
} Prevention
- Always cast dynamic header values to string before adding them.
- Implement __toString on value objects used as headers.
When it happens
Trigger: Passing ['curl' => [CURLOPT_HTTPHEADER => ['X-Foo: bar', ['nested']]]] or ['curl' => [CURLOPT_HTTPHEADER => ['X-Foo: bar', 123]]], where an entry is an array, integer, boolean, or non-stringable object.
Common situations: Building header arrays dynamically and forgetting to cast values; appending an array by mistake; passing a value object without __toString.
Related errors
- headers must be an array
- The headers array must have header name as keys.
- %s entries must not contain a carriage return or line feed.
- CURLOPT_PROXYHEADER must be an array when a Proxy-Authorizat
- The "max_host_connections" and "max_total_connections" clien
AI-assisted analysis of guzzle/guzzle@9b200fc580 (2026-08-04).
Data as JSON: /data/errors/5d3c548ae766ebf0.json.
Report an issue: GitHub.