Content types and rendering

Anchorify renders shares with one of ten content types:

Type What it does
markdown GitHub-flavored markdown via marked.
code Syntax-highlighted source via highlight.js.
json Pretty-printed JSON in a code-style frame.
yaml YAML in a code-style frame.
csv Comma-separated values as a Notion-style HTML table.
tsv Tab-separated values as a Notion-style HTML table.
html Raw HTML, sanitized via DOMPurify before rendering.
image Inline <img> from R2 storage (24h signed URL).
pdf Inline <embed> from R2 storage (24h signed URL).
binary "Can't preview" page with a Download button.

The default for text uploads is markdown. Binary uploads classify themselves at upload time based on MIME hint + filename extension — the V3 web upload page detects binary content via a NUL-byte heuristic and routes the bytes to R2 storage instead of inlining them in the share row.

How the type is picked

Precedence, highest to lowest:

  1. An explicit type override on the request (form field, CLI flag, or API field).
  2. The filename extension.
  3. Default to markdown.

The extension is the part after the final . in the filename, lowercased.

Extension map

Extension Type
.md, .markdown markdown
.json json
.yaml, .yml yaml
.csv csv
.tsv tsv
.html, .htm html
.js, .ts, .tsx, .jsx, .py, .rb, .go, .rs, .java, code
.c, .cc, .cpp, .cxx, .h, .hpp, .cs, .swift, .kt,
.kts, .scala, .php, .pl, .lua, .r, .sh, .bash,
.zsh, .fish, .ps1, .sql, .dockerfile, .toml, .xml,
.svg

Anything not in the map falls through to markdown.

Binary classification

When the web upload page detects binary content (a NUL byte in the first 1KB), it picks the content type from the MIME hint + filename:

