PHPOffice/PHPWord · error · PhpOffice\PhpWord\Exception\Exception

Can not clone row, template variable not found or variable…

Error message

Can not clone row, template variable not found or variable contains markup.

What it means

TemplateProcessor::cloneRow searches the main document part for the completed macro '${search}'; if strpos returns false (or 0-falsy) it throws 'Can not clone row, template variable not found or variable contains markup.' — meaning the variable text is absent, split across XML runs (markup inside it), or the search did not match.

Solutions

  1. Retype the placeholder in Word as plain text (paste into Notepad first, then retype in the cell) to keep it in a single XML run
  2. Call $processor->cloneRow('variable', $n) with the bare variable name — ensureMacroCompleted adds ${}
  3. Verify the placeholder exists in the main body (not header/footer) by checking the template XML
  4. Ensure the template variable is in a table row (cloneRow is for table rows only)
  5. Use setValue before inspecting, or inspect the saved XML to confirm the exact placeholder text

Example fix

// before
$processor->cloneRow('${rowId}', 3); // wrong: no ${} needed, may not match
// after
$processor->cloneRow('rowId', 3);
$processor->setValue('rowId#1', 'first');
Defensive patterns

Strategy: try-catch

Validate before calling

$snippet = file_get_contents('template.docx'); // or inspect raw main part
// verify the placeholder survives as a contiguous string in the XML:
$hasMacro = strpos($mainPartXml, '${rowId}') !== false;
if (!$hasMacro) {
    throw new RuntimeException('Placeholder ${rowId} split across XML runs or missing');
}

Try / catch

try {
    $template->cloneRow('rowId', 3);
} catch (\PhpOffice\PhpWord\Exception\Exception $e) {
    if (str_contains($e->getMessage(), 'Can not clone row')) {
        // retype placeholder in Word as plain text and re-check
    }
}

Prevention

When it happens

Trigger: cloneRow('variable', n) where '${variable}' is not in the template, or Word split the placeholder into multiple <w:t> runs due to spell-check/formatting so the contiguous string does not exist in the raw XML; also triggers when the placeholder is at position 0 handled incorrectly or in headers/footers (cloneRow only searches the main part).

Common situations: Placeholders typed into Word where autocorrect/spellcheck breaks them into separate XML runs; variable placed in a table in a header instead of the body; typo in variable name; missing ${...} delimiters in the search argument.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of PHPOffice/PHPWord@aef95c0415 (2026-09-14). Data as JSON: /api/errors/a16240bc07a2a807. Report an issue: GitHub.

Appendix: source

Thrown at src/PhpWord/TemplateProcessor.php:768

     */
    public function getVariables()
    {
        return array_keys($this->getVariableCount());
    }

    /**
     * Clone a table row in a template document.
     *
     * @param string $search
     * @param int $numberOfClones
     */
    public function cloneRow($search, $numberOfClones): void
    {
        $search = static::ensureMacroCompleted($search);

        $tagPos = strpos($this->tempDocumentMainPart, $search);
        if (!$tagPos) {
            throw new Exception('Can not clone row, template variable not found or variable contains markup.');
        }

        $rowStart = $this->findRowStart($tagPos);
        $rowEnd = $this->findRowEnd($tagPos);
        $xmlRow = $this->getSlice($rowStart, $rowEnd);

        // Check if there's a cell spanning multiple rows.
        if (preg_match('#<w:vMerge w:val="restart"/>#', $xmlRow)) {
            // $extraRowStart = $rowEnd;
            $extraRowEnd = $rowEnd;
            while (true) {
                $extraRowStart = $this->findRowStart($extraRowEnd + 1);
                $extraRowEnd = $this->findRowEnd($extraRowEnd + 1);

                // If extraRowEnd is lower then 7, there was no next row found.
                if ($extraRowEnd < 7) {
                    break;
                }

View on GitHub (pinned to aef95c0415)