From file to link.

Documents made with coding agents often stay as local files. HTMLinks gives you a simple skill to upload an HTML document and get a shareable link. Update it later, keep the same link, and add a password when needed.

HTMLinks accepts HTML directly from your agent. Uploading needs no account or API key. Use free managed hosting or point the same skill at your own server.

Install the skill.

Choose your agent, run the GitHub install command in your project directory, and reload your agent’s skills. On first use, it asks for managed hosting or your self-hosted server and remembers your answer.

npx skills add michael-elkabetz/HTMLinks --skill htmlinks --agent claude-code

Install from GitHub in your project, then reload your agent’s skills.

First upload? Your agent asks: managed hosting or your own server? It remembers your choice.

“/HTMLlink upload the Idea HTML doc”

Your agent uploads. You get a link. That’s it.

Add --global to install across projects. The repository’s skills/htmlinks/ folder includes both the skill instructions and the standalone helper. Installing the application or its dependencies is unnecessary.

Ask your agent to publish a selected HTML file. It returns a sharing URL and stores the document’s private management key locally. No sign-in or token setup follows installation.

Run htmlinks.mjs config to see the saved destination. Change it with config --hosting managed or config --hosting self-hosted --url https://your-server. An explicit HTMLINKS_URL overrides the destination for one invocation. Preferences are stored locally; updating the skill preserves them.

Ask to password-protect a document before upload. The skill uses --access password --password-stdin with secure password input. For an existing document, use access <id> --mode password --password-stdin. Content updates preserve the password and URL.

Talk to the API.

Base: https://www.htmlinks.com/api/v1. Create a document with multipart file and optional JSON metadata fields.

curl 'https://www.htmlinks.com/api/v1/documents' \
  -F 'file=@./design.html' \
  -F 'metadata={"title":"System design","access":"link"}'

Creation returns id, url, manageKey, revision, and etag. Keep the management key private. Every later operation on that document requires Authorization: Bearer <manageKey>.

MethodPath under /api/v1Action
POST/documentsUpload a file. No authentication required.
GET/documents/:idRead author metadata and current ETag.
PATCH/documents/:idChange title, category, access, password, or expiry.
PUT/documents/:id/contentReplace HTML and preserve its URL.
GET/documents/:id/sourceDownload HTML source.
GET/documents/:id/revisionsList previous revisions.
GET/documents/:id/revisions/:revisionId/sourceDownload a previous revision.
POST/documents/:id/rollbackRepublish a revision as a new revision.
DELETE/documents/:idDelete immediately; recoverable during retention.
POST/documents/:id/restoreRestore within the retention window.

There is no public document list, account API, or management dashboard. The CLI’s list command lists records saved on your own machine.

Metadata accepts title, category (design, presentation, technical, report, other), access (link, password, or private), optional password (8–256 characters), and nullable expiresAt (ISO timestamp). Passwords belong in a protected request body, never URLs or command arguments.

Updates, rollback, deletion, and restore require If-Match with the exact current ETag. Missing preconditions return 428; stale edits return 412. The CLI fetches this automatically. Rollback accepts {"revisionId":"…"}.

For upload retries, use a random Idempotency-Key of 32–128 characters (a UUID works). Reuse it with the same file and metadata for up to 24 hours. Treat this retry key as a temporary secret: it can recover the creation response. Cached responses are encrypted at rest. Different input returns 409.

Errors use {error: {code, message, requestId}}. Uploads are limited to 10 MiB, with rate and instance-storage limits. Operators can lower the limits. Download OpenAPI.

A link is not an edit key.

The sharing URL lets recipients read a link-access document. The management key controls that one document’s metadata, source, revisions, updates, and deletion. Neither credential grants access to anyone else’s documents.

The skill stores keys in ~/.config/htmlinks/ using private file permissions. It omits them from its output. Back up that folder; losing a key means losing edit/delete access. There is no account recovery flow. An operator can still remove abusive content through the database.

Password protection prevents delivery until a recipient unlocks the document. Changing access or deleting revokes future requests, including existing viewer sessions. Authorized readers can still save copies. Private mode requires a management-key-authorized preview handoff through POST /documents/:id/preview.

Keep it self-contained.

Upload UTF-8 .html or .htm. Inline scripts and CSS work. Embed images and fonts as data URLs. Slide navigation and local charts can run inside the viewer’s isolated sandbox.

External scripts, API requests, browser storage, forms, popups, and nested frames are blocked. Native PDF, PPTX, DOCX, ZIP, and multi-file sites are not accepted. Try the real HTML slide example.

Give it your own home.

Self-hosting currently supports local PostgreSQL with files stored on disk, or Supabase for both PostgreSQL and file storage. You run HTMLinks on your own server with either option.

To start with local PostgreSQL, run these commands with Docker Compose and Node.js 22.22+ installed. The setup creates your .env file and configures the database for you:

git clone https://github.com/michael-elkabetz/HTMLinks.git
cd HTMLinks
node scripts/init.mjs
docker compose up --build -d

Open http://localhost:3000 and install the skill from that instance. The viewer uses http://127.0.0.1:3002. There is no owner setup, account registration, or API-token configuration.

Choose self-hosted in the skill’s first-use question and supply your app origin, such as http://localhost:3000. The same skill and API work for both hosting options. Source on GitHub.

Compose includes PostgreSQL, migrations, private file storage, an isolated viewer, and maintenance. Named volumes preserve data. Production requires HTTPS and separate app/content domains. Configure upload limits and a trusted proxy before exposing an anonymous-upload service.

Back up both PostgreSQL and uploaded files, plus HTMLINKS_SECRET for in-flight retry responses. docker compose down retains data; removing volumes deletes it.

Prefer Supabase?

Create a Supabase project and a private Storage bucket named htmlinks. In your HTMLinks checkout, run node scripts/init.mjs if you haven’t already, then update these values in .env:

# Your Supabase Postgres connection string
DATABASE_URL=postgres://...
STORAGE_DRIVER=supabase

# Your Supabase project URL and server-side service-role key
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# The name of your private Storage bucket
SUPABASE_BUCKET=htmlinks

Then start HTMLinks with the Supabase configuration. It connects to your Supabase database and storage and runs migrations for you:

docker compose -f compose.cloud.yaml up --build -d

If migrations need a separate database connection, also set MIGRATION_DATABASE_URL to a direct connection string. For a public server, set APP_URL and VIEWER_URL to your HTTPS app and content origins on separate domains.

Keep the bucket private and the service-role key on your server. Leave the htmlinks schema out of the exposed Data API schemas. You don’t need Supabase Auth. When using the agent skill, choose self-hosted and enter your HTMLinks app URL.