getgrav/grav · error · InvalidArgumentException
URL must be a string
Error message
URL must be a string
What it means
UriFactory::parseUrl() is Grav's UTF-8 aware replacement for parse_url(); it first asserts the input is a PHP string and throws InvalidArgumentException otherwise. It is reached through UriFactory::createFromString() and direct parseUrl() calls. Non-string values (arrays, null, objects) are rejected before any parsing happens.
Source
Thrown at system/src/Grav/Framework/Uri/UriFactory.php:123
'pass' => $pass,
'host' => $host,
'port' => $port,
'path' => $path,
'query' => $query
];
}
/**
* UTF-8 aware parse_url() implementation.
*
* @param string $url
* @return array
* @throws InvalidArgumentException
*/
public static function parseUrl($url)
{
if (!is_string($url)) {
throw new InvalidArgumentException('URL must be a string');
}
$encodedUrl = preg_replace_callback(
'%[^:/@?&=#]+%u',
static fn($matches) => rawurlencode((string) $matches[0]),
$url
);
$parts = is_string($encodedUrl) ? parse_url($encodedUrl) : false;
if ($parts === false) {
throw new InvalidArgumentException("Malformed URL: {$url}");
}
return $parts;
}
/**
* Parse query string and return it as an array.View on GitHub (pinned to 6040efed04)
Solutions
- Validate with is_string() before calling parseUrl()/createFromString()
- Reject array input explicitly when reading query parameters you intend to use as URLs
- Cast known scalar inputs: `(string) $value` — but reject arrays/objects first
Example fix
// before $parts = UriFactory::parseUrl($_GET['redirect'] ?? null); // ?redirect[]=x -> array -> throws // after $raw = $_GET['redirect'] ?? ''; $parts = is_string($raw) && $raw !== '' ? UriFactory::parseUrl($raw) : null;
Defensive patterns
Strategy: type-guard
Validate before calling
$url = $_GET['url'] ?? '';
if (!is_string($url) || $url === '') {
throw new \InvalidArgumentException('url parameter must be a non-empty string');
} Type guard
/** @param mixed $value */
function isParsableUrlString(mixed $value): bool
{
return is_string($value) && $value !== '';
} Try / catch
try {
$uri = Grav\Framework\Uri\UriFactory::createFromString($url);
} catch (\InvalidArgumentException $e) {
// Covers both 'URL must be a string' and 'Malformed URL'
throw new HttpBadRequest($request, 'Invalid URL parameter');
} Prevention
- Validate query parameters with is_string() before using them as URLs (guards ?param[]= injection)
- Give optional URL inputs explicit string defaults ('' ) rather than null
- Wrap third-party input parsing in try/catch and map InvalidArgumentException to a 400 response
When it happens
Trigger: Passing `$_GET['url']` straight in when the client sends `?url[]=x` (PHP turns that into an array); passing null from an optional parameter default; passing an object (even Stringable — no __toString invocation happens here).
Common situations: Endpoints that parse user-supplied redirect targets or link URLs; array-syntax query-parameter injection (`param[]=`) hitting unvalidated code; optional config values that default to null being fed to createFromString().
Related errors
- Malformed URL: {$url}
- Uri scheme must be a string
- Uri user info must be a string
- Uri host must be a string
- Uri port must be null or an integer between 0 and 65535
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/bf6fa9d4a31b1e5a.
Report an issue: GitHub.