---
title: The front-matter capability system
description: How IFrontMatter distinguishes universal capabilities (as default members) from selective ones (as separate interfaces) — and why presence of a capability interface is a meaningful signal.
canonical_url: https://usepennington.net/explanation/core/front-matter-capabilities/
sidecar_url: https://usepennington.net/explanation/core/front-matter-capabilities.md
content_hash: sha256:1df43f5264bd2127872dc7abd6eb4fb35e2a763e38faebf3fc34368a3696b2b1
tokens: 1532
uid: explanation.core.front-matter-capabilities
reading_time_minutes: 3
---

Under the Hood
# The front-matter capability system

How IFrontMatter distinguishes universal capabilities (as default members) from selective ones (as separate interfaces) — and why presence of a capability interface is a meaningful signal.

 
Where does the line fall between "every page needs this" and "only some pages need this" when those two categories share a base interface?

 
## Context

 
A content engine asks a lot of questions about each page. Is it a draft? Does it belong in search? In the LLM index? Does it carry a cross-reference uid, a description, or a date? These questions apply to every page, even when the answer is trivially "no." A smaller set of questions — does it have tags? does it participate in ordered navigation? does it carry a section label? is it a redirect? — applies only to some content types.

 
Pennington models this split directly on the type system. The universal questions live on `IFrontMatter` itself, with sensible defaults, so every record answers them without having to opt in. The selective questions live on separate capability interfaces, so a content type that does not implement `ITaggable` is not tagged — and the engine can tell at compile time.

 
## How it works

 
### `IFrontMatter`: universal capabilities with defaults

 
`IFrontMatter` has one abstract member (`Title`) and seven default-implemented ones. Every front-matter record inherits `IsDraft => false`, `Search => true`, `Llms => true`, `SearchOnly => false`, `Uid => null`, `Description => null`, and `Date => null` without declaring them.

 
```csharp:symbol
public interface IFrontMatter
{
    /// <summary>Page title rendered in the browser tab, navigation, and OpenGraph tags.</summary>
    string Title { get; }
  
    /// <summary>True when the page is a draft and should be excluded from builds.</summary>
    bool IsDraft => false;
  
    /// <summary>True when the page should be included in the search index.</summary>
    bool Search => true;
  
    /// <summary>True when the page should be included in llms.txt output.</summary>
    bool Llms => true;
  
    /// <summary>
    /// When true, the page is included in indexing channels (search, llms.txt) but excluded
    /// from the rendered navigation tree. Useful for FAQ entries, glossary terms, or other
    /// content that should be discoverable by search but should not clutter the sidebar.
    /// </summary>
    bool SearchOnly => false;
  
    /// <summary>Stable cross-reference identifier used by xref links.</summary>
    string? Uid => null;
  
    /// <summary>Short summary used in meta descriptions, OpenGraph tags, and listings.</summary>
    string? Description => null;
  
    /// <summary>
    /// Publication date surfaced in feeds and sitemaps. Also drives scheduled publishing:
    /// when this is set to a moment after the build clock, the page is excluded from build
    /// output (same dev-vs-build behavior as <see cref="IsDraft"/>) until the clock catches up.
    /// </summary>
    DateTime? Date => null;
}
```

 
The contract gives every record common defaults it can override. A minimal record exposes a single required `Title` property and the engine handles drafts, search indexing, LLM indexing, cross-references, descriptions, and dates gracefully. Engine code uses the members directly — `if (page.IsDraft)` works on every `IFrontMatter` without checking for each interface first.

 
### The capability interfaces

 
Tags, order, section labels, redirects, and Standard Site document keys live on separate interfaces because the interface's *presence* is itself a signal. Seeing `IOrderable` on a record says the content type consciously participates in ordered navigation; folding it into `IFrontMatter` would erase that distinction, since every record would then carry the member whether it meant anything or not. The selectivity is real, too: a blog post has tags but no meaningful order among siblings; a doc page has an order but no redirect target; a redirect stub carries a destination URL and little else. Folding these into `IFrontMatter` would force every record to carry empty tag arrays and meaningless sort keys.

 
```csharp:symbol
public interface IOrderable
{
    /// <summary>Sort order for this page within its section (lower sorts first).</summary>
    int Order { get; }
}
```

 
`NavigationBuilder` keys off the `IOrderable` interface itself, not a sentinel value in the `Order` property. A content type either implements the interface and participates in ordered navigation, or it does not; there is no "this page has no meaningful order" case to handle. The same applies to `ITaggable` (tag cloud participation), `ISectionable` (section-label breadcrumbs), `IRedirectable` (redirect-stub semantics), and `IStandardSiteDocument` (the AT Protocol record key for Standard Site syndication).

 
The rule of thumb is simple: if adoption is universal, the member lives on `IFrontMatter` with a sensible default. If adoption is selective, it lives on a capability interface so that pattern-matching on the interface remains meaningful.

 
### Custom front-matter records

 
A custom record buys typed access to extra keys (an `apiVersion` or `gitHubUrl` field becomes a strongly-typed property) plus the same set of capability interfaces to opt into. The defaults give what the shipped records would give; the custom record only declares what it adds. See [Define custom front-matter keys](https://usepennington.net/how-to/pages/front-matter.md) for the recipe.

 
## Further reading

 
 - Reference: [IFrontMatter and capability defaults](https://usepennington.net/reference/api/i-front-matter.md)
 - Reference: [Front matter key reference](https://usepennington.net/reference/front-matter/keys.md)
 - How-to: [Work with front matter](https://usepennington.net/how-to/pages/front-matter.md)
 
 
[Previous
                
                Dev mode and build mode share one code path](https://usepennington.net/explanation/core/dev-vs-build.md)[Next
                    
                The response-processing pipeline](https://usepennington.net/explanation/core/response-processing.md)