---
title: "Find and replace text in a Word document in C#"
url: https://recursiveai.org/onedocument/cookbook/find-and-replace-in-word/
description: "Replace a phrase in a DOCX, even when its formatting changes halfway through."
version: "1.0.0"
---

Replace a phrase in a DOCX, even when its formatting changes halfway through.

```shell
dotnet add package RecursiveAI.OneDocument.Documents
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using RecursiveAI.OneDocument.Documents;

using var doc = Document.Open("invoice.docx");
doc.Replace("Northwind", "Contoso");
doc.SaveAs("updated.docx");
```

Writes `updated.docx` with “Contoso” in place of “Northwind”; the snippet prints nothing. The input file is in the zip.

Unzip [the example](https://recursiveai.org/onedocument/cookbook/find-and-replace-in-word/find-and-replace-in-word.zip), then `dotnet run`.

## Full program

A Word run is a piece of text that shares the same formatting, such as a bold word inside a paragraph. Any `.docx` works: change the input file name and the phrase to find and replace.

`invoice.docx` is included in the example. Its paragraph is `Invoice for Northwind: 120 EUR.`; only “North” is bold.

```csharp
using RecursiveAI.OneDocument.Documents;

using var doc = Document.Open("invoice.docx");
var report = doc.Replace("Northwind", "Contoso");
doc.SaveAs("updated.docx");
Console.WriteLine($"Replaced: {report.Count}");
foreach (var issue in report.Issues)
    Console.WriteLine($"{issue.Code}: {issue.Message}");
Console.WriteLine(doc.ExtractText().Trim());
```

`dotnet run` prints:

```text
Replaced: 1
Invoice for Contoso: 120 EUR.
```

Inspect `report.Issues` for matches left unchanged; an invalid regular expression raises `ArgumentException`.

## Variations

### Case-sensitive whole words

Replace the `var report = doc.Replace(...)` line in the full program with:

```csharp
var report = doc.Replace("Northwind", "Contoso",
    new DocumentReplaceOptions {
        Search = new() { MatchCase = true,
            WholeWord = true }
    });
```

Output:

```text
Replaced: 1
Invoice for Contoso: 120 EUR.
```

## Related recipes

- [Convert PowerPoint to PDF in C#](https://recursiveai.org/onedocument/cookbook/convert-pptx-to-pdf/)
- [Fill a Word template in C#](https://recursiveai.org/onedocument/cookbook/fill-word-template/)
- [Mail merge DOCX files in C#](https://recursiveai.org/onedocument/cookbook/mail-merge/)
- [Extract text from DOCX, XLSX and PPTX in C#](https://recursiveai.org/onedocument/cookbook/extract-text-from-office-files/)

## The short answer

Use `Document.Open`, call `doc.Replace`, then save the changed document with `SaveAs`. OneDocument searches the text of each paragraph, joining its runs before matching. You don’t need to guess how Word split a name across formatting changes. The example deliberately splits “Northwind” between a bold run and an ordinary run, yet finds the complete name.

The return value is a `ReplaceReport`, not just a count. It lists each replacement and its location, plus each match left unchanged and its reason. The complete program prints the count and any issues so that a successful save isn’t mistaken for a successful replacement. See the [find-and-replace documentation](https://recursiveai.org/onedocument/docs/operations-find-replace/) for the matching and editing contract.

## How do I find and replace text in a Word document?

For a fixed phrase, use literal matching, as in the snippet at the top. Literal matching is case-insensitive in the invariant culture by default. Replacement text is used exactly as written, including dollar signs. If letter case matters, set `MatchCase`; if only an entire word should match, set `WholeWord`. The variation above combines both options. Word boundaries mean the neighboring characters are not letters, digits or underscores.

The default document search includes the body, tables, headers, footers, footnotes, endnotes, comments and text boxes. This is useful when a company name also appears in the footer. A replacement in an unexpected story is still a real replacement: inspect the report’s locations when you need to review the scope of a batch edit.

New text takes the properties of the first matched character’s run. This is a concrete formatting rule, not a promise that every original run boundary survives. In this example, the replacement starts where the bold part of the original name started. Surrounding text stays in its original runs. Parts with no match are not modified.

Save to a different path if you want the source file available for comparison. The call edits the open model; saving is the separate step that writes the result. For edits that reviewers must accept or reject, `DocumentReplaceOptions.TrackedAuthor` records replacements as tracked deletions and insertions.

## Why isn’t Find and Replace working in Word?

Check the search boundaries first. A match can cross runs, hyperlinks and content controls inside a paragraph, but never crosses paragraphs. Paragraph marks cannot be used to join or split paragraphs through this API. Two words that look adjacent on a page may therefore belong to different searchable paragraphs.

Next, distinguish visible text from field instructions. Cached field results are searched as text. Field codes are searched only when `IncludeFieldCodes` is enabled, and each instruction is searched on its own. Replacing a cached result does not recompute the field or re-run a content control’s data binding. Text inside tracked deletions is not part of the current paragraph text.

Finally, distinguish literal patterns from regular expressions. `UseRegex` enables .NET regex syntax and replacement groups; it is not enabled by default. An invalid pattern throws rather than quietly reporting no matches. Regex execution is bounded by the configured timeout, and a timeout raises `OfficeLimitException`. Empty matches are skipped and matches don’t overlap. These rules explain common “found less than expected” results without resorting to editing the DOCX archive as raw XML.
