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:
- An explicit type override on the request (form field, CLI flag, or API field).
- The filename extension.
- 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 inhrefandsrc.- CSS values that look like script injection —
expression(...),javascript:insideurl(), 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 codeand use a filename likescript.pyfor 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.csvfiles when the first row has no commas but multiple semicolons. The render is correct; the lint flags it asinfoso 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.
- 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
- Slide decks — Marp + reveal.js.
- Lint API — pre-publish format check.
- Web dashboard — the Render as picker.
- Command-line —
--typeandanchorify type. - REST API — the
typefield onPOST/PATCH.