Skip to main content

Element Hierarchy Rules

Valid Document Structure​

OOXML has strict rules about which elements can contain which other elements. Understanding these rules prevents document corruption and makes debugging easier.

The Complete Hierarchy​


Rule Categories​

1. Block-Level Elements​

Can appear directly in: w:body, table cells (w:tc), SDT content, headers/footers

Elements:

  • w:p - Paragraph
  • w:tbl - Table
  • w:sdt - Content control (block variant)
<!-- ✅ VALID: Block elements in body -->
<w:body>
<w:p>...</w:p>
<w:tbl>...</w:tbl>
<w:sdt>...</w:sdt>
<w:p>...</w:p>
</w:body>

❌ Cannot be in: Runs, inline elements


2. Inline Elements​

Must appear inside: Paragraphs or inline containers

Elements:

  • w:r - Run
  • w:hyperlink - Hyperlink
  • w:sdt - Content control (inline variant)
<!-- ✅ VALID: Inline elements in paragraph -->
<w:p>
<w:r>...</w:r>
<w:hyperlink>...</w:hyperlink>
<w:sdt>...</w:sdt>
</w:p>

❌ Cannot be in: Body (must be wrapped in paragraph)


3. Content Elements​

Must appear inside: Runs

Elements:

  • w:t - Text
  • w:br - Line break
  • w:tab - Tab
  • w:drawing - Image
  • w:sym - Symbol
<!-- ✅ VALID: Content in run -->
<w:r>
<w:t>Text</w:t>
<w:br/>
<w:tab/>
</w:r>

❌ Cannot be in: Paragraphs directly, body, table cells directly


Detailed Rules by Element​

Document Root​

<w:document>  <!-- Root element -->
<w:body> <!-- Exactly one body -->
<!-- Block elements -->
</w:body>
</w:document>

Rules:

  • Must have exactly one w:body child
  • May have w:background (optional)
  • Namespace declarations required

Body (w:body)​

Valid children (in order):

  1. Any number of block elements (w:p, w:tbl, w:sdt)
  2. Exactly one w:sectPr (section properties) at the end
<w:body>
<w:p>...</w:p> <!-- Paragraphs -->
<w:tbl>...</w:tbl> <!-- Tables -->
<w:sdt>...</w:sdt> <!-- Block SDTs -->
<w:p>...</w:p> <!-- More content -->

<w:sectPr> <!-- Section properties (last) -->
<w:pgSz w:w="12240" w:h="15840"/> <!-- Page size -->
<w:pgMar w:top="1440" w:bottom="1440"/> <!-- Margins -->
</w:sectPr>
</w:body>

Rules:

  • Can contain unlimited block elements
  • w:sectPr must be last (if present)
  • Cannot directly contain runs or text

Paragraph (w:p)​

Valid children (in order):

  1. w:pPr (paragraph properties) - optional, must be first
  2. Any number of inline elements
<w:p>
<w:pPr> <!-- Properties first -->
<w:pStyle w:val="Normal"/>
<w:jc w:val="center"/>
</w:pPr>

<w:r>...</w:r> <!-- Runs -->
<w:hyperlink>...</w:hyperlink> <!-- Hyperlinks -->
<w:sdt>...</w:sdt> <!-- Inline SDTs -->
<w:bookmarkStart/> <!-- Bookmarks -->
<w:bookmarkEnd/>
</w:p>

Rules:

  • w:pPr must be first child (if present)
  • Can be empty: <w:p/>
  • Cannot contain tables or block SDTs
  • Cannot contain text directly (must use runs)

Common mistake:

<!-- ❌ WRONG: Text without run -->
<w:p>
<w:t>Text</w:t>
</w:p>

<!-- ✅ CORRECT -->
<w:p>
<w:r>
<w:t>Text</w:t>
</w:r>
</w:p>

Run (w:r)​

Valid children (in order):

  1. w:rPr (run properties) - optional, must be first
  2. Any number of content elements
<w:r>
<w:rPr> <!-- Properties first -->
<w:b/>
<w:sz w:val="24"/>
</w:rPr>

<w:t>Text</w:t> <!-- Text -->
<w:br/> <!-- Line break -->
<w:tab/> <!-- Tab -->
<w:drawing>...</w:drawing> <!-- Image -->
<w:sym w:font="Wingdings" w:char="F020"/> <!-- Symbol -->
</w:r>

