# Track Changes

> Process tracked changes with ServerTextControl: list changes with author and time, accept or reject them by author or number and finalize DOCX and PDF files.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Document Processing > Track Changes
- **4070 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/track-changes/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/track-changes/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/track-changes/llms-full.txt

---

Contracts come back from MS Word and from reviewers in the Document Editor with every edit marked: inserted and deleted text, each with its author and time. On the server, your code lists these tracked changes, accepts the changes of trusted authors, applies the decisions reviewers made in a web page and produces a clean final document without any markup.

Recording changes while users type is described in [Track Changes](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/track-changes/llms-full.txt) in the Document Editor section, and reviewing them in the browser in [Tracked Changes](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-viewer/tracked-changes/llms-full.txt) in the Document Viewer section. The examples below are console programs that load *Documents/contract.docx*.

### How Tracked Changes Work

A [TrackedChange](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/llms-full.txt) is a piece of text that was inserted or deleted while tracking was turned on. It stays marked until it's accepted, which keeps the change, or rejected, which undoes it:

| Property | Contains |
|---|---|
| [ChangeKind](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/changekind-property/llms-full.txt) | `InsertedText` or `DeletedText` |
| [UserName](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/username-property/llms-full.txt) | The author; `null` or an empty string for an unknown author |
| [ChangeTime](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/changetime-property/llms-full.txt) | The time of the change in UTC |
| [Text](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/text-property/llms-full.txt) | The inserted or deleted text |
| [Start](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/start-property/llms-full.txt), [Length](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/length-property/llms-full.txt) | The one-based position of the first character and the number of characters |
| [Number](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/number-property/llms-full.txt) | The position of the change in the text, starting with 1 |

[ServerTextControl.TrackedChanges](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/trackedchanges-property/llms-full.txt) returns the [TrackedChangeCollection](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchangecollection/llms-full.txt) of the main text. Headers, footers and text frames have a collection of their own, [IFormattedText.TrackedChanges](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/iformattedtext/trackedchanges-property/llms-full.txt). Keep these points in mind:

- **Accept or reject.** [TrackedChangeCollection.Remove](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchangecollection/remove-method/llms-full.txt) accepts a change with `true` and rejects it with `false`, and it's the only way to remove a tracked change. Rejecting removes inserted text and restores deleted text.
- **Formats.** Tracked changes are saved and loaded with DOCX, RTF and the TX Text Control format, and with DOC on the Classic engine. They're compatible with the tracked changes of MS Word.
- **Comments.** A comment attached to a tracked change is removed when the change is accepted or rejected.

### List the Tracked Changes of a Document

The following program prints every tracked change of the main text, the text frames and the headers and footers:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/contract.docx", StreamType.WordprocessingML);

    // The main text, text frames, headers and footers each have their own changes
    foreach (IFormattedText textPart in tx.TextParts)
    {
        foreach (TrackedChange change in textPart.TrackedChanges)
        {
            string author = string.IsNullOrEmpty(change.UserName) ? "Unknown author" : change.UserName;

            Console.WriteLine($"{textPart.GetType().Name} {change.Number}: {change.ChangeKind} by {author}, " +
                $"{change.ChangeTime:yyyy-MM-dd HH:mm} UTC, characters {change.Start} to {change.Start + change.Length - 1}: \"{change.Text}\"");
        }
    }
}
```

[ServerTextControl.TextParts](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/textparts-property/llms-full.txt) contains the main text first, then the text frames and then the headers and footers.

### Accept and Reject Changes by Author

A typical rule accepts the changes of trusted reviewers and rejects all others. The following program accepts the changes of one author, rejects the rest and saves the reviewed document:

```csharp
using TXTextControl;

string trustedAuthor = "Alice Johnson";

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/contract.docx", StreamType.WordprocessingML);

    // Copy the changes to a list first, because Remove changes the collection
    List<TrackedChange> changes = tx.TrackedChanges.Cast<TrackedChange>().ToList();

    int accepted = 0;
    int rejected = 0;

    foreach (TrackedChange change in changes)
    {
        bool accept = change.UserName == trustedAuthor;

        if (tx.TrackedChanges.Remove(change, accept))
        {
            if (accept) accepted++; else rejected++;
        }
    }

    Console.WriteLine($"{accepted} accepted, {rejected} rejected");
    tx.Save("Documents/contract-reviewed.docx", StreamType.WordprocessingML);
}
```

The same list can be filtered by any property, for example `change.ChangeKind == ChangeKind.DeletedText` for deletions only or `change.ChangeTime` for changes made before a date. The blog article [Inspect and Process Track Changes in DOCX Documents with TX Text Control with .NET C#](https://www.textcontrol.com/blog/2026/03/10/inspect-and-process-track-changes-in-docx-documents-with-tx-text-control-with-dotnet-csharp/) filters by author, kind and time with LINQ.

### Apply the Decisions of a Review

In a review application, users decide in a web page which changes to accept, and the page sends the numbers of these changes to the server. The following program accepts the changes with the numbers it receives:

```csharp
using TXTextControl;

