# Billing
Source: https://docs.htmldrop.app/account/billing
How subscriptions, currency and invoices work.
Payments are handled by Stripe. htmldrop never sees your card details.
## Subscribing and changing plans
Upgrade from **Dashboard → Billing**. Changing plans, updating a card, and
cancelling all happen through the billing portal linked from the same page.
Annual billing charges ten months for twelve — the discount is applied to the
plan price, not as a coupon, so it persists for the life of the subscription.
## Currency
Prices are shown and charged in a currency resolved from your country when you
sign up. There is deliberately **no currency picker**: pricing reflects the market
you are actually in rather than whichever currency happens to be cheapest.
The currency is fixed to the account once resolved, so a later trip abroad or a
VPN cannot change what you are charged. If your country was detected wrongly,
email [support@htmldrop.app](mailto:support@htmldrop.app) and we will correct it.
## Tax
EU and UK VAT is added at checkout where applicable. Business customers can enter
a VAT number in the checkout form.
## Invoices
Every invoice is available in the billing portal, and receipts are emailed when a
payment succeeds.
## Cancelling
Cancel any time from the portal. Your plan stays active until the end of the
period you have already paid for, then the account returns to free — drops beyond
the free cap are paused rather than deleted, and become live again if you
re-subscribe or delete enough to fit.
## Exporting your data
`GET /api/v1/sites/export` returns every drop as a `.zip`, and the dashboard has
the same button. There is no lock-in and no export fee.
# Plans and limits
Source: https://docs.htmldrop.app/account/plans-and-limits
Exactly what each plan allows — drops, upload size, storage, versions, domains.
| | Anonymous | Free | Plus | Business |
| ---------------------- | ---------- | ------------------------ | --------------- | ---------------- |
| Price | — | \$0 | $12/mo · $99/yr | $49/mo · $490/yr |
| Signup | Not needed | Required | Required | Required |
| Active drops | — | 3 | 25 | 100 |
| Max per upload | 2 MB | 10 MB | 100 MB | 500 MB |
| Total storage | — | 50 MB | 2 GB | 20 GB |
| Versions kept | — | 3 | 10 | 30 |
| Lifetime | 7 days | Live while you're active | Permanent | Permanent |
| Folders and `.zip` | — | ✓ | ✓ | ✓ |
| Custom domains | — | — | 1 | 10 |
| Password protection | — | — | ✓ | ✓ |
| htmldrop badge | Shown | Shown | Removed | Removed |
| Analytics integrations | — | — | ✓ | ✓ |
Caps apply when you add, never retroactively. Going over a limit — for example
after a plan change — never deletes a live drop; it just stops you creating more
until you delete something or upgrade.
## "Live while you're active", precisely
A free drop runs a 30-day clock that resets every time the **owner** signs in.
Visitor traffic does not reset it. We email before anything expires, and an
expired drop can be recovered by signing back in before it is finally removed.
Paid drops have no clock at all.
## Grandfathered plans
Starter and Pro are no longer sold. If you are on one, nothing changes: your
price, your limits and your entitlements stay exactly as they were for as long as
the subscription is active.
## Which plan do I need?
* **Just sharing one file with someone** — anonymous is enough.
* **A handful of things you want to keep** — free.
* **A portfolio, client work, anything on your own domain** — Plus.
* **A team, many drops, or several domains** — Business.
# ChatGPT
Source: https://docs.htmldrop.app/agents/chatgpt
Add htmldrop as a custom connector, then publish from any conversation.
ChatGPT can talk to htmldrop through a custom MCP connector. Connectors cannot be
installed from inside a chat — that is deliberate on OpenAI's side, since it is
exactly what a prompt-injection attack would try — so you add it once in
settings.
**Settings → Apps → Advanced** and enable **Developer mode**. On Business and
Enterprise workspaces an admin may need to permit custom connectors first.
**Settings → Connectors → Create**, then enter:
```
https://htmldrop.app/mcp
```
Give it a name and a description — the model reads the description when
deciding whether to use it, so something like "Publishes HTML or Markdown to
a live URL" works well.
The first publish opens a browser window for OAuth. Approve once; ChatGPT
holds and refreshes the token from then on.
## Using it
> Make me a one-page portfolio and publish it with htmldrop
ChatGPT writes the HTML, calls `htmldrop_publish`, and hands back the live URL.
## Without a connector
If you would rather not add one, ask ChatGPT for a **single self-contained HTML
file**, download it, and drag it onto [htmldrop.app](https://htmldrop.app). Same
result, one extra step.
# CI and headless
Source: https://docs.htmldrop.app/agents/ci-and-headless
Publish from GitHub Actions, a container, or anywhere without a browser.
OAuth needs a browser, so automated environments use an API token instead.
[Dashboard → Settings → API tokens](https://htmldrop.app/dashboard/settings).
Tokens look like `hsk_live_…` and are shown once.
Never commit it. In GitHub Actions, add it under repository
**Settings → Secrets and variables → Actions**.
Use the CLI, or run the MCP server over stdio if an agent is driving.
## GitHub Actions
```yaml theme={null}
name: Publish preview
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci && npm run build
- name: Publish to htmldrop
env:
HTMLDROP_API_TOKEN: ${{ secrets.HTMLDROP_API_TOKEN }}
run: npx @htmldrop.app/cli deploy
working-directory: dist
```
## MCP over stdio
```bash theme={null}
HTMLDROP_API_TOKEN=$HTMLDROP_API_TOKEN npx -y @htmldrop.app/mcp
```
## Direct API
If you would rather not add a Node dependency, the
[REST API](/api/authentication) takes the same token as a bearer credential.
# Claude
Source: https://docs.htmldrop.app/agents/claude
Add htmldrop to Claude as a custom connector, or install the desktop bundle.
## Claude (web and desktop)
**Settings → Connectors → Add custom connector**, then:
```
https://htmldrop.app/mcp
```
The first publish opens a browser sign-in. Approve once and Claude keeps the
connection.
## Desktop bundle
Claude Desktop also accepts a one-click bundle — download
[htmldrop.mcpb](https://htmldrop.app/htmldrop.mcpb) and open it. Claude installs
the server and prompts for the same OAuth approval.
## Using it
> Publish this page with htmldrop
Claude calls `htmldrop_publish` and replies with the URL. Ask it to update and it
republishes to the same slug, so the link you already shared stays correct.
Claude is good at producing a single self-contained file when asked to. If a page
comes back split across several files, say "make it one self-contained
index.html" and publishing gets simpler.
# Claude Code
Source: https://docs.htmldrop.app/agents/claude-code
One command, OAuth in the browser, publish from the terminal.
```bash theme={null}
claude mcp add --transport http htmldrop https://htmldrop.app/mcp
```
That is the whole setup. The first tool call opens your browser for approval;
tokens are stored and refreshed by Claude Code.
## Using it
Inside a session:
> make me a landing page for my studio and put it online
Claude Code writes the file, calls `htmldrop_publish`, and prints the live URL.
Because it can also read your working directory, "publish the dist folder" works
once you have run your build.
## Verifying the connection
```bash theme={null}
claude mcp list
```
`htmldrop` should appear as a connected HTTP server. If a call fails with an
authorization error, remove and re-add the server to force a fresh sign-in:
```bash theme={null}
claude mcp remove htmldrop
claude mcp add --transport http htmldrop https://htmldrop.app/mcp
```
# CLI
Source: https://docs.htmldrop.app/agents/cli
Deploy the current directory from your terminal.
```bash theme={null}
npx @htmldrop.app/cli login # opens the browser, once
npx @htmldrop.app/cli deploy # publishes the current directory
```
`deploy` uploads the working directory as a drop and prints the live URL.
Deploying again updates the same drop, keeping the URL and adding a version.
## Typical use
```bash theme={null}
npm run build
cd dist
npx @htmldrop.app/cli deploy
```
## Without a browser
Set an API token and skip `login` entirely:
```bash theme={null}
HTMLDROP_API_TOKEN=hsk_live_... npx @htmldrop.app/cli deploy
```
That is the same mechanism used in [CI](/agents/ci-and-headless).
Install the **scoped** package. `@htmldrop.app/cli` is ours; the unscoped
`htmldrop` package on npm belongs to an unrelated project.
# Codex
Source: https://docs.htmldrop.app/agents/codex
Configure htmldrop in ~/.codex/config.toml with an API token.
Codex reads MCP servers from `~/.codex/config.toml`. Add htmldrop with a stdio
server and an API token:
```toml theme={null}
# ~/.codex/config.toml
[mcp_servers.htmldrop]
command = "npx"
args = ["-y", "@htmldrop.app/mcp"]
env = { HTMLDROP_API_TOKEN = "hsk_live_..." }
```
Create the token at
[htmldrop.app/dashboard/settings](https://htmldrop.app/dashboard/settings) under
API tokens. Treat it like a password — it grants full access to your drops.
Restart Codex after editing the file, then:
> Publish this file with htmldrop
The token path is used here because it works everywhere without a browser. If you
prefer OAuth and your Codex build supports remote HTTP MCP servers, point it at
`https://htmldrop.app/mcp` instead and skip the token entirely.
# Cursor and Cline
Source: https://docs.htmldrop.app/agents/cursor-and-cline
Add the remote server to your MCP settings JSON.
Both read a JSON MCP configuration. Add htmldrop as a remote server:
```json theme={null}
{
"mcpServers": {
"htmldrop": { "url": "https://htmldrop.app/mcp" }
}
}
```
**Cursor**: Settings → MCP → add the server, or edit `.cursor/mcp.json` in your
project.
**Cline**: the MCP servers panel, or `cline_mcp_settings.json`.
The first publish opens a browser for OAuth approval. No keys in the config file.
## Using it
> Publish the current file to htmldrop and give me the link
Since both tools can see your workspace, they can publish a built folder as
easily as a single file — run your build first, then ask for the output directory
to be published.
# Publishing from AI agents
Source: https://docs.htmldrop.app/agents/overview
One MCP server, OAuth in the browser, no API keys to paste. Connect once, then publishing is just asking.
htmldrop runs a hosted [MCP](https://modelcontextprotocol.io) server at
`https://htmldrop.app/mcp`. Any MCP-capable assistant can connect to it and
publish on your behalf — you approve once in the browser, and no keys are ever
pasted into a chat.
## The fastest setup
Paste this into your agent and it will configure itself from our machine-readable
instructions:
```
Fetch and execute the appropriate instructions to set me up for htmldrop from https://htmldrop.app/for-agents
```
Or follow a per-client guide: [ChatGPT](/agents/chatgpt) ·
[Claude](/agents/claude) · [Claude Code](/agents/claude-code) ·
[Codex](/agents/codex) · [Cursor and Cline](/agents/cursor-and-cline) ·
[CLI](/agents/cli) · [CI and headless](/agents/ci-and-headless).
## What the agent can do
| Tool | What it does |
| ------------------ | ------------------------------------------------------------------------------------------ |
| `htmldrop_publish` | Publishes HTML or Markdown and returns the live URL. Optional `slug`, `title`, `password`. |
| `htmldrop_list` | Lists the drops on the account. |
| `htmldrop_delete` | Deletes a drop by id. |
Publishing to an existing slug updates that drop and keeps the URL, so "update my
portfolio and republish" works the way you would hope.
## How authentication works
The remote server is OAuth-first. On the first tool call your browser opens, you
approve, and the client stores and refreshes tokens itself. Nothing to copy, and
you can revoke access at any time from the dashboard.
For environments with no browser — CI, containers, cron — use an API token
instead. See [CI and headless](/agents/ci-and-headless).
The npm packages are **scoped**: `@htmldrop.app/mcp` and `@htmldrop.app/cli`. The
unscoped `htmldrop` package is an unrelated third-party project. If an assistant
suggests `npx htmldrop`, correct it.
# Anonymous drops
Source: https://docs.htmldrop.app/api/anonymous-drops
Publish without an account: two endpoints, no credentials.
## Publish HTML
```bash theme={null}
curl -F "file=@page.html" https://htmldrop.app/api/v1/drops
```
`multipart/form-data` with a single `file` field. Returns:
```json theme={null}
{
"url": "https://wandering-fox-42.htmldrop.app",
"id": "v7r6ihmnv4mn",
"slug": "wandering-fox-42"
}
```
## Publish Markdown
```bash theme={null}
curl -X POST https://htmldrop.app/api/v1/drops-md \
-H "Content-Type: application/json" \
-d '{"title":"Release notes","markdown":"# 1.2\n\nShipped."}'
```
| Field | Required | Notes |
| ---------- | -------- | ------------------------------ |
| `markdown` | yes | CommonMark source |
| `title` | no | Page title and dashboard label |
## List the drops for this browser
```bash theme={null}
curl https://htmldrop.app/api/v1/drops --cookie-jar jar --cookie jar
```
Anonymous ownership is carried by a signed cookie, so this returns the drops made
by that browser. It is what the homepage uses to show you your own drops when you
return.
## Limits
* **2 MB** per upload
* **7 days** lifetime, on a fixed clock
* Single `.html` or `.md` files only — folders and `.zip` need an account
* Per-IP and per-session caps apply to prevent bulk abuse
Sign up from the same browser and these drops are claimed into your account
automatically, keeping their URLs.
# Authentication
Source: https://docs.htmldrop.app/api/authentication
API tokens for automation, OAuth for agents, and which endpoints need neither.
The API lives at `https://htmldrop.app/api/v1`. There are three ways to call it.
## No credentials
[Anonymous drop endpoints](/api/anonymous-drops) need nothing at all — that is
what powers the homepage drop area. Those drops are capped at 2 MB and live 7
days.
## API token
For scripts, CI and headless agents. Create one at
[Dashboard → Settings → API tokens](https://htmldrop.app/dashboard/settings);
tokens look like `hsk_live_…` and are shown once.
```bash theme={null}
curl https://htmldrop.app/api/v1/sites \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN"
```
A token carries the full rights of the account that created it. Store it as a
secret, never commit it, and revoke it from the same settings page if it leaks.
## OAuth
For MCP clients. The server implements OAuth 2.1 with PKCE and dynamic client
registration, so a compliant client discovers everything it needs:
```
https://htmldrop.app/.well-known/oauth-authorization-server
https://htmldrop.app/.well-known/oauth-protected-resource
```
Scopes are `mcp` and `cli`. You approve in the browser, the client stores and
refreshes tokens, and access can be revoked from the dashboard. See
[publishing from AI agents](/agents/overview).
## Rate limits and abuse controls
Anonymous publishing is rate limited per IP and per browser session, and uploads
pass an invisible bot check plus a phishing heuristic. Authenticated calls are
governed by your plan's limits rather than these controls — signing in is the
right fix for hitting an anonymous cap.
# Errors
Source: https://docs.htmldrop.app/api/errors
Status codes you should expect, and what to do about each.
Errors return JSON with an `error` field:
```json theme={null}
{ "error": "plan_limit", "detail": "drop limit reached for plan free" }
```
| Status | Meaning | What to do |
| ------ | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `400` | Malformed request — bad JSON, missing field, unsupported file type | Fix the payload. Only `.html`, `.htm`, `.md` and `.markdown` are accepted for single-file publishing |
| `401` | Missing or invalid credentials | Check the `Authorization` header; the token may have been revoked |
| `403` | Authenticated but not permitted — e.g. email not verified before checkout, or a feature above your plan | Verify your email, or upgrade for password protection and custom domains |
| `404` | No such drop, or not yours | Confirm the id and the account the token belongs to |
| `409` | Conflict — e.g. the slug is taken, or the account already has an active subscription | Pick another slug; for billing, use the portal link returned in the body |
| `413` | Upload exceeds your plan's per-upload cap | See [plans and limits](/account/plans-and-limits) |
| `429` | Rate limited — most often anonymous publishing from one network | Sign in; authenticated publishing is not governed by the anonymous caps |
| `5xx` | Our fault | Retry with backoff. Live status is at [htmldrop.app/status](https://htmldrop.app/status) |
## Serving errors
Requests to a drop's URL, rather than the API, return HTML rather than JSON:
* **404** — nothing published at that address
* **410** — the drop expired, or was removed by trust and safety
* **503** — the owner has not verified their email yet
Each is a branded page explaining what happened, safe to show to a visitor who
followed an old link.
# Sites
Source: https://docs.htmldrop.app/api/sites
Create a site, upload files and bundles to it, list, read and delete — the full authenticated surface.
Base URL is `https://htmldrop.app/api/v1`. Every endpoint here needs an
[API token](/api/authentication).
Most authenticated endpoints require a verified email and return
`403 {"error":"email_not_verified"}` otherwise. The exceptions — so a new
account can get one site live before confirming — are `POST /sites`,
`GET /sites`, `GET /sites/{id}`, `DELETE /sites/{id}`, `POST /sites/{id}/upload`
and `POST /sites/{id}/upload-bundle`. In practice this only affects dashboard
sessions: API tokens can only be created by an already-verified account.
## Create a site
```
POST /sites
```
Both fields are optional — omit `slug` for a random one, omit `name` to leave it
blank.
```bash theme={null}
curl -X POST https://htmldrop.app/api/v1/sites \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug": "my-project", "name": "My Project"}'
```
Returns `201`:
```json theme={null}
{
"id": "st_...",
"slug": "my-project",
"name": "My Project",
"access_mode": "public",
"has_password": false,
"branding_enabled": true
}
```
Creating a site only reserves the slug — it has no content until you upload.
## Upload a single file
```
POST /sites/{id}/upload
```
Multipart with one `file` field: `.html`/`.htm` is served as-is, `.md` is
rendered to HTML. Every upload creates a version and promotes it live.
```bash theme={null}
curl -X POST https://htmldrop.app/api/v1/sites/st_.../upload \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN" \
-F "file=@./index.html"
```
Returns `201`:
```json theme={null}
{ "version_id": "v_...", "version_number": 2, "byte_size": 4213, "file_count": 1 }
```
## Upload a folder or .zip
```
POST /sites/{id}/upload-bundle
```
Two accepted shapes:
Repeated `files` fields with a parallel `paths` field giving each file's
relative path. This is what the dashboard's folder picker sends, and what
multipart clients that can't preserve directory structure should use.
```bash theme={null}
curl -X POST https://htmldrop.app/api/v1/sites/st_.../upload-bundle \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN" \
-F "files=@./dist/index.html" -F "paths=index.html" \
-F "files=@./dist/assets/app.css" -F "paths=assets/app.css" \
-F "files=@./dist/assets/app.js" -F "paths=assets/app.js"
```
One `file` field whose filename ends in `.zip`. htmldrop extracts it
server-side and deploys the contents.
```bash theme={null}
curl -X POST https://htmldrop.app/api/v1/sites/st_.../upload-bundle \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN" \
-F "file=@./site.zip"
```
Either way `index.html` must exist at the root of the bundle. The response has
the same shape as a single-file upload.
## List and read
```bash theme={null}
curl https://htmldrop.app/api/v1/sites \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN"
curl https://htmldrop.app/api/v1/sites/st_... \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN"
```
`GET /sites` returns an array; `GET /sites/{id}` returns one site in the same
shape `POST /sites` returns.
## Update settings
`PATCH /sites/{id}` changes a site's configuration — name, slug, password,
analytics, and `spa_fallback`:
```bash theme={null}
curl -X PATCH https://htmldrop.app/api/v1/sites/{id} \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spa_fallback": true}'
```
## Delete
```bash theme={null}
curl -X DELETE https://htmldrop.app/api/v1/sites/st_... \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN"
```
Returns `204`. Deletion is immediate and permanent.
## The authenticated account
```bash theme={null}
curl https://htmldrop.app/api/v1/me \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN"
```
Returns the signed-in user and tenant: email, verification status, plan, role,
and the organisations available to switch between.
## Usage and export
```bash theme={null}
curl https://htmldrop.app/api/v1/usage -H "Authorization: Bearer $HTMLDROP_API_TOKEN"
curl https://htmldrop.app/api/v1/sites/export -H "Authorization: Bearer $HTMLDROP_API_TOKEN" -o export.zip
```
`usage` reports consumption against your plan's caps. `export` returns every
drop as a `.zip` — there is no lock-in and no export fee.
# Versions
Source: https://docs.htmldrop.app/api/versions
List a drop's history and promote an older version back to live.
## List versions
```bash theme={null}
curl https://htmldrop.app/api/v1/sites/{id}/versions \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN"
```
Returns the retained versions, newest first. How many are kept depends on your
plan — 3 on free, 10 on Plus, 30 on Business.
## Roll back
```bash theme={null}
curl -X POST \
https://htmldrop.app/api/v1/sites/{id}/versions/{version_id}/promote \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN"
```
Promoting makes that version live immediately at the same URL. The rollback is
itself recorded as a publish, so nothing is lost and you can roll forward again.
See [updating and rolling back](/publishing/updating-and-rollback) for the
concept and the dashboard equivalent.
# Concepts
Source: https://docs.htmldrop.app/concepts
Drops, versions, slugs, and the difference between anonymous, free and paid.
## Drop
A drop is one published thing at one URL. It might be a single HTML file or a
whole folder — either way it is one entry in your dashboard and one address.
## Slug
The subdomain a drop is served from: `slug.htmldrop.app`. Anonymous drops get a
random slug. With an account you can choose one (3–63 characters, lowercase
letters, numbers and hyphens), and on a paid plan you can put the drop on your
own domain instead.
## Version
Every upload to an existing drop creates a version. The newest is live; older
ones stay retrievable so you can [roll back](/publishing/updating-and-rollback).
How many are kept depends on your plan — 3 on free, 10 on Plus, 30 on Business.
## The three states an account can be in
| | Anonymous | Free account | Paid |
| ----------------------- | ------------- | ------------------------------------------------------------------ | --------------------------------- |
| Signup needed | No | Yes | Yes |
| Drop lifetime | 7 days, fixed | Live while you're active — a 30-day clock that each sign-in resets | Permanent |
| Max upload | 2 MB | 10 MB | 100 MB (Plus) / 500 MB (Business) |
| Folders and `.zip` | No | Yes | Yes |
| Custom domain, password | No | No | Yes |
Anonymous drops made in a browser are **claimed automatically** when you sign up
from that same browser — you do not have to re-upload anything.
# What htmldrop is
Source: https://docs.htmldrop.app/introduction
Turn any web-compatible file into a live, shareable URL — no build step, no framework, no signup to start.
htmldrop takes something you already have — an HTML file, a Markdown document, a
folder, a `.zip`, a built SPA, a p5.js sketch — and puts it on the web at a real
URL you can send to anyone. There is no build step, no framework to learn, and
nothing to install.
Drag an `.html` file onto the homepage and get a URL back. No account.
Connect once with OAuth, then tell ChatGPT or Claude to publish for you.
## What it is good at
**Sharing something that isn't a document.** Email attachments and PDF exports
lose interactivity. A drop keeps it — charts stay interactive, animations still
run, links still work.
**Everything an AI just built you.** Models are very good at producing a single
self-contained HTML file. They are not good at hosting it. htmldrop closes that
last step, either by drag-and-drop or by letting the agent publish directly
through [MCP](/agents/overview).
**Work that needs a URL, not a repository.** A portfolio, a CV, a client
proposal, a one-page report. No git, no CI, no deploy config.
## What it is not
It is not an application host. There is no server-side runtime, no database, and
no build pipeline — htmldrop serves static files. If your project needs `npm run
build`, run that yourself and drop the output folder.
## How a drop behaves
Every drop is served over HTTPS at `slug.htmldrop.app`, or at your own domain on
a paid plan. Uploads are versioned, so publishing again keeps the old copy and
you can [roll back](/publishing/updating-and-rollback) with one click.
**Anonymous drops live 7 days.** With a free account, drops stay live as long as
you sign in now and then — each visit resets a 30-day clock and we email you
before anything expires. Paid plans make every drop permanent. See
[plans and limits](/account/plans-and-limits).
# Folders and .zip bundles
Source: https://docs.htmldrop.app/publishing/folders-and-zip
Publish a multi-page site, a static-site-generator build, or a client-routed SPA.
A free account unlocks publishing a whole directory: drag a folder onto the
dashboard, or upload a `.zip`. Structure is preserved, so linked CSS, JS,
images and fonts resolve exactly as they would on any static host.
Folders and `.zip` bundles require an account. The homepage drop area, which
needs no signup, accepts single `.html` and `.md` files only.
`index.html` at the root of the upload is the landing page. Everything else is
served at its relative path.
## Static site generators
Ship the build output — `dist/`, `build/`, `_site/` — and every page works with
no configuration. Generators that write `about/index.html` for the page at
`/about` resolve automatically, as do flat builds that write `about.html`. A
directory page requested without its trailing slash redirects to the canonical
`/about/` form, so relative links inside the page resolve correctly.
Astro, Hugo, Eleventy, Jekyll and friends all work this way.
## Single-page apps
Ship `dist/` or `build/` as a bundle. For apps that route entirely on the
client, turn on **SPA fallback**:
Open the site and toggle **SPA fallback**.
```bash theme={null}
curl -X PATCH https://htmldrop.app/api/v1/sites/{id} \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spa_fallback": true}'
```
With it on, a path matching no file **and** no page — a client-side route like
`/settings` — serves `index.html` so your router takes over. Leave it off for
multi-page sites, so genuinely missing pages return a real 404.
## WebGL and canvas projects
p5.js sketches, Three.js scenes and similar multi-asset work deploy with their
textures, models and scripts intact — this is the case bundles exist for.
## Zipping a build
```bash theme={null}
cd dist && zip -r ../site.zip . && cd ..
```
Then drop `site.zip` on the dashboard, or publish it through the
[API](/api/sites) or an [agent](/agents/overview).
## Size limits
The cap applies to the upload as a whole: **10 MB** on free, **100 MB** on Plus,
**500 MB** on Business, with total storage of 50 MB, 2 GB and 20 GB. See
[plans and limits](/account/plans-and-limits). Over the cap, split the assets or
upgrade.
# Publishing HTML
Source: https://docs.htmldrop.app/publishing/html
Single files, inline assets, and what does and doesn't work in a static drop.
Drop an `.html` file and it is served exactly as written. No transformation, no
build step, no framework detection.
## Make it self-contained
A single file with its CSS and JavaScript inline is the most reliable thing you
can publish. Relative references to files you did not upload will 404 — if your
page needs `styles.css` or `logo.png`, either inline them or publish the whole
[folder](/publishing/folders-and-zip).
External URLs work normally: a CDN script, a Google Font, an image hosted
elsewhere all load as they would anywhere.
## What runs
Anything the browser can do on its own:
* JavaScript, including canvas and WebGL — p5.js sketches, Three.js scenes
* CSS animations, custom properties, container queries
* `fetch()` to any API that permits cross-origin requests
* Web fonts, SVG, video and audio elements
## What does not
There is no server-side runtime. No PHP, no Node, no Python, no database, no
environment variables. Server-rendered frameworks must be built to static output
first — run `npm run build`, then publish the resulting folder.
Anything in a drop is public to anyone with the URL unless you add
[password protection](/sharing/password-protection). Never publish API keys,
tokens or credentials — treat a drop like a public repository.
## Head control
Your `
` is preserved, so ``, ``, Open Graph
tags and favicons all behave as expected — including in link previews when the
URL is pasted into Slack, WhatsApp or a social post.
On free and anonymous drops a small "Made with htmldrop" badge is added in the
corner. Paid plans remove it.
# Publishing Markdown
Source: https://docs.htmldrop.app/publishing/markdown
Drop a .md file and get a styled, readable page — no theme to pick.
Drop a `.md` or `.markdown` file and htmldrop renders it to a styled HTML page
server-side. You do not choose a theme or write any CSS.
This is the fastest route for anything that is fundamentally text: release notes,
a proposal, meeting notes, a runbook, an RFC.
## What is supported
Standard CommonMark, including:
* Headings, lists, and nested lists
* **Bold**, *italic*, `inline code`, and fenced code blocks
* Links, images, and blockquotes
* Tables
* Horizontal rules
The rendered page is responsive and readable on a phone without any work from
you.
## Via the API
Markdown has its own endpoint that takes JSON rather than a file upload:
```bash theme={null}
curl -X POST https://htmldrop.app/api/v1/drops-md \
-H "Content-Type: application/json" \
-d '{"title":"Q3 notes","markdown":"# Q3 notes\n\nWe shipped."}'
```
The `title` becomes the page title and the dashboard label. See
[anonymous drops](/api/anonymous-drops) for the full contract.
If you want precise control over layout, publish [HTML](/publishing/html)
instead. Markdown is for when you want to write and be done.
# Pasting HTML or Markdown
Source: https://docs.htmldrop.app/publishing/paste
No file on disk? Paste markup straight into the dashboard and publish it.
Sometimes the thing you want online was never a file — a snippet an assistant
produced in a chat window, a note you just typed, a fragment from someone
else's message.
In the dashboard you can paste HTML or Markdown directly into a text box and
publish it to a URL, with no intermediate save-to-disk step.
Go to [htmldrop.app/dashboard](https://htmldrop.app/dashboard) and start a
new drop.
Choose the paste option and drop your HTML or Markdown in. Markdown is
rendered to a styled page; HTML is served as-is.
You get a live URL immediately, exactly as with an uploaded file — including
versions, so you can paste a revision later and still roll back.
If an AI assistant produced the content, connecting it via
[MCP](/agents/overview) removes this step entirely — the agent publishes
directly and hands you the link.
# Templates
Source: https://docs.htmldrop.app/publishing/templates
Ready-to-edit starting points when you don't want to begin with a blank page.
[htmldrop.app/templates](https://htmldrop.app/templates) has starting points you
can customise and publish in minutes — useful when you know what you want but
would rather not write the markup.
A clean, monospace résumé layout.
A grid layout for showing project work.
A stacked links page for a single bio link.
A shareable page for one event.
Pick one, edit the content in the dashboard, and publish. The result is an
ordinary drop — same URL behaviour, same versions, same option to add a
[custom domain](/sharing/custom-domains) or a
[password](/sharing/password-protection) afterwards.
Templates are a starting point, not a constraint: you can export or replace the
markup at any time, and a template-based drop can later be overwritten by an
upload like any other.
# Updating and rolling back
Source: https://docs.htmldrop.app/publishing/updating-and-rollback
Every upload is a version. Publishing again never loses the old copy.
Publishing to an existing drop creates a new version and makes it live
immediately. The URL never changes, so a link you shared last week keeps working
and simply shows the newer content.
## Rolling back
Open the drop in the dashboard, find the version you want in the history, and
promote it. It becomes live at once — a rollback is itself a normal publish, so
nothing is destroyed and you can roll forward again.
Via the API:
```bash theme={null}
curl -X POST https://htmldrop.app/api/v1/sites/{site_id}/versions/{version_id}/promote \
-H "Authorization: Bearer $HTMLDROP_API_TOKEN"
```
## How many versions are kept
| Plan | Versions retained |
| -------- | ----------------- |
| Free | 3 |
| Plus | 10 |
| Business | 30 |
Once the limit is reached, the oldest version is dropped as a new one arrives.
Being over a limit never deletes a live drop — caps apply when you add, not
retroactively.
## Updating from an agent
Publishing to the same slug is an update, so this works as you would expect:
> Update my portfolio and republish
The agent calls `htmldrop_publish` with the existing slug, and the live URL stays
the same.
# Quickstart
Source: https://docs.htmldrop.app/quickstart
From a file on your machine to a live URL, three ways: the browser, an AI agent, or the API.
## The fastest way: drop it
Go to [htmldrop.app](https://htmldrop.app) and drag an `.html` or `.md` file onto
the drop area. You get a live URL immediately — no account, no email.
Anonymous drops are capped at **2 MB** and stay live for **7 days**. Sign up
free at any point and the drops made in that browser are claimed into your
account automatically.
## From an AI agent
If your assistant speaks [MCP](/agents/overview), it can publish on your behalf.
Connect once:
```bash theme={null}
claude mcp add --transport http htmldrop https://htmldrop.app/mcp
```
Then just ask:
> Publish this page with htmldrop
The agent calls `htmldrop_publish` and replies with the live URL. Setup guides:
[ChatGPT](/agents/chatgpt) · [Claude](/agents/claude) ·
[Claude Code](/agents/claude-code) · [Codex](/agents/codex) ·
[Cursor and Cline](/agents/cursor-and-cline).
## From the command line
```bash theme={null}
npx @htmldrop.app/cli login # browser sign-in, once
npx @htmldrop.app/cli deploy # publishes the current directory
```
The npm packages are **scoped**: `@htmldrop.app/cli` and `@htmldrop.app/mcp`.
The unscoped `htmldrop` package on npm is an unrelated third-party project — do
not install it.
## From the API
```bash theme={null}
curl -F "file=@page.html" https://htmldrop.app/api/v1/drops
```
That endpoint is the anonymous one — it needs no credentials and returns a share
URL. For anything tied to your account, see [authentication](/api/authentication).
# Analytics
Source: https://docs.htmldrop.app/sharing/analytics
Connect GA4, Meta Pixel, PostHog or Plausible by pasting an ID — no code edits.
Paid plans can attach analytics to a drop without touching its markup. Paste an
ID in the dashboard and htmldrop injects the snippet when the page is served.
| Provider | What to paste |
| ------------------ | ------------------------------------------ |
| Google Analytics 4 | Measurement ID, e.g. `G-XXXXXXX` |
| Meta Pixel | Pixel ID |
| PostHog | Project API key (and host, if self-hosted) |
| Plausible | The domain configured in Plausible |
Because injection happens at serve time, it applies to every page of a folder
upload and survives republishing — you never have to remember to re-add a tag.
## Consent banner
Cookie-setting trackers (GA4 and Meta Pixel) can trigger a built-in consent
banner, available in 12 languages, so a drop aimed at EU visitors can ask before
those scripts run. Cookieless setups (PostHog and Plausible in their default
configurations) do not require it.
The consent banner covers the trackers **you** attach to your drop. It is
separate from htmldrop's own analytics on our marketing site, which is governed
by our [privacy policy](https://htmldrop.app/privacy).
## Built-in numbers
Every drop shows basic view counts in the dashboard without any provider
attached. The integrations above are for when you want your own tooling,
audiences and funnels.
# Custom domains
Source: https://docs.htmldrop.app/sharing/custom-domains
Serve a drop from your own domain, with SSL issued automatically.
Paid plans let you serve a drop from a domain you own — `share.yourbrand.com`
instead of `slug.htmldrop.app`. SSL is issued and renewed automatically; there is
nothing to configure or pay for separately.
| Plan | Custom domains |
| -------- | -------------- |
| Free | — |
| Plus | 1 |
| Business | 10 |
## Setting one up
In the dashboard, open the drop, then the domain card, and enter the hostname
you want to use.
Create the CNAME record shown to you at your DNS provider. If the domain sits
behind Cloudflare, leave the record **DNS only** (grey cloud) until
verification completes.
We poll for the record and issue the certificate automatically. This is
usually minutes, but DNS propagation can take longer — the card shows the
current state throughout.
## Notes
* **Bring your own domain.** htmldrop does not sell domains; register yours
wherever you like.
* **Apex domains** (`yourbrand.com` with no subdomain) depend on your DNS
provider supporting ALIAS/ANAME flattening. A subdomain like `share.` or
`docs.` always works.
* Removing a domain is instant and the drop reverts to its `htmldrop.app`
address, which never stops working.
# Links and expiry
Source: https://docs.htmldrop.app/sharing/links-and-expiry
How long a drop stays live, what the 30-day clock actually means, and how claiming works.
## Anonymous drops
A drop made without an account lives **7 days** from upload, on a fixed clock
that nothing resets. It is capped at 2 MB.
Anonymous drops are remembered per browser through a signed cookie, which is how
the homepage can still list yours when you come back — and how they get claimed
if you sign up later.
## Free accounts
Free drops stay live **as long as you are active**. Each time the owner signs in,
a 30-day clock resets. If a full 30 days pass with no sign-in, we email you
before anything expires, and expired drops can be recovered by signing back in
before they are removed for good.
The clock is reset by **owner activity**, not by visitors. A page getting traffic
does not keep it alive; you signing in does.
## Paid plans
Drops never expire. No clock, no email, no recovery window to worry about.
## Claiming anonymous drops
Sign up from the same browser and every anonymous drop made there is moved into
your account automatically — same URLs, no re-upload. This is why the homepage
shows a "Sign up & claim" prompt once you have drops.
## Deleting
Deleting a drop is immediate and permanent: the URL stops resolving and returns a
branded page explaining it is gone. Anyone still holding the link sees that page,
not your content.
# Password protection
Source: https://docs.htmldrop.app/sharing/password-protection
Put a single password in front of a drop — for client drafts and anything not meant to be found.
Paid plans can require a password before a drop renders. Visitors get a simple
unlock screen; there are no accounts to create and nothing for them to install.
Use it for client drafts, internal documents, early ideas — anything you want to
send to specific people rather than publish to the world.
## Turning it on
Open the drop in the dashboard, set a password on the protection card, and save.
It applies immediately, including to links already shared.
Once unlocked, a visitor stays unlocked for that drop in that browser, so they
are not asked again on every page of a multi-page bundle.
## What it is and isn't
Password protection is **access control, not encryption**. It stops casual and
search-engine access to a URL; it is not a substitute for encrypting genuinely
sensitive material. Protected drops are excluded from search indexing and from
the public gallery.
A protected drop's URL is still a normal URL — anyone who has both the link and
the password can open it, and they can pass both on. Rotate the password if a
draft's audience changes.