# Replace text split across runs in python-docx

> Replace a phrase that spans multiple Word runs without flattening the paragraph. Decide how replacement text inherits formatting and handle ambiguous matches.

Maintained by Vespper. Updated 2026-09-27.

Canonical page: https://www.vespper.com/guides/python-docx-replace-across-runs

Topic: [Python DOCX troubleshooting](https://www.vespper.com/guides#python). [Start the learning path](https://www.vespper.com/guides#start-here).

A Word paragraph can display one continuous sentence while storing it in several runs. Searching each run separately misses a phrase that crosses a run boundary. Searching `paragraph.text` finds the phrase, but assigning back to that property replaces the paragraph's run structure.

This is a documented developer problem in [python-docx issue 415](https://github.com/python-openxml/python-docx/issues/415). The following example demonstrates a deliberately narrow solution for a single match in a paragraph containing only ordinary text runs. It uses python-docx 1.2.0.

## Map character positions back to runs

```python
from docx import Document
from docx.oxml.ns import qn

def replace_once(paragraph, old, new):
    if not old:
        raise ValueError("The search text must not be empty")
    # Reject hyperlinks, fields, drawings, breaks, and revision containers.
    if any(child.tag not in {qn("w:pPr"), qn("w:r")}
           for child in paragraph._p):
        raise ValueError("Expected direct text runs only")
    runs = paragraph.runs
    for run in runs:
        if any(child.tag not in {qn("w:rPr"), qn("w:t")}
               for child in run._r):
            raise ValueError("Expected ordinary text only")
    text = "".join(run.text for run in runs)
    start = text.find(old)
    if start < 0 or text.find(old, start + 1) >= 0:
        raise ValueError("Expected exactly one match")
    stop = start + len(old)
    offset = 0
    for run in runs:
        value = run.text
        end = offset + len(value)
        if offset < stop and end > start:
            left = max(0, start - offset)
            right = min(len(value), stop - offset)
            replacement = new if offset <= start < end else ""
            run.text = value[:left] + replacement + value[right:]
        offset = end

doc = Document()
p = doc.add_paragraph()
p.add_run("Fri").bold = True
p.add_run("day deadline")
replace_once(p, "Friday", "Monday")
assert p.text == "Monday deadline"
assert p.runs[0].bold is True
```

## Choose a formatting policy

This function gives the entire replacement the formatting of the run where the match begins. In the example, all of “Monday” becomes bold, although only “Fri” was bold originally. Unmatched text remains in its original runs. That is a policy choice, not the only correct interpretation of mixed formatting.

The two underscored XML properties are implementation details used to reject unsupported content. Pin and test your library version. This is not a complete replacement engine for arbitrary customer documents.

## Move from a script to an agent

An agent also needs to establish which occurrence the user means. For Vespper, search the document, take the complete current HTML block, and edit that block with its classes intact. If the block is not unique, extend the anchor with adjacent content. The agent works with returned document structure rather than implementing run offsets itself.

Test your replacement on an absent phrase, a repeated phrase, mixed formatting, and a run containing an image. The last case should be rejected by this example. See the [Python text API](https://python-docx.readthedocs.io/en/stable/api/text.html) for the behavior of paragraph and run setters.

## Next steps

- [Check images inside text runs](https://www.vespper.com/guides/python-docx-preserve-images): Inspect non-text content before assigning to a run's text.
- [Define document preservation checks](https://www.vespper.com/guides/preserve-docx-formatting): Expand validation beyond the matched phrase to surrounding formatting and document structure.

## Related guides

- [python-docx: create and edit Word documents in Python](https://www.vespper.com/llms/python-docx.md)

