[@layout]: ../docs.layout.md

# Publish to Cloudflare

Publish a static PathMX build as a Cloudflare Worker with Static Assets.
Supported Plugin server slices can run alongside the exported pages.

---

## Prepare the site and account

Start with the [static export walkthrough](../start/publish.guide.md#static-export)
in an initialized project. You need a Cloudflare account with permission to deploy
Workers. For the custom-domain example below, use a domain in a zone managed by
that account and replace every `docs.example.com` with your chosen hostname.

Authorize Wrangler from a local terminal, then confirm the selected account:

```sh
bunx wrangler@4.125.0 login
bunx wrangler@4.125.0 whoami
```

This is the Wrangler version used by the installed adapter. Login opens the
browser's OAuth flow; `whoami` identifies the account. For headless use, provide
`CLOUDFLARE_API_TOKEN` through your CI secret environment and configure `accountId`.
See [Wrangler authentication](https://developers.cloudflare.com/workers/wrangler/commands/general/#login).
Keep credentials out of Plugin files and version control.

---

## Install

```sh
bun add @pathmx/cloudflare@beta
```

Configure the publication in a repository Plugin:

```ts
// plugins/cloudflare.plugin.ts
import { CloudflarePlugin } from "@pathmx/cloudflare"

export default CloudflarePlugin({
  worker: "my-pathmx-site",
  origin: "https://docs.example.com",
  domain: "docs.example.com",
})
```

Set `accountId` when local Cloudflare credentials can access more than one
account. Use `basePath` when PathMX should live below an origin prefix such as
`/course-guide`.

Custom Domains are the default. To attach an exact Worker route in an existing
zone instead:

```ts
export default CloudflarePlugin({
  worker: "my-pathmx-site",
  origin: "https://docs.example.com",
  domain: "docs.example.com",
  routing: { type: "route", zone: "example.com" },
})
```

---

## Publish

Build the local managed stage without contacting the deployment service:

```sh
bun run pmx cloudflare publish
```

Rebuild the local managed stage and ask Wrangler to validate it without deploying:

```sh
bun run pmx cloudflare publish --dry-run
```

The stage lives at `.pathmx/publish/cloudflare/`, with `site/`, routing data,
Wrangler configuration, and a receipt. The commands print `Staged N routes…` or
`Validated N routes…`. **Dry-run does write the local stage**; it does not change
the deployed Worker.

---

## Deploy and verify

Deploy after validation succeeds:

```sh
bun run pmx cloudflare publish --deploy
```

The adapter runs Wrangler's account check before deployment. A successful command
prints `Deployed N routes…`. Open your configured HTTPS origin, follow a deep
page link, reload it, and fetch its `.md` alternate. Confirm the content and assets
match the staged site rather than treating a successful upload as page acceptance.

If authentication fails, run `whoami` again and log in or correct the CI token's
account/permissions. If the domain cannot be attached, verify its zone and routing
choice. The local stage remains available for inspection; rerun validation after
correcting configuration. This static publication does not provision a writable
PathMX account server.


---

## Add Analytics to static pages

Install and enable the ordinary Analytics Plugin:

```sh
bun run pmx plugins add analytics
```

Then select its server slice in the existing Cloudflare Plugin:

```ts
import { CloudflarePlugin } from "@pathmx/cloudflare"

export default CloudflarePlugin({
  worker: "my-pathmx-site",
  origin: "https://docs.example.com",
  domain: "docs.example.com",
  servers: ["analytics"],
})
```

`publish --dry-run` validates the static pages, receiver bundle, and SQLite-backed
Durable Object binding. `publish --deploy` creates the binding and applies the
host migration. Each selected Plugin owns one named database instance, so a
redeployment retains its observations. The adapter rejects uninstalled Plugins,
Plugins without a `publishServer` hook, duplicate IDs, and unsupported mount paths.
Published servers currently require an origin-root deployment (`basePath` omitted).
Keep the server list and its order stable after first deployment; changing Durable
Object classes requires an explicit Cloudflare migration plan. Never delete a
namespace as part of a routine deployment or rollback.

For private CLI reports and exports, generate a random secret of at least 32
characters and install it using Wrangler's private input prompt:

```sh
bunx wrangler@4.125.0 secret put PATHMX_INSPECT_TOKEN --config .pathmx/publish/cloudflare/wrangler.jsonc
```

Supply that same secret as `PATHMX_INSPECT_TOKEN` when using the
[Analytics remote commands](./analytics.reference#report-from-a-static-cloudflare-site).
It grants report/export access, not a public statistics API or arbitrary SQL.
Missing reporting credentials fail closed without disabling page collection.
Missing storage bindings or unavailable storage return 503 on the collector while
static pages remain independently available.

SQLite state and secrets belong to Cloudflare, not the managed publication stage.
Preserve the namespace when changing builds, and keep a successful Analytics
export plus recovery bookmark before administrative recovery. For local Wrangler
checks, place `--persist-to` storage outside `.pathmx/publish/cloudflare/`; staging
replaces that whole generated directory.