Rules:

  • w:rPr must be first child (if present)
  • Cannot contain paragraphs, tables, or block elements
  • Cannot contain other runs
  • Cannot be empty (should have at least one content element)

Common mistakes:

<!-- ❌ WRONG: Paragraph in run -->
<w:r>
<w:p>...</w:p>
</w:r>

<!-- ❌ WRONG: Table in run -->
<w:r>
<w:tbl>...</w:tbl>
</w:r>

<!-- ❌ WRONG: SDT in run (block-level SDT) -->
<w:r>
<w:sdt>
<w:sdtContent><w:p>...</w:p></w:sdtContent>
</w:sdt>
</w:r>

Table (w:tbl)​

Valid children (in order):

  1. w:tblPr (table properties) - optional, must be first
  2. w:tblGrid (grid definition) - required
  3. One or more w:tr (table rows)
<w:tbl>
<w:tblPr> <!-- Properties first -->
<w:tblW w:w="5000" w:type="pct"/>
<w:tblBorders>...</w:tblBorders>
</w:tblPr>

<w:tblGrid> <!-- Grid definition -->
<w:gridCol w:w="2500"/>
<w:gridCol w:w="2500"/>
</w:tblGrid>

<w:tr>...</w:tr> <!-- Rows -->
<w:tr>...</w:tr>
</w:tbl>

Rules:

  • Must have at least one row (w:tr)
  • w:tblGrid is required
  • Cannot be directly in runs or inline elements
  • Can be nested inside table cells

Valid locations:

<!-- ✅ In body -->
<w:body>
<w:tbl>...</w:tbl>
</w:body>

<!-- ✅ In table cell (nested table) -->
<w:tc>
<w:p>...</w:p>
<w:tbl>...</w:tbl>
</w:tc>

<!-- ✅ In SDT content -->
<w:sdt>
<w:sdtContent>
<w:tbl>...</w:tbl>
</w:sdtContent>
</w:sdt>

<!-- ❌ WRONG: In run -->
<w:r>
<w:tbl>...</w:tbl>
</w:r>

Table Row (w:tr)​

Valid children (in order):

  1. w:trPr (row properties) - optional, must be first
  2. One or more w:tc (table cells)
<w:tr>
<w:trPr> <!-- Properties first -->
<w:trHeight w:val="500" w:hRule="atLeast"/>
<w:cantSplit/>
</w:trPr>

<w:tc>...</w:tc> <!-- Cells -->
<w:tc>...</w:tc>
</w:tr>

Rules:

  • Must have at least one cell
  • Cannot exist outside of table
  • All cells in a row should align with table grid

Table Cell (w:tc)​

Valid children (in order):

  1. w:tcPr (cell properties) - optional, must be first
  2. At least one w:p (paragraph) - REQUIRED
  3. Optional additional paragraphs or nested tables
<w:tc>
<w:tcPr> <!-- Properties first -->
<w:tcW w:w="2500" w:type="dxa"/>
<w:vMerge w:val="restart"/>
</w:tcPr>

<w:p> <!-- At least one paragraph REQUIRED -->
<w:r><w:t>Cell content</w:t></w:r>
</w:p>

<w:p>...</w:p> <!-- More paragraphs OK -->
<w:tbl>...</w:tbl> <!-- Nested table OK -->
</w:tc>

Critical rule: ⚠️ Every table cell MUST contain at least one paragraph!

<!-- ❌ WRONG: Cell without paragraph -->
<w:tc>
<w:tcPr>...</w:tcPr>
<w:r><w:t>Text</w:t></w:r> <!-- No paragraph wrapper! -->
</w:tc>

<!-- ✅ CORRECT: Cell with paragraph -->
<w:tc>
<w:tcPr>...</w:tcPr>
<w:p>
<w:r><w:t>Text</w:t></w:r>
</w:p>
</w:tc>

<!-- ✅ CORRECT: Empty cell (still has paragraph) -->
<w:tc>
<w:tcPr>...</w:tcPr>
<w:p/> <!-- Empty paragraph -->
</w:tc>

This is one of the most common mistakes in document generation!


Content Control (w:sdt)​

Content controls can be block-level or inline, with different rules for each.

Block-Level SDT​

