slimphp/Slim · error · InvalidArgumentException
Missing data for URL segment: %s
Error message
Missing data for URL segment: %s
What it means
RouteParser::relativeUrlFor() (used by urlFor() and fullUrlFor()) expands the route pattern into URL segments, filling each {placeholder} from the $data array you pass. It tries every expression variant from most specific (all optional params filled) to least; if no variant can be satisfied — i.e. some required placeholder has no entry in $data — it throws this InvalidArgumentException, naming the first placeholder it could not resolve. The lookup uses array_key_exists, so keys must match placeholder names exactly (case-sensitive); numeric-indexed arrays from older idioms do not work.
Source
Thrown at Slim/Routing/RouteParser.php:90
$segmentName = $segment[0];
break;
}
$segments[] = $data[$segment[0]];
}
/*
* If we get to this logic block we have found all the parameters
* for the provided $data which means we don't need to continue testing
* less specific expressions
*/
if (!empty($segments)) {
break;
}
}
if (empty($segments)) {
throw new InvalidArgumentException('Missing data for URL segment: ' . $segmentName);
}
$url = implode('', $segments);
if ($queryParams) {
$url .= '?' . http_build_query($queryParams);
}
return $url;
}
/**
* {@inheritdoc}
*/
public function urlFor(string $routeName, array $data = [], array $queryParams = []): string
{
$basePath = $this->routeCollector->getBasePath();
$url = $this->relativeUrlFor($routeName, $data, $queryParams);
View on GitHub (pinned to 80900fb39c)
Solutions
- Supply a value for every required placeholder, keyed exactly by its name: urlFor('user.post', ['id' => 1, 'postId' => 9])
- Check the data keys against the route pattern for typos and case mismatches — placeholder names are case-sensitive
- If the segment is legitimately omittable, make it optional in the pattern: /user/{id}[/{slug}]
- Add a pre-flight check that diffs array keys against required placeholders parsed from the pattern before calling urlFor()
Example fix
// before
$app->get('/user/{id}/posts/{postId}', PostAction::class)->setName('user.post');
$url = $routeParser->urlFor('user.post', ['id' => 1]); // throws 'Missing data for URL segment: postId'
// after
$url = $routeParser->urlFor('user.post', ['id' => 1, 'postId' => 9]); Defensive patterns
Strategy: validation
Validate before calling
function requiredRouteParams(string $pattern): array
{
// strip optional segments (square brackets, innermost first)
while (preg_match('/\[[^\[\]]*\]/', $pattern)) {
$pattern = (string) preg_replace('/\[[^\[\]]*\]/', '', $pattern);
}
preg_match_all('/\{([^}:]+)(?::[^}]*)?\}/', $pattern, $m);
return $m[1];
}
$route = $collector->getNamedRoute($name);
$missing = array_diff(requiredRouteParams($route->getPattern()), array_keys($data));
if ($missing) {
throw new InvalidArgumentException(
sprintf('urlFor(%s) is missing: %s', $name, implode(', ', $missing))
);
}
$url = $routeParser->urlFor($name, $data); Try / catch
try {
$url = $routeParser->urlFor($name, $data);
} catch (InvalidArgumentException $e) {
// message names the missing placeholder — log it and fall back to a safe default
$log->warning($e->getMessage(), ['route' => $name, 'data' => $data]);
$url = $defaultPath ?? '/';
} Prevention
- Derive data keys from the pattern itself (or central route metadata) instead of hand-typing them at call sites
- When adding a new required segment to a pattern, grep all urlFor()/fullUrlFor() call sites for that route name in the same commit
- Remember only placeholders inside [...] are optional; everything else must appear in $data keyed by exact name
- Add the requiredRouteParams() pre-flight check in link-builder services that accept dynamic route data
When it happens
Trigger: Calling $routeParser->urlFor('user.post', ['id' => 1]) for a route pattern like /user/{id}/posts/{postId} — the message will name 'postId'; a typo'd or wrongly-cased data key ('userId' vs 'id'); passing an empty array for a route with any required placeholder; forgetting that only placeholders inside [...] brackets are optional and can be omitted.
Common situations: Adding a new required segment to a route pattern but not updating every urlFor() call site; mixed conventions where some code passes positional arrays; template or config files storing route data keyed by old names; optional segments like /archive[/{year}] working without data while /user/{id} requiring it, causing inconsistent expectations across routes.
Related errors
- Named route does not exist for name: %s
- No base path defined.
- Route collector cache file directory `%s` is not writable
- Route not found, looks like your route cache is stale.
- Cannot create RouteContext before routing has been completed
AI-assisted analysis of slimphp/Slim@80900fb39c (2026-08-21).
Data as JSON: /api/errors/37a557c24b1d8c5f.
Report an issue: GitHub.