twigphp/Twig · error · SyntaxError

Argument " " is defined twice for " ".

Error message

Argument "%s" is defined twice for %s "%s".

What it means

During compilation of a function/filter/method call, Twig maps named arguments to callable parameters. This error is thrown when the same argument name appears twice in the call's named-argument node, i.e. the template author passed the same named argument more than once (or a name collides with a positional slot already filled). Twig rejects this unconditionally at compile time as a SyntaxError.

Solutions

  1. Remove the duplicate named argument from the template call
  2. If both values were intended, rename one or combine them
  3. If generating templates programmatically, deduplicate argument names before rendering
  4. Check the exact call signature with twig's debug output or the function's PHP signature to see accepted names

Example fix

// before (template)
{{ date(post.created_at, format='Y-m-d', format='d/m/Y') }}
// after
{{ date(post.created_at, format='Y-m-d') }}
Defensive patterns

Strategy: validation

Validate before calling

// Twig (PHP) pre-check before compiling user templates
$names = [];
foreach ($node->getNode('arguments') as $key => $arg) {
    if (\is_string($key)) {
        if (isset($names[$key])) { throw new \RuntimeException("Duplicate argument '$key'"); }
        $names[$key] = true;
    }
}

Try / catch

try {
    $twig->parse($twig->tokenize(new \Twig\Source($template, 'index')));
} catch (\Twig\Error\SyntaxError $e) {
    if (str_contains($e->getMessage(), 'is defined twice')) {
        // surface template line: $e->getTemplateLine()
    }
}

Prevention

When it happens

Trigger: A template like {{ date|date(format='d', format='m') }}, or calling a function with a named argument whose name is also claimed by an earlier positional argument, e.g. {{ range(1, low=2) }}. In getArguments(), the name is found in $parameters and its target position $pos is already occupied.

Common situations: Copy-pasted argument lists where a parameter was renamed but the old named argument left in place; dynamic templates generated by code that appends named arguments without deduplication; mixing positional and named arguments where a named argument repeats a positional one.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13). Data as JSON: /api/errors/7908c3d6ade1752f. Report an issue: GitHub.

Appendix: source

Thrown at src/Node/Expression/CallExpression.php:199

        $names = [];
        $missingArguments = [];
        $optionalArguments = [];
        $pos = 0;
        foreach ($callableParameters as $callableParameter) {
            $name = $this->normalizeName($callableParameter->name);
            if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
                if ('start' === $name) {
                    $name = 'low';
                } elseif ('end' === $name) {
                    $name = 'high';
                }
            }

            $names[] = $name;

            if (\array_key_exists($name, $parameters)) {
                if (\array_key_exists($pos, $parameters)) {
                    throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->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".',
                        $name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments)
                    ), $this->getTemplateLine(), $this->getSourceContext());
                }

                $arguments = array_merge($arguments, $optionalArguments);
                $arguments[] = $parameters[$name];
                unset($parameters[$name]);
                $optionalArguments = [];
            } elseif (\array_key_exists($pos, $parameters)) {
                $arguments = array_merge($arguments, $optionalArguments);
                $arguments[] = $parameters[$pos];
                unset($parameters[$pos]);
                $optionalArguments = [];

View on GitHub (pinned to a414c3a491)