---
name: pagehub-import
description: Send a local static site into PageHub as an editable multi-page site
---

# Import a local site into PageHub

## 1. When to use this

The user has a site on their machine — plain HTML/CSS/JS, or a React / Vite / Next / Astro project — and wants it in PageHub so they can edit it visually and host it. The import builds a **new draft site** with one editable page per `.html` file. It never changes an existing site.

## 2. Make it static first

PageHub imports the HTML that's in each file. It doesn't run JavaScript to draw the page. A page's own scripts (`<script>`, `on*` handlers) are kept as written and listed in the import summary; they run on the preview and the live site, not in the editor canvas.

- **Plain HTML:** send the folder as it is.
- **React, Vite, Astro, SvelteKit:** run the build (`npm run build`) and send the output folder — usually `dist/` or `build/`.
- **Next.js:** use static export (`output: "export"` in `next.config.js`), build, and send `out/`.
- Check one built page: its `<body>` must hold real content. A body that is only `<div id="root"></div>` imports empty, and the import says so.

## 3. Folder checklist

- `index.html` at the top of the folder. It becomes the home page.
- One `.html` file per page (`about.html`, `services/roofing.html`, …).
- Links between pages are relative (`href="about.html"`), so they turn into links between the new pages.
- Images, CSS, JS and fonts sit inside the folder. Files outside it, and links to files that aren't there, are skipped with a warning.
- Tailwind from the CDN is fine — PageHub compiles Tailwind itself. Colors and fonts from an inline `tailwind.config` become theme colors and fonts in Design System; anything that can't carry over is listed.
- No `node_modules`, no `.git`. Hidden files are left out.
- At most **50 MB** zipped and **2,000 files**.

## 4. Option A — local PageHub MCP server

If the PageHub MCP server runs locally (stdio) with `PAGEHUB_API_KEY` set and `PAGEHUB_UPLOAD_ROOT` pointing at the project folder:

```
import_site({ filePath: "./dist", name: "Acme Roofing" })
```

It zips the folder (or takes a `.zip`), uploads it, waits for the import and returns the editor link. `name` is optional; the default is the home page's title.

## 5. Option B — hosted PageHub connector + curl

For an agent signed in to the hosted connector `https://pagehub.dev/api/mcp` that can run shell commands (Claude Code: `claude mcp add --transport http pagehub https://pagehub.dev/api/mcp`).

1. Zip the built folder, from inside it, so `index.html` is at the top of the zip:
   ```bash
   cd dist && zip -r ../site.zip . -x '.*' '*/.*' && cd ..
   ```
2. Get its exact size in bytes:
   ```bash
   wc -c < site.zip
   ```
3. Ask for an upload link: `import_site({ size: <bytes> })`. It returns an `uploadKey`, an upload URL and a ready `curl` line.
4. Run that line exactly, from the folder that holds `site.zip`:
   ```bash
   curl -sS -X PUT -H "Content-Type: application/octet-stream" --data-binary @site.zip "<uploadUrl>"
   ```
   No output means it uploaded. The link works for **10 minutes**, and the file must be exactly the size you passed — otherwise storage answers with an XML `403` error. If that happens, start again from step 2.
5. Start the import: `import_site({ uploadKey: "<uploadKey>", name: "Acme Roofing" })`. It returns an `importId`.
6. Call `get_import_status({ importId })` every ~5 seconds until the status is `ready` (or `failed`, with the reason). `ready` includes the site id, editor link and preview link.

**No shell at all** (for example claude.ai chat): send the files inline instead — `import_site({ files: [{ path: "index.html", content: "<!doctype html>…" }, { path: "css/site.css", content: "…" }] })`. Text files only, up to 300 files and 3 MB in total, then poll `get_import_status` the same way.

## 6. Option C — plain curl with an API key

No MCP needed. Create a key at https://pagehub.dev/dashboard/api-key and run these from the folder that holds `site.zip` (zipped as in Option B). The examples use `jq` to read the responses; without it, copy the values by hand.

```bash
export PAGEHUB_API_KEY="ph_…"
SIZE=$(wc -c < site.zip | tr -d ' ')

# 1. Upload link (valid 10 minutes, pinned to this exact size)
PRESIGN=$(curl -sS -X POST https://pagehub.dev/api/import/presign \
  -H "Authorization: Bearer $PAGEHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"source\":\"html\",\"filename\":\"site.zip\",\"size\":$SIZE}")
KEY=$(echo "$PRESIGN" | jq -r .key)
UPLOAD_URL=$(echo "$PRESIGN" | jq -r .uploadUrl)

# 2. Upload the zip (no output = uploaded)
curl -sS -X PUT -H "Content-Type: application/octet-stream" --data-binary @site.zip "$UPLOAD_URL"

# 3. Start the import
IMPORT_ID=$(curl -sS -X POST https://pagehub.dev/api/import \
  -H "Authorization: Bearer $PAGEHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"source\":\"html\",\"fileKeys\":[\"$KEY\"],\"autoConfirm\":true,\"options\":{\"siteName\":\"Acme Roofing\"}}" \
  | jq -r .importId)

# 4. Check progress every ~5 seconds until "status" is "ready" or "failed"
curl -sS "https://pagehub.dev/api/import/status?importId=$IMPORT_ID" \
  -H "Authorization: Bearer $PAGEHUB_API_KEY" | jq '{status, progress, siteId, error, warnings}'
```

When it's `ready`, the response carries `siteId`, `draftId` and `previewUrl` (the draft's preview link: hand that over, don't build one), and the editor is at `https://pagehub.dev/dashboard/sites/<siteId>/design`.

## 7. What you get

- A new **draft** site. Nothing is published.
- One page per `.html` file, with links between them working.
- The header and footer that repeat across pages become one shared header and footer.
- **Redirects from the old page addresses**, such as `/about.html` → `/about`, so old links keep working once the site is live on its domain.
- A list of warnings. The common ones:
  - **"… is drawn by JavaScript (React) …"** — that page had no HTML content. Build or export it as static HTML (section 2) and import again.
  - **"… is linked but isn't in the upload"** — a file the pages point to is missing from the folder. Add it and import again, or fix it in the editor.
  - **"Some Tailwind config colors couldn't be read …"** / **"Your Tailwind config adds fonts PageHub has no slot for …"** — add those in Design System so the classes style again.
  - **"Classes like bg-opacity-50 are from an older Tailwind …"** — switch to the slash form (`bg-black/50`).
  - **"Imported the first 5 of 7 pages — your plan's limit."** — the rest weren't imported. A higher plan imports more.

## 8. After the import

- Give the user the editor link and let them look.
- Publish only when the user asks — `publish_site` makes it live.
- For follow-up changes, use the PageHub tools on the new site id: `list_pages`, `list_site_files`, `edit_site_file`, `edit_site_files`.

## 9. Limits and privacy

- 50 MB zipped, 2,000 files. Inline files: 300 files, 3 MB.
- The Free plan imports up to 5 pages per site; paid plans import more.
- The zip goes to private storage, is never publicly reachable, and is deleted 7 days after the import finishes, so a failed import can be re-run without uploading again.
- The import is a deterministic conversion. No AI runs on the content.
