PHPOffice/PHPWord · error · PhpOffice\PhpWord\Exception\Exception
Can not delete row , template variable not found or…
Error message
Can not delete row %s, template variable not found or variable contains markup.
What it means
TemplateProcessor::deleteRow() searches the document's main XML part for the row placeholder template variable. If the placeholder cannot be found at all, or is found but wrapped in split XML runs (markup), the table boundaries cannot be located and this exception is thrown. The row will not be deleted.
Solutions
- Verify the placeholder exists exactly once in the table row of the docx (unzip and grep document.xml).
- Retype the placeholder in Word as plain, contiguous text with uniform formatting so it stays in one <w:t> run.
- Preprocess document.xml to merge split runs before processing.
- Check that the placeholder is not already wrapped in ${...} when passed (deleteRow adds macro chars itself unless they are present).
Example fix
// before
$processor->deleteRow('CustomerRow'); // variable split by Word markup -> throws
// after
// In document.xml ensure the row contains a single run:
// <w:r><w:t>CustomerRow</w:t></w:r>
$processor->deleteRow('CustomerRow'); Defensive patterns
Strategy: validation
Validate before calling
function placeholderIsPlainText(string $templatePath, string $var): bool {
$zip = new ZipArchive();
$zip->open($templatePath);
$xml = $zip->getFromName('word/document.xml');
$zip->close();
return preg_match('/<w:t[^>]*>\s*\$\{' . preg_quote($var, '/') . '\}\s*<\/w:t>/', $xml) === 1;
} Type guard
function isDeleteRowSafe(string $xml, string $var): bool {
return preg_match('/<w:tbl>(?:(?!<\/w:tbl>).)*?<w:t[^>]*>\s*\$\{' . preg_quote($var, '/') . '\}\s*<\/w:t>/s', $xml) === 1;
} Try / catch
try {
$processor->deleteRow($var);
} catch (\PhpOffice\PhpWord\Exception\Exception $e) {
if (str_contains($e->getMessage(), 'Can not delete row')) {
// log and continue with the row intact, or fix the template
} else {
throw $e;
}
} Prevention
- Keep template variables as single contiguous text runs (retype them after any Word edit).
- Disable spell-check marks / proofErr in templates or clean the XML before processing.
- Only call deleteRow on variables that live inside table rows.
- Add a pre-flight check that greps document.xml for each variable before processing.
When it happens
Trigger: Calling $templateProcessor->deleteRow('placeholder') where the placeholder text does not exist in the document, or where Word split the placeholder across multiple <w:r> runs (e.g. spell-check proofErr tags, formatting changes mid-word, autocorrect).
Common situations: Typo in the variable name vs. the docx template; editing the template in Word causing the variable to be split with markup; using deleteRow on a variable that is not inside a table row; template saved by a different tool that encodes characters differently (e.g. smart quotes).
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
- Can not find the start position of the table.
- Can not find the start position of the row to clone.
- The part " " doesn't exist
- Could not load the given XML document.
- Invalid value, on of ' . implode(', ', $position) . '…
AI-assisted analysis of PHPOffice/PHPWord@aef95c0415 (2026-09-14).
Data as JSON: /api/errors/814e0a5d55c04774.
Report an issue: GitHub.
Appendix: source
Thrown at src/PhpWord/TemplateProcessor.php:819
$result = $this->getSlice(0, $rowStart);
$result .= implode('', $this->indexClonedVariables($numberOfClones, $xmlRow));
$result .= $this->getSlice($rowEnd);
$this->tempDocumentMainPart = $result;
}
/**
* Delete a table row in a template document.
*/
public function deleteRow(string $search): void
{
if (self::$macroOpeningChars !== substr($search, 0, 2) && self::$macroClosingChars !== substr($search, -1)) {
$search = self::$macroOpeningChars . $search . self::$macroClosingChars;
}
$tagPos = strpos($this->tempDocumentMainPart, $search);
if (!$tagPos) {
throw new Exception(sprintf('Can not delete row %s, template variable not found or variable contains markup.', $search));
}
$tableStart = $this->findTableStart($tagPos);
$tableEnd = $this->findTableEnd($tagPos);
$xmlTable = $this->getSlice($tableStart, $tableEnd);
if (substr_count($xmlTable, '<w:tr') === 1) {
$this->tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd);
return;
}
$rowStart = $this->findRowStart($tagPos);
$rowEnd = $this->findRowEnd($tagPos);
$xmlRow = $this->getSlice($rowStart, $rowEnd);
$this->tempDocumentMainPart = $this->getSlice(0, $rowStart) . $this->getSlice($rowEnd);
View on GitHub (pinned to aef95c0415)