Table of Contents

Forms and content controls

Word has two kinds of form control: modern content controls (Developer tab › Controls) and legacy form fields. DocWright reads and fills both. You don't need to render anything: load, fill and save.

See what a form contains

using DocWright;
using DocWright.Dom;
using DocWright.Dom.ContentControls;
using DocWright.Dom.Forms;

using FileStream input = File.OpenRead("forms.docx");
using WordDocument document = converter.Load(input);

// Content controls (Developer tab › Controls): body, headers, footers and notes.
foreach (ContentControl control in document.FindContentControls())
{
    Console.WriteLine($"control  {control.Kind,-14} tag={control.Tag ?? "-",-10} value={control.GetValue()}");
}

// Legacy form fields (the Word 97 ones).
foreach (FormField field in document.FindFormFields())
{
    Console.WriteLine($"field    {field.Kind,-14} name={field.Name ?? "-",-10} value={field.GetValue()}");
}

Output (this step, and the refusal further down)

control  PlainText      tag=name       value=Ada Lovelace
control  RichText       tag=summary    value=Rich text keeps its own run formatting.
control  CheckBox       tag=agreeYes   value=true
control  CheckBox       tag=agreeNo    value=false
control  DropDownList   tag=dept       value=ENG
control  ComboBox       tag=office     value=WLG
control  DatePicker     tag=signed     value=6 August 2026
control  BuildingBlockGallery tag=block      value=Quick part placeholder.
control  Group          tag=group      value=Grouped content is locked as a unit.
control  RepeatingSection tag=lines      value=First line item.
Second line item.
control  RepeatingSectionItem tag=-          value=First line item.
control  RepeatingSectionItem tag=-          value=Second line item.
control  PlainText      tag=signer     value=Grace Hopper
field    Text           name=FullName   value=Katherine Johnson
field    CheckBox       name=Agree      value=true
field    DropDown       name=Dept       value=Engineering
ContentControlEditException: 'Not an option' is not one of the drop-down list's entries, and a drop-down list accepts nothing else. Add the entry first, or use a combo box.

Fill it

using DocWright.Dom.ContentControls;
using DocWright.Dom.Forms;

foreach (ContentControl control in document.FindContentControls())
{
    switch (control.Kind)
    {
        case ContentControlKind.PlainText:
        case ContentControlKind.RichText:
            control.SetValue("Filled by DocWright");
            break;
        case ContentControlKind.CheckBox:
            control.SetValue("true");                    // also repaints the check glyph
            break;
        case ContentControlKind.DatePicker:
            control.SetDate(new DateTimeOffset(2026, 10, 1, 0, 0, 0, TimeSpan.Zero));
            break;
        case ContentControlKind.DropDownList:
        case ContentControlKind.ComboBox:
            control.SetValue(control.ListItems[^1].DisplayText);   // pick the last entry
            break;
    }
}

foreach (FormField field in document.FindFormFields())
{
    if (field.Kind == FormFieldKind.CheckBox)
    {
        field.IsChecked = true;
    }
    else if (field.Kind == FormFieldKind.Text)
    {
        field.SetValue("Katherine Johnson");               // truncated to the field's max length
    }
}
forms.docx: before and after filling
The form before fillingBefore
The form after filling: text replaced, both check boxes ticked, a different drop-down entry and dateAfter
Content control kind Set it with
PlainText, RichText SetValue(text)
CheckBox SetValue("true"), or IsChecked = true. The check glyph is repainted too.
DropDownList SetValue(value or display text). It must be one of the entries.
ComboBox SetValue(anything). Free text is allowed.
DatePicker SetDate(DateTimeOffset, culture?). The control's date format is applied.
RepeatingSection AddRepeatingItem(), MoveRepeatingItem(item, index), RemoveRepeatingItem(item)

Find controls with FindContentControls(), FindContentControls(kind) or FindContentControlsByTag(tag). Tags are the reliable handle: set them in Word's control properties.

Invalid values are refused

A drop-down only accepts its own entries, as in Word. An edit DocWright can't make with certainty throws and changes nothing:

using DocWright.Dom.ContentControls;
using DocWright.Core;

// A drop-down only accepts one of its own entries, as in Word.
ContentControl? dropDown = document.FindContentControls(ContentControlKind.DropDownList).FirstOrDefault();
try
{
    dropDown?.SetValue("Not an option");
}
catch (DocWrightException ex)
{
    Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}

Legacy form fields

FindFormFields() and FindFormField(name) return FormField objects. Set IsChecked on check boxes, SelectedIndex on drop-downs, and SetValue(text) on text fields. Text is cut to the field's maximum length. A check box shows CheckedState ?? IsCheckedByDefault: its stated state if there is one, otherwise its default.

Flatten before sending

To send a filled form to someone who shouldn't edit it, replace each field with what it shows:

using DocWright.Dom.Forms;

// For a recipient with no interactive fields: keep what is shown, drop the field.
foreach (FormField field in document.FindFormFields().ToList())
{
    field.Flatten();
}

Reading changes nothing

Reading a control's properties never modifies the document. A control you only inspect saves byte-for-byte, and properties DocWright doesn't model are preserved even when you edit others.

Protection is not security

document.Settings.Protection tells you what the author restricted, for example forms-only editing, and it is preserved on save, including the password hash. DocWright does not enforce it. Every editing API works on a protected document. Treat protection as the author's intent, not as a security control.

Custom XML data binding

A content control can be bound to a node in a custom XML part stored inside the document. Word treats the XML as the source of truth. CustomXmlDataStore.Open(document) gives you the parts:

Call Does
store.RefreshBoundControls() Copies the XML values into the bound controls.
store.SetBoundValue(control, value) Updates both the control and its XML node.
store.ReadValue(binding), store.WriteValue(binding, value) Reads or writes the XML only.
store.Flush() Writes changed parts back to the document before you save.

Bindings use the XPath Word generates: absolute element paths with optional positions and an optional final attribute. Anything else throws ContentControlBindingException, naming the unsupported construct, instead of resolving the wrong node.