# Markdown

> A member that holds a markdown document. You read and write it as ordinary text, but it is stored as a list of sections split on its headings, so a person and an agent can edit different parts of the same document without overwriting each other. A markdown member is never required, and one that has never been written reads back null.

<!-- id: types-markdown · area: types · stability: stable · html: https://osysharp.com/reference/types/markdown/ -->

## Summary        {#summary}

`Markdown` is the type for a member that holds a **document** rather than a line of text. You use it exactly like a
string — assign markdown to it, read markdown back — but it is not stored as one blob. The platform splits the text on
its headings and keeps one row per section.

That storage is what buys you the behaviour you actually want from a document:

- **Two writers can work at once.** A person editing one section and an agent rewriting another do not collide,
  because they are writing different rows.
- **An edit costs what the edit is worth.** Ticking a checkbox rewrites one section, not the whole document.
- **Search sees sections, not files.** With `[Searchable]`, each section is indexed on its own, so a search result
  points at the part that answered rather than at a ten-page document.

You do not have to think about any of that to use one. Assign a string, read a string.

## Signature      {#signature}

```osy syntax
entity Article {
  Markdown Body;            // document-backed; reads null until something writes it
  Markdown? Notes;          // `?` is accepted but changes nothing — see below
  [Searchable] Markdown Manual;   // each SECTION is indexed separately
}
```

## Description    {#description}

### Reading and writing   {#read-write}

A markdown member reads and writes as text. Assigning replaces the whole document; reading returns the document
reassembled in order.

```osy title="write it, read it" test app=types-markdown
entity Article {
  [MaxLength(200)] string Title;
  Markdown Body;
}

void PublishDraft(string title) {
  var a = new Article {
    Title = title,
    Body  = "# Overview\n\nWhat this is about.\n\n## Details\n\nThe specifics."
  };
}

string ReadBody(Article a) {
  return a.Body;
}
```

The document written above is stored as **two** sections — `Overview` and `Details` — because those are its two
headings. `ReadBody` returns the text you wrote, reassembled from them.

### A markdown member is never required   {#optional}

Most value-shaped types with no natural zero — `string`, an `enum`, `DateTime`, `Guid` — are **required**: you must
supply a value before a row can be saved (see [Optional and required members](https://osysharp.com/reference/types/optional-and-required/)). **`Markdown` is not one of them.**

A document that has never been written to genuinely is not there, and there is nothing you could "supply" at create
time that would make it there. So a bare `Markdown Body;` is optional, and a row that never touches it saves fine:

```osy title="a row with an untouched document saves normally" test app=types-markdown-optional
entity Page {
  [MaxLength(200)] string Title;
  Markdown Body;
}

void CreateEmptyPage(string title) {
  var p = new Page { Title = title };   // Body is never set — this is fine
}
```

Reading `p.Body` afterwards returns `null`, not an empty string: nothing has been written, and the platform does not
invent a document to hand you. Writing `Markdown? Body;` is accepted and means the same thing — the `?` is redundant
here rather than wrong.

### Assigning null clears it   {#null}

`a.Body = null;` empties the document — it removes its sections. It does not delete the row that owns it.

### It has no column of its own   {#storage}

A markdown member is stored in its own section rows, keyed by the owning row and the member's name, so it adds no
column to its entity's table. Two consequences worth knowing:

- **Do not filter on it in a query.** There is no column to compare against, so `Where(a => a.Body.Contains("x"))` is
  not the way to find text. Use `[Searchable]` and search it — that is what section-level indexing is for.
- **Deleting the row deletes its document.** You do not clean it up yourself.

### Searching it   {#searchable}

`[Searchable]` on a markdown member indexes **each section separately**, which is almost always what you want from a
long document — a hit points at the section that matched.

```osy title="indexing it, one section at a time" syntax
entity Manual {
  [Searchable] Markdown Body;     // sections indexed individually
}
```

See [[Searchable]](https://osysharp.com/reference/memory/searchable/) for how the results are queried.

## Examples       {#examples}

```osy title="a knowledge-base article, written and then appended to" test app=types-markdown-kb
entity Kb {
  [MaxLength(200)] string Title;
  Markdown Body;
}

void Seed(string title) {
  var k = new Kb { Title = title, Body = "# Intro\n\nStart here." };
}

void Rewrite(Kb k, string body) {
  k.Body = body;
}
```

## See also       {#see-also}
- [Optional and required members](https://osysharp.com/reference/types/optional-and-required/) — why most no-natural-zero types are required, and why this one is not
- [[Searchable]](https://osysharp.com/reference/memory/searchable/) — indexing a document so its sections can be searched
- [entity](https://osysharp.com/reference/entity/declaration/) — declaring the entity a markdown member lives on