Valid children:

  1. w:sdtPr (properties) - required, must be first
  2. w:sdtEndPr (end properties) - optional
  3. w:sdtContent - required, must be last

Can contain: Block elements (paragraphs, tables)

<!-- ✅ Block SDT in body -->
<w:body>
<w:sdt>
<w:sdtPr>
<w:id w:val="123"/>
<w:tag w:val="fieldName"/>
</w:sdtPr>

<w:sdtContent>
<w:p>...</w:p> <!-- Paragraphs -->
<w:tbl>...</w:tbl> <!-- Tables -->
</w:sdtContent>
</w:sdt>
</w:body>

Inline SDT​

Valid children:

  1. w:sdtPr (properties) - required, must be first
  2. w:sdtEndPr (end properties) - optional
  3. w:sdtContent - required, must be last

Can contain: Inline elements (runs)

<!-- ✅ Inline SDT in paragraph -->
<w:p>
<w:sdt>
<w:sdtPr>
<w:id w:val="456"/>
<w:tag w:val="inlineField"/>
</w:sdtPr>

<w:sdtContent>
<w:r>...</w:r> <!-- Runs only -->
</w:sdtContent>
</w:sdt>
</w:p>

Common mistake:

<!-- ❌ WRONG: SDT inside run -->
<w:p>
<w:r>
<w:sdt>...</w:sdt>
</w:r>
</w:p>

<!-- ✅ CORRECT: SDT inside paragraph, contains runs -->
<w:p>
<w:sdt>
<w:sdtContent>
<w:r>...</w:r>
</w:sdtContent>
</w:sdt>
</w:p>

🔗 See Also: Content Controls (SDT)


Properties Order Rules​

Paragraph Properties Order​

When multiple properties exist, they must follow this order:

<w:pPr>
<!-- Style reference first -->
<w:pStyle w:val="Normal"/>

<!-- Keep options -->
<w:keepNext/>
<w:keepLines/>

<!-- Numbering -->
<w:numPr>...</w:numPr>

<!-- Spacing -->
<w:spacing w:before="120" w:after="120"/>

<!-- Indentation -->
<w:ind w:left="720"/>

<!-- Alignment -->
<w:jc w:val="center"/>

<!-- Borders and shading -->
<w:pBdr>...</w:pBdr>
<w:shd>...</w:shd>
</w:pPr>

Rule: Order matters! Wrong order can cause validation errors.

Run Properties Order​

<w:rPr>
<!-- Style reference first -->
<w:rStyle w:val="Emphasis"/>

<!-- Font -->
<w:rFonts w:ascii="Times New Roman"/>

<!-- Size -->
<w:sz w:val="24"/>
<w:szCs w:val="24"/>

<!-- Color -->
<w:color w:val="FF0000"/>

<!-- Formatting flags -->
<w:b/>
<w:i/>
<w:u w:val="single"/>

<!-- Highlight -->
<w:highlight w:val="yellow"/>
</w:rPr>

Complete Valid Structures​

Simple Document​

<w:document xmlns:w="...">
<w:body>
<w:p>
<w:r>
<w:t>Hello World</w:t>
</w:r>
</w:p>
</w:body>
</w:document>

Hierarchy: document → body → p → r → t

Document with Formatted Text​

<w:body>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
</w:pPr>

<w:r>
<w:rPr>
<w:b/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>Bold 14pt Text</w:t>
</w:r>
</w:p>
</w:body>

Document with Table​

<w:body>
<w:tbl>
<w:tblPr>
<w:tblW w:w="5000" w:type="pct"/>
</w:tblPr>

<w:tblGrid>
<w:gridCol w:w="2500"/>
<w:gridCol w:w="2500"/>
</w:tblGrid>

<w:tr>
<w:tc>
<w:p>
<w:r><w:t>Cell 1</w:t></w:r>
</w:p>
</w:tc>

<w:tc>
<w:p>
<w:r><w:t>Cell 2</w:t></w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>
</w:body>

Document with Image​

<w:body>
<w:p>
<w:r>
<w:drawing>
<wp:inline xmlns:wp="...">
<wp:extent cx="914400" cy="914400"/>
<wp:docPr id="1" name="Image"/>

<a:graphic xmlns:a="...">
<a:graphicData uri="...">
<pic:pic xmlns:pic="...">
<pic:blipFill>
<a:blip r:embed="rId5"/>
</pic:blipFill>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
</w:body>

