Add review notes to Word text.
dotnet add package RecursiveAI.OneDocument.DocumentsRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using RecursiveAI.OneDocument.Documents; using var doc = Document.Create(); var run = doc.Body.AddParagraph("Date?") .Runs().Single(); doc.Comments.Add(run, run, "Jo", "Please confirm."); doc.SaveAs("review.docx");
A run is a span of document content. The snippet comments on the whole “Date?” run with “Please confirm.”
Writes review.docx with a comment; prints nothing.
Unzip the example, then dotnet run.
The full program
The snippet at the top creates review.docx with a comment on “Date?”; it prints nothing. No input file is needed.
The full program creates the text “Confirm delivery date.”, adds Morgan’s comment “Which date?”, then reopens the file to inspect the author, text and all three anchor elements. It removes the comment and saves review-clean.docx.
using RecursiveAI.OneDocument.Documents; using (var doc = Document.Create()) { var paragraph = doc.Body.AddParagraph("Confirm delivery date."); var run = paragraph.Runs().Single(); doc.Comments.Add(run, run, "Morgan", "Which date?"); doc.SaveAs("review.docx"); } using var reopened = Document.Open("review.docx"); var comment = reopened.Comments.Single(); Console.WriteLine($"{comment.Id}: {comment.Author}: {comment.Text}"); var anchor = comment.Anchor; Console.WriteLine($"Anchors: start={anchor.Start is not null}, end={anchor.End is not null}, reference={anchor.Reference is not null}"); reopened.Comments.Remove(comment); reopened.SaveAs("review-clean.docx"); using var clean = Document.Open("review-clean.docx"); Console.WriteLine($"Comments after removal: {clean.Comments.Count()}"); Console.WriteLine($"Body: {clean.Body.Paragraphs().First(p => p.Text.Length > 0).Text}");
0: Morgan: Which date? Anchors: start=True, end=True, reference=True Comments after removal: 0 Body: Confirm delivery date.
Variations
Open the review-clean.docx made by the full program, find the run containing “delivery date”, and attach a comment to that entire run. This matches only a phrase contained in one run; text split across runs needs a range spanning those runs.
using RecursiveAI.OneDocument.Documents; using var doc = Document.Open("review-clean.docx"); var run = doc.Body.Paragraphs() .SelectMany(paragraph => paragraph.Runs()) .First(run => run.Text.Contains("delivery date", StringComparison.Ordinal)); var comment = doc.Comments.Add(run, run, "Jo", "Please confirm Friday."); doc.SaveAs("review-existing.docx"); Console.WriteLine($"{comment.Author}: {comment.Text}");
Jo: Please confirm Friday.Run the full program first to create review.docx, then add a reply:
using RecursiveAI.OneDocument.Documents; using var doc = Document.Open("review.docx"); var parent = doc.Comments.Single(); doc.Comments.Reply(parent, "Riley", "Friday."); doc.SaveAs("review-reply.docx"); Console.WriteLine(parent.Replies.Single().Text);
Friday.Related recipes
Create a Word document · Find and replace in Word
The short answer
Use Document.Comments.Add(first, last, author, text) to attach a comment to a range of Word runs, then save the DOCX. A run is a span of document content. Passing the same run twice comments on that run; passing different first and last runs spans the range between them.
A comment is an annotation, not a replacement for the words it marks. The full program reopens the saved file and reads the comment through Document.Comments. It then removes the comment and shows that “Confirm delivery date.” remains in the document. Use text-editing APIs instead when the requirement is to change those words.
How do I add comments to a Microsoft Word document in C#?
Choose the text runs that bound the range, supply the author and comment text, then call Add. The comments documentation says the method writes the comment, a range start before the first run, a range end after the last run, and a reference run after the end. Those are linked pieces of a Word comment, not independent annotations.
The full program checks the three anchor members after reopening: Start, End and Reference. All are present for this generated comment.
When adding to an existing document, select runs from that document rather than creating detached content for the range. Text can be split across runs, so a visual phrase is not necessarily a single run. The generated paragraph here intentionally has one text run, making the selected range unambiguous.
Do not manually assign an id based on the current collection count. OneDocument allocates one more than the highest id in use, with a minimum of zero for comments. The documented definition of “in use” includes comment parts and references or ranges in the main part, including dangling references. This avoids attaching an old reference to a newly created comment just because its body was missing.
Dates are optional. The library writes a date only when the caller passes one; it does not read the clock. This example omits the date rather than inserting a runtime-dependent timestamp. The API also supports initials when your workflow needs them.
How do I add a reply to a Word comment?
The reply variation opens the file created by the full program, obtains its one top-level comment, and calls Comments.Reply. The saved result is review-reply.docx. A reply is anchored beside the parent’s markers and records its parent link in the extended comments part. A reply to a reply goes to the top-level comment, according to the same documentation.
Use Comments.Remove(comment) when deleting a review thread rather than deleting only its text or one XML element. It removes replies, range markers, reference runs and entries in the modern comment parts; the people entry stays. The full program exercises this path and reopens the cleaned document to count the remaining comments. Removal is cleanup of review annotations, not redaction of the document’s body.