How to implement the Untitled Protocol

Pick the surface that fits your stack. No registration, no build step beyond what you already have.


1 — Any HTML page

Add one line inside <head>:

<meta name="untitled" content="now">

Available states — combine freely with a comma:

now present-tense content that reflects the author's thinking right now
draft work in progress; not the author's settled position
ephemeral intentionally transient — must not be archived
anonymous author identity must not be inferred or attributed
collective created by multiple contributors; no singular author
generative AI-generated or AI-assisted content
<!-- combine freely -->
<meta name="untitled" content="now, anonymous">
<meta name="untitled" content="draft, collective">

2 — React, Vue, Astro, Next.js

Install untitled-meta for programmatic control in any JS/TS project:

npm install untitled-meta

Client-side

import { useEffect } from 'react'
import { setUntitled } from 'untitled-meta'

export default function DraftPost() {
  useEffect(() => { setUntitled('draft') }, [])
  return <article>...</article>
}
// inside <script setup> in your component
import { onMounted } from 'vue'
import { setUntitled } from 'untitled-meta'

onMounted(() => setUntitled('now'))
<!-- src/layouts/Layout.astro -->
<html>
  <head>
    <meta name="untitled" content="now">
  </head>
  <body>
    <slot />
  </body>
</html>
'use client'
import { useEffect } from 'react'
import { setUntitled } from 'untitled-meta'

export default function Page() {
  useEffect(() => { setUntitled('now') }, [])
  return <main>...</main>
}
import { setUntitled } from 'untitled-meta'

setUntitled('now')           // creates or updates <meta name="untitled">
setUntitled('now, draft')    // combine states

Server-side

import { headerObject } from 'untitled-meta'

// Applies to all routes
app.use((req, res, next) => {
  res.set(headerObject('now'))
  next()
})

// Per route
app.get('/drafts/:id', (req, res) => {
  res.set(headerObject('draft'))
  res.send(content)
})
// middleware.js
import { NextResponse } from 'next/server'
import { headerObject } from 'untitled-meta'

export function middleware(request) {
  const response = NextResponse.next()
  const h = headerObject('now')
  response.headers.set('X-Untitled', h['X-Untitled'])
  return response
}

3 — WordPress

Install the Untitled Protocol plugin — no code required.

wp plugin install untitled-protocol --activate

Or search for Untitled Protocol in Plugins → Add New in your WordPress admin.

Once activated:

The plugin outputs:

<meta name="untitled" content="now">

Works for any content type. Processed before the document is parsed — no HTML changes needed.

# Site-wide
add_header X-Untitled "now";

# Per location
location /drafts/ {
    add_header X-Untitled "draft";
}

location /temp/ {
    add_header X-Untitled "ephemeral";
    add_header Cache-Control "no-store";
}
Header always set X-Untitled "now"

<Directory "/var/www/drafts">
    Header always set X-Untitled "draft"
</Directory>
// Middleware — applies to all routes
app.use((req, res, next) => {
  res.setHeader('X-Untitled', 'now')
  next()
})

// Single route
app.get('/draft', (req, res) => {
  res.setHeader('X-Untitled', 'draft')
  res.send(content)
})
@app.after_request
def set_untitled_header(response):
    response.headers['X-Untitled'] = 'now'
    return response
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [{ "key": "X-Untitled", "value": "now" }]
    }
  ]
}
/*
  X-Untitled: now

5 — Sidecar file

For files without HTTP headers — assets in archives, shared drives, email attachments — place an UNTITLED file alongside them.

cat > report.pdf.UNTITLED <<EOF
state: draft
created: 2026-05-22
note: Internal draft. Not for citation.
EOF
cat > UNTITLED <<EOF
state: ephemeral, anonymous
spec: https://untitled.now/spec/sidecar/v1
EOF
echo "state: now" > document.pdf.UNTITLED

6 — The visual indicator

untitled.js reads the <meta name="untitled"> tag and renders a small pulsing _ in the bottom-right corner. Zero configuration.

<script src="https://untitled.now/js/untitled.js"></script>

No render if no meta tag is present.

7 — Verify your implementation

# Check the HTTP header
curl -sI https://your-site.com | grep -i x-untitled

# Check the meta tag
curl -s https://your-site.com | grep -i 'name="untitled"'

Or use the validator: untitled.now/validate

Not sure which state fits your content? untitled.now/suggest takes any URL and returns an AI-powered recommendation.