// The numbers of the changes a reviewer accepted
int[] acceptedNumbers = { 2, 5 };

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/contract.docx", StreamType.WordprocessingML);

    // The highest number first, so the lower numbers still refer to the same changes
    foreach (int number in acceptedNumbers.Distinct().OrderByDescending(n => n))
    {
        if (number >= 1 && number <= tx.TrackedChanges.Count)
        {
            tx.TrackedChanges.Remove(tx.TrackedChanges[number], true);
        }
    }

    tx.Save("Documents/contract.docx", StreamType.WordprocessingML);
}
```

The indexer [Item](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchangecollection/item-property/llms-full.txt) takes the number of a change from 1 to [Count](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchangecollection/count-property/llms-full.txt). Removing a change doesn't change the numbers of the changes in front of it, which is why the program starts with the highest number. The blog article [Building a Modern Track Changes Review Workflow in ASP.NET Core C#](https://www.textcontrol.com/blog/2026/04/28/building-a-modern-track-changes-review-workflow-in-aspnet-core-csharp/) builds the complete review application, including page previews.

### Accept All Changes Before Publishing

To archive a document or send it as PDF without markup, accept all its changes. The following program accepts the changes of all text parts and saves the result as DOCX and PDF:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/contract.docx", StreamType.WordprocessingML);

    foreach (IFormattedText textPart in tx.TextParts)
    {
        // The number 1 is always the first remaining change
        while (textPart.TrackedChanges.Count > 0)
        {
            if (!textPart.TrackedChanges.Remove(textPart.TrackedChanges[1], true))
            {
                break; // the change couldn't be removed
            }
        }
    }

    tx.Save("Documents/contract-final.docx", StreamType.WordprocessingML);
    tx.Save("Documents/contract-final.pdf", StreamType.AdobePDF);
}
```

`SaveSettings.OmittedContent` also has a value `TrackedChanges`. It leaves out only the markup of the changes when a document is saved, while the text and formatting that belong to the changes are saved, and it can't be used for PDF. To decide what happens to inserted and deleted text, accept or reject the changes with `Remove` as shown above.

### Save the Changed Text and React to Changes

- **The text of a change with its formatting.** [TrackedChange.Save](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/save-method/llms-full.txt) writes the changed text to a string in a `StringStreamType` format, such as HTML, or to a byte array in a `BinaryStreamType` format. [Select](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/trackedchange/select-method/llms-full.txt) selects the changed part of the document.
- **Events.** [TrackedChangeCreated](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/trackedchangecreated-event/llms-full.txt) occurs for each tracked change of a document that is loaded, and [TrackedChangeDeleted](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/trackedchangedeleted-event/llms-full.txt) when a change is accepted or rejected. [React to Removed Elements](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/events/#react-to-removed-elements) logs every accepted change with it.

### Find the Comments of a Change

A reviewer can explain a change in a comment on the same text. No property connects a comment to a tracked change, but their positions do: a comment belongs to a change when the ranges of their characters overlap. The following program lists each change with the comments on it:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/contract.docx", StreamType.WordprocessingML);

    foreach (TrackedChange change in tx.TrackedChanges)
    {
        int changeEnd = change.Start + change.Length - 1;

        Console.WriteLine($"{change.Number} {change.ChangeKind} by {change.UserName}: \"{change.Text}\"");

        foreach (CommentedText comment in tx.Comments)
        {
            int commentEnd = comment.Start + comment.Length - 1;

            // Comments that aren't replies and whose range overlaps the change
            if (comment.RepliedComment == null && comment.Start <= changeEnd && commentEnd >= change.Start)
            {
                Console.WriteLine($"    {comment.UserName}: {comment.Comment}");
            }
        }
    }
}
```

[Comments](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/comments/llms-full.txt) describes comments and their replies. The sample of the blog article [Explaining Contract Tracked Changes Automatically Using .NET C# and AI](https://www.textcontrol.com/blog/2026/01/09/explaining-contract-tracked-changes-automatically-using-dotnet-csharp-and-ai/) pairs changes and comments this way.

### Record Changes on the Server

`ServerTextControl` reads, accepts and rejects tracked changes, but it has no setting that records new ones. The class [ServerVisualization.TextViewGenerator](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-servervisualization/textviewgenerator/llms-full.txt), which is derived from `ServerTextControl`, has one: with [IsTrackChangesEnabled](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-servervisualization/textviewgenerator/istrackchangesenabled-property/llms-full.txt) set to `true`, changes made to the document are marked up, and the first name of [UserNames](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-servervisualization/textviewgenerator/usernames-property/llms-full.txt) becomes their author. The blog article [Transforming Legal Review with AI and TX Text Control: A Smarter, Faster Approach to Legal Document Processing in .NET C#](https://www.textcontrol.com/blog/2024/11/01/transforming-legal-review-with-ai-and-tx-text-control-a-smarter-faster-approach-to-legal-document-processing/) uses it to insert text suggestions as tracked changes that users then accept or reject in the Document Editor.

### Next Steps

- [Comments](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/comments/llms-full.txt): read, add and remove comments.
- [Document Comparison](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/comparison/llms-full.txt): find the differences between two versions of a document.
- [Track Changes](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/track-changes/llms-full.txt) in the Document Editor: let users record and review changes.
- [Tracked Changes](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-viewer/tracked-changes/llms-full.txt) in the Document Viewer: let users accept and reject changes in the browser.

---

## Nearby

- **Previous:** [Comments](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/comments/llms-full.txt)
- **Next:** [Document Protection](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/protection/llms-full.txt)
