Add a tool
A tool is one folder with one index.html in it. There is no
build step, no framework and no bundler — write HTML, CSS and JavaScript,
commit, push, and Cloudflare deploys it.
1. Scaffold the folder
npm run new-tool -- slug-of-my-tool
That copies public/tools/_template/ to
public/tools/slug-of-my-tool/. Copying the folder by hand
works just as well.
2. Register it
Add an entry to the tools array in
public/assets/tools.js so it appears on the landing page and
gets its title and description:
{
slug: "slug-of-my-tool",
name: "My Tool",
description: "One sentence about what it does.",
icon: "#",
tags: ["text", "developer"],
added: "2026-09-04",
}
3. Write the tool
Put your interface inside <main id="tool">. The shared
script adds the header, the page title, the privacy notice and the footer
around it:
<link rel="stylesheet" href="/assets/styles.css">
<main id="tool">
<!-- your UI -->
</main>
<script type="module" src="/assets/tool-shell.js"></script>
<script type="module" src="./tool.js"></script>
Reuse the shared classes — .panel, .field,
.row, .stats, .status,
button.primary — so every tool looks like part of the same
site. Run npm run dev to preview locally.
The rules every tool must follow
-
No network calls. No
fetch(), noXMLHttpRequest, nonavigator.sendBeacon(), noWebSocket, noEventSource, no<form action>, no<img>pointed at another origin. Everything a tool needs must be computed locally. -
No third-party resources. No CDN scripts, no Google
Fonts, no external stylesheets, no embedded iframes, no analytics or
error-reporting SDKs. Vendor any library you genuinely need into
public/assets/vendor/and load it from this origin. -
No hidden persistence.
localStorageis fine for the user's own settings. Do not store the content they are working on unless the page tells them you do and gives them a way to clear it. -
Keep the privacy notice. It is injected by
/assets/tool-shell.js. Do not remove it or hide it, and do not weaken its wording — it lives in one place,public/assets/shell.js. -
Say so if a tool is different. If a tool ever needs to
talk to the network, it must state that on its own page, above the
fold, in plain language — and the CSP in
public/_headershas to be widened deliberately, in the same pull request, with a reviewer. - Stay accessible. Label every input, keep contrast usable in light and dark, and make sure the tool works with a keyboard alone.
How this is checked
npm test scans public/ for the forbidden APIs
and for off-origin URLs, and fails if it finds any. The Content Security
Policy in public/_headers blocks them again at runtime, in
the visitor's browser, even if something slips past review. Neither check
replaces reading the diff.