Hierarchy: body → p → r → drawing → inline → graphic → pic


Quick Reference Matrix​

Can X contain Y?​

Parent ↓ / Child →ptblsdtrtdrawing
body✅✅✅❌❌❌
p❌❌✅*✅❌❌
r❌❌❌❌✅✅
tc✅✅✅❌❌❌
sdt(block)✅✅✅❌❌❌
sdt(inline)❌❌❌✅❌❌

*Only inline SDT in paragraph


Validation Rules Summary​

✅ Always Valid​

  1. body → p → r → t
  2. body → tbl → tr → tc → p
  3. body → sdt → p → r → t
  4. tc → tbl (nested table)
  5. p → hyperlink → r → t

❌ Never Valid​

  1. r → p (run cannot contain paragraph)
  2. r → tbl (run cannot contain table)
  3. r → sdt(block) (run cannot contain block SDT)
  4. p → tbl (paragraph cannot contain table)
  5. p → t (paragraph cannot directly contain text)
  6. tc without p (cell must have paragraph)

AppraiSys Patterns​

Creating Valid Structure​

// ✅ Correct hierarchy
var body = mainPart.Document.Body;

// Add paragraph
var paragraph = new Paragraph();

// Add run to paragraph
var run = new Run();
paragraph.Append(run);

// Add text to run
var text = new Text("Content");
run.Append(text);

// Add paragraph to body
body.Append(paragraph);

// Final structure: body → p → r → t ✅

Table Cell with Required Paragraph​

// ✅ Correct: Cell always has paragraph
var cell = new TableCell();

// Properties
cell.Append(new TableCellProperties(
new TableCellWidth { Width = "2500", Type = TableWidthUnitValues.Dxa }
));

// Required paragraph
cell.Append(new Paragraph(
new Run(
new Text("Cell content")
)
));

row.Append(cell);

Common Validation Errors​

Error: "Unexpected element"​

Element 'http://...wordprocessingml/2006/main:tbl' 
cannot be a child of element 'http://...wordprocessingml/2006/main:r'

Cause: Table inside run (violates hierarchy rules)

Fix: Move table to body level

Error: "Required element missing"​

Element 'http://...wordprocessingml/2006/main:tc' 
is incomplete. Expected element 'http://...wordprocessingml/2006/main:p'

Cause: Table cell without paragraph

Fix: Add at least one paragraph to cell

Error: "Element order incorrect"​

Element 'http://...wordprocessingml/2006/main:r' 
cannot appear after element 'http://...wordprocessingml/2006/main:pPr'

Cause: Properties must come before content

Fix: Reorder elements (properties first)


Next Steps​

You now understand valid element hierarchies. Complete your fundamentals knowledge with practice:

➡️ Next: Practice Exercises - Hands-on learning

Review:

Advanced:


Test Your Understanding​

Question 1​

Is this structure valid?

<w:body>
<w:tbl>
<w:tr>
<w:tc>
<w:r><w:t>Text</w:t></w:r>
</w:tc>
</w:tr>
</w:tbl>
</w:body>
Answer

No! The table cell is missing a required paragraph:

<w:tc>
<w:p> <!-- Required paragraph -->
<w:r><w:t>Text</w:t></w:r>
</w:p>
</w:tc>

Every table cell must contain at least one paragraph.

Question 2​

Can a paragraph contain a table?

Answer

No! Tables are block-level elements. They cannot be inside paragraphs.

Tables must be at the body level (or inside table cells, SDT content, etc.):

<!-- ❌ WRONG -->
<w:p>
<w:tbl>...</w:tbl>
</w:p>

<!-- ✅ CORRECT -->
<w:body>
<w:p>...</w:p>
<w:tbl>...</w:tbl>
</w:body>

Question 3​

What's the minimum valid content for a table cell?

Answer

An empty paragraph:

<w:tc>
<w:p/>
</w:tc>

Or with properties:

<w:tc>
<w:tcPr>
<w:tcW w:w="2500" w:type="dxa"/>
</w:tcPr>
<w:p/>
</w:tc>

The cell MUST have at least one paragraph, even if empty.


💡 Remember: OOXML hierarchy rules are strict. Follow them, and your documents will be valid every time!