twigphp/Twig · error · SyntaxError
Argument " " is defined twice for " ".
Error message
Argument "%s" is defined twice for %s "%s".
What it means
While mapping extracted template arguments onto the callable's parameters, extractArguments() detects when a parameter has already received a value — both positionally and by name. It throws a SyntaxError naming the doubly-defined argument, the callable type, and its name, preventing silent overwrites during compilation.
Solutions
- Remove the duplicate — keep either the positional or the named value, not both.
- Use named arguments exclusively for the affected call.
- In extension code, guard against programmatic argument arrays that can collide with positional entries.
Example fix
{# before #}
{{ filter.colorize('text', color='red', 'red') }}
{# after #}
{{ filter.colorize('text', color='red') }} Defensive patterns
Strategy: validation
Validate before calling
// Normalize names the way Twig does and reject collisions before rendering
function hasDuplicateCallableArgs(array $supplied, array $paramNames): bool {
$norm = fn (string $s) => strtolower(preg_replace('/[_A-Z]/', '_', lcfirst($s)));
$used = [];
foreach ($supplied as $k => $_) {
$key = is_int($k) ? (isset($paramNames[$k]) ? $norm($paramNames[$k]) : '#'.$k) : $norm($k);
if (isset($used[$key])) { return true; }
$used[$key] = true;
}
return false;
} Try / catch
try {
$out = $twig->render($template, $ctx);
} catch (Twig\Error\SyntaxError $e) {
if (str_contains($e->getMessage(), 'is defined twice for')) {
error_log($e->getMessage().' at '.$e->getTemplateLine());
}
throw $e;
} Prevention
- When switching a call to named arguments, remove the positional duplicate in the same change.
- Avoid mixing positional and named arguments for the same callable.
- Run template lints in CI to surface normalization collisions (case/underscore variants).
When it happens
Trigger: A template call supplies argument N positionally and the same parameter also by name (normalized names collide), e.g. {{ replace('a', 'b', from='a') }} where the first parameter is $from.
Common situations: Migrating calls from positional to named style and leaving one value in both forms; dynamic argument building in extensions producing collisions; case/underscore naming differences that normalize to the same parameter.
Related errors
- Calling the "parent" function outside of a block is…
- Calling the "parent" function on a template that does not…
- Argument " " is defined twice for " ".
- You cannot assign a value to
- A template that extends another one cannot include content…
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/f4384ee6eb0a38ad.
Report an issue: GitHub.
Appendix: source
Thrown at src/Util/CallableArgumentsExtractor.php:96
$missingArguments = [];
$optionalArguments = [];
$pos = 0;
foreach ($callableParameters as $callableParameter) {
$callableParameterName = $callableParameter->name;
if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
if ('start' === $callableParameterName) {
$callableParameterName = 'low';
} elseif ('end' === $callableParameterName) {
$callableParameterName = 'high';
}
}
$callableParameterNames[] = $callableParameterName;
$normalizedCallableParameterName = $this->normalizeName($callableParameterName);
if (\array_key_exists($normalizedCallableParameterName, $extractedArguments)) {
if (\array_key_exists($pos, $extractedArguments)) {
throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $callableParameterName, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());
}
if (\count($missingArguments)) {
throw new SyntaxError(\sprintf(
'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".',
$callableParameterName, $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', array_map([$this, 'toSnakeCase'], $callableParameterNames)), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments)
), $this->node->getTemplateLine(), $this->node->getSourceContext());
}
$arguments = array_merge($arguments, $optionalArguments);
$arguments[] = $extractedArguments[$normalizedCallableParameterName];
unset($extractedArguments[$normalizedCallableParameterName]);
$optionalArguments = [];
} elseif (\array_key_exists($pos, $extractedArguments)) {
$arguments = array_merge($arguments, $optionalArguments);
$arguments[] = $extractedArguments[$pos];
unset($extractedArguments[$pos]);
$optionalArguments = [];View on GitHub (pinned to a414c3a491)