Hint Type
image/* MIME, or .png/.jpg/.gif/.webp/.avif/.svg image
application/pdf MIME, or .pdf pdf
Anything else binary binary

The share's content column holds the sentinel "@@blob"; the actual bytes live in R2 under shares/<share_id>/<filename> and are fetched via a 24-hour signed URL on each render. CLI anchorify upload-folder rejects binaries today — use the web upload page for images, PDFs, and other binaries.

Overriding the type

If the extension is wrong (or there is no filename, e.g. paste-without-filename), set the type explicitly.

Web

In the /new form, set Render as to a specific type. For an existing share, open the dashboard, open the actions menu (), pick Render as…, choose a type, submit.

CLI

At publish time:

anchorify path/to/data.txt --type csv

On an existing share:

anchorify type my-share json

API

In POST /api/v1/shares, pass "type": "json". To change an existing share, PATCH /api/v1/shares/<id> with {"type":"json"}. See the API reference.

HTML sanitization

HTML shares are rendered with DOMPurify before they reach the browser. The sanitization strips:

  • All <script> tags.
  • All inline event handlers (onclick, onload, etc.).
  • javascript: URLs in href and src.
  • CSS values that look like script injection — expression(...), javascript: inside url(), and similar.

The result is HTML that renders as authored visually but cannot execute code in the visitor's browser. This is enforced; you cannot turn it off.

If your HTML relies on JavaScript to render correctly, it will not work as an HTML share. Convert it to markdown, or host it somewhere else.

CSV and TSV rendering

CSV and TSV shares render as Notion-style HTML tables:

  • The first row is the header (<thead>).
  • Subsequent rows are the body (<tbody>).
  • Zebra striping on even rows.
  • Sticky header on vertical scroll.
  • Cells word-wrap on long values.
  • Numeric cells right-align. Any cell matching ^-?\d+(\.\d+)?$ (signed integers and simple decimals) gets right-aligned with tabular figures so numeric columns line up. Currency strings ($10), comma-thousands (1,234), dates (2024-01-02), and scientific notation (1e5) stay left-aligned.

There is no setting to disable header detection. If your file does not have a header row, the first row will still render as one. Add a header row at the top of the file before publishing.

Supported source formats

Source Stored content type Delimiter
.csv filename csv ,
.tsv filename tsv \t
Explicit --type csv / --type tsv csv / tsv as above

The parser is RFC 4180-tolerant: quoted commas, doubled quotes ("""), and embedded newlines all parse correctly. Inside-quote newlines render as <br> so multi-line cells stay readable.

Caps and truncation

For practicality the table renderer caps at 5,000 rows × 50 columns. When either cap trips, the share renders the first 5,000 rows × 50 columns and shows a banner above the table:

Showing first 5,000 of N rows · 50 of M columns · [Download full file]

The Download full file link points at ?raw=1 on the same share URL — a small endpoint that serves the original content with Content-Type: text/csv (or text/tab-separated-values) and Content-Disposition: attachment. Recipients can save the file locally and open it in Excel / Numbers / a spreadsheet of their choice.

If your data is bigger than the caps, the truncated preview still works as a quick-look. For full analysis the recipient downloads.

Out of scope

  • Column sort, filter, edit. The table is read-only; clicking a header does nothing. This is deliberate to keep the table render simple + server-side. Heavier interactive views are a V4 conversation.
  • Inferring header rows. If the file has no header, add one or live with the first data row appearing as a header.

Code rendering

Code shares use highlight.js. The language is inferred from the filename extension via the same extension map. If the extension is not bundled in the highlight.js common subset, the share falls back to a plain <pre> with no highlighting.

The render uses the GitHub light theme.

Common pitfalls per type

Anchorify silently fixes some authoring mistakes (UTF-8 BOM, JSON trailing commas, semicolon-delimited CSVs, leading HTML comments before slide frontmatter). For everything else there's a lint API and an owner-only warning banner on the share page. The list below covers what's worth knowing.

Markdown

  • YAML frontmatter shows as visible text — happens when the frontmatter block is malformed (no closing ---, embedded mid-document, contains an unrecognized key). Anchorify hides frontmatter only when the block opens with ---, closes with ---, and contains at least one recognized key (title, author, date, marp, reveal, theme, tags, etc.). Strict --- divider in the middle of your document is fine — it renders as a thematic break.
  • First <h1> ate a BOM — UTF-8 byte-order marks at the top of the file. Now stripped silently.
  • Want slide-deck rendering? See Slide decks.

Code

  • No syntax highlighting — the filename's extension isn't in our mapping (see the table above) or the language isn't bundled in highlight.js's common subset. The file still renders inside a <pre><code> block; just no token coloring.
  • No filename at all — the lint flags this. Set --type code and use a filename like script.py for highlighting.

JSON

  • Trailing commas[1, 2, 3,] parses fine. Anchorify recovers via a second-chance parse so the rendered output is still pretty-printed.
  • Comments (// foo) / single-quoted keys — JSON5 features that we don't try to fix. The render falls back to raw highlight; the lint flags it as an error.
  • UTF-8 BOM — stripped silently before parsing.

YAML

  • Tabs — YAML forbids tabs for indentation. The file still highlights but anyone consuming it programmatically will choke. Convert to spaces.

CSV / TSV

  • European Excel saves with ; — Anchorify detects this on .csv files when the first row has no commas but multiple semicolons. The render is correct; the lint flags it as info so you know the file isn't strictly portable.
  • BOM in the first cell header — Excel adds one. Stripped silently.
  • No header row — first row will render as a header regardless. Add a real header before publishing.
  • Caps: 5000 rows × 50 columns. Beyond that, the recipient gets a "Download full file" link to grab the raw CSV.

HTML

  • JavaScript stripped — DOMPurify removes all <script> tags, inline event handlers, javascript: URLs, and dangerous CSS. If your HTML needs JS to function, host it somewhere else.
  • <iframe>, <object>, <embed> also stripped — same sanitizer policy.

Image

  • SVG with embedded <script> — rendered as inline <img> so embedded scripts won't execute when the page loads. However, if you reference the same SVG directly (?raw=1), the browser may execute the script. Treat user-supplied SVGs as untrusted.
  • Mismatched MIME vs extension — the upload form picks the type from MIME first, then extension; the renderer trusts that.

PDF

  • Corrupt magic bytes — browser shows a blank embed. Re-export the PDF.
  • Encrypted PDFs — browser shows a password prompt. Same UX as opening the PDF locally.

Slides (Marp + reveal.js)

  • See the dedicated Slide decks guide for activation, frontmatter shape, and the most common pitfalls (frontmatter not detected, missing slide separators, ignored directives).

Next steps