Content-Signal: search=yes, ai-input=yes, ai-train=no
Content-Type: text/markdown; charset=utf-8
x-markdown-tokens: 312
Vary: Accept
What it does
Content-Signal Headers
Adds Content-Signal to every response, telling AI agents what they may do with your content — search, RAG, or training. Based on the Content Signals spec.
Content Negotiation
When an agent sends Accept: text/markdown, serves the .html.md companion file instead of HTML. Falls back to on-the-fly conversion via Turndown.
Token Count Header
Adds x-markdown-tokens so agents can budget their context window before downloading. Reads from YAML frontmatter or estimates at ~4 chars/token.
Per-Path Overrides
Use glob patterns to set different signal values per route. Block AI access to /api/** while allowing /blog/** for search and RAG.
On-the-fly Conversion
No companion file? Set convert: true and HTML responses are converted to markdown automatically. Strip nav, footer, ads with configurable selectors.
Security Built-in
Path traversal protection with separator-bounded checks. Vary header merging preserves downstream values. Status codes correctly preserved through interception.
How it works
Install
npm install contentsignals
Add middleware
app.use(contentsignals({...}))
Drop companions
Place .html.md files next to your HTML pages
Done
Agents get markdown, browsers get HTML, everyone gets Content-Signal
Quick start
Add to any Express app in 4 lines.
import express from 'express';
import { contentsignals } from 'contentsignals';
const app = express();
app.use(contentsignals({
signals: { search: true, aiInput: true, aiTrain: false },
staticDir: './public',
convert: true,
}));
app.use(express.static('./public'));
app.listen(3000);