Preview Deploys
Per-PR preview deployments on Cloudflare Pages
How Preview Deploys Work
When you deploy with a non-main branch name, Cloudflare Pages creates a preview deployment with a unique URL:
https://<branch>.<account>-<project>.pages.dev For PR-based previews, use the PR number as the branch:
npx wrangler@4 pages deploy deploy \
--project-name=my-site \
--branch="pr-${PR_NUMBER}" \
--commit-hash="${GITHUB_SHA}" \
--commit-message="Preview: PR #${PR_NUMBER}"The preview URL becomes:
https://pr-42.takazudo-my-site.pages.dev Preview URL Pattern
The URL format is:
https://<branch-slug>.<account-slug>-<project-name>.pages.dev Where:
<branch-slug>is the--branchvalue (sanitized)<account-slug>is derived from your Cloudflare account<project-name>is the--project-namevalue
Note
The <account-slug> part is auto-generated by Cloudflare. Check your Pages dashboard to see the exact subdomain format for your account.
Posting Preview URL to PR
Use actions/github-script to post or update a comment with the preview URL:
- name: Comment preview URL on PR
uses: actions/github-script@v8
env:
DEPLOY_URL: ${{ steps.cf-deploy.outputs.deploy_url }}
with:
script: |
const deployUrl = process.env.DEPLOY_URL;
const prNumber = context.issue.number;
const marker = '<!-- cf-preview-pr -->';
const body = [
marker,
`## Cloudflare Pages Preview`,
'',
`Preview deployment is ready.`,
'',
`| | URL |`,
`|---|---|`,
`| Preview | ${deployUrl} |`,
'',
`Built from commit: \`${context.sha.substring(0, 7)}\``,
].join('\n');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}The HTML comment marker (<!-- cf-preview-pr -->) ensures subsequent deploys update the existing comment rather than creating new ones.
Required Permissions
The workflow needs pull-requests: write permission:
permissions:
contents: read
pull-requests: writeFork PRs Don't Get Previews
GitHub does not expose repository secrets to workflows triggered by a pull request from a fork. If your preview-deploy workflow runs on the pull_request event and the PR comes from a fork, CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID resolve to empty strings and the deploy step fails with an authentication error.
Note
This is expected GitHub Actions behavior, not a bug in this setup. Preview deploys only work for branches pushed to this repository (same-repo PRs). Fork PRs from external contributors will not get a preview URL unless you switch to pull_request_target (which runs with repo secrets against the fork's code — treat this as a security decision, not a quick fix) or split the workflow into a build-on-fork / deploy-after-approval pattern.