An opinionated fork of the official Cloudflare Pages Action.
A GitHub Action for creating Cloudflare Pages deployments, using Direct Upload with Wrangler.
- ✅ Generated build summaries
- ✅ Deploy multiple sites from a single monorepo
- ✅ Builds site previews of PRs from forked repositories, a known issue with official solutions
- ✅ GitHub Deployments on PRs from forks
Important
This action entirely replaces the Cloudflare Pages GitHub integration. Before continuing, you should disable the automatic builds made by Cloudflare for the repository you are applying this action to.
- Locate your Cloudflare account ID.
- Generate an API token.
- Add the Cloudflare account ID and API token as secrets to your GitHub repository.
- Create a
.github/workflows/publish.yml
file in your repository:
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Publish to Cloudflare Pages
steps:
- name: Checkout
uses: actions/checkout@v3
# Run a build step here if your project requires one
# ...
- name: Publish to Cloudflare Pages
uses: AdrianGonz97/refined-cf-pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
githubToken: ${{ secrets.GITHUB_TOKEN }}
projectName: YOUR_PROJECT_NAME
directory: YOUR_BUILD_OUTPUT_DIRECTORY
# Optional: Supply a deployment name if you want to have GitHub Deployments triggered
deploymentName: Production
# Optional: Switch what branch you are publishing to.
# By default, this will be the branch which triggered this workflow
branch: main
# Optional: Change the working directory
workingDirectory: my-site
# Optional: Change the Wrangler version, allows you to point to a specific version or a tag such as `beta`
# Specifying an empty string ('') will omit the version specifier.
wranglerVersion: '3'
- Replace
YOUR_PROJECT_NAME
andYOUR_BUILD_OUTPUT_DIRECTORY
with the appropriate values to your Pages project.
And you're ready to go!
Tip
Be sure to check out the Enabling PR Previews from Forks section if you're interested in enabling this particular feature.
To find your account ID, log in to the Cloudflare dashboard > select your zone in Account Home > find your account ID in Overview under API on the right-side menu. If you have not added a zone, add one by selecting Add site.
If you do not have a zone registered to your account, you can also get your account ID from the pages.dev
URL. E.g: https://dash.cloudflare.com/<ACCOUNT_ID>/pages
To generate an API token:
- Log in to the Cloudflare dashboard.
- Select My Profile from the dropdown menu of your user icon on the top right of your dashboard.
- Select API Tokens > Create Token.
- Under Custom Token, select Get started.
- Name your API Token in the Token name field.
- Under Permissions, select Account, Cloudflare Pages and Edit:
- Select Continue to summary > Create Token.
- Go to your project’s repository in GitHub.
- Under your repository’s name, select Settings.
- Select Secrets and variables > Actions > New repository secret.
- Create a secret and put
CLOUDFLARE_ACCOUNT_ID
as the name with the value being your Cloudflare account ID. - Create another secret and put
CLOUDFLARE_API_TOKEN
as the name with the value being your Cloudflare API token.
If you have already connected your repository to the Cloudflare Pages GitHub integration, you'll need to disable it.
- Go to your project’s repository in GitHub.
- Under your repository’s name, select Settings.
- Select GitHub Apps, and next to Cloudflare Pages, select Configure
- Under Repository access, select Only select repositories, and remove your repository.
If your site does not use/have any runtime secrets in your preview environment on Cloudflare, then it should be fine to use as-is. We use this method in shadcn-svelte and its implementation can be found here.
Example: Preview Deployment with NO RUNTIME/BUILD-TIME SECRETS
First we'll build the project in an unprivileged environment where secrets are not exposed. This allows use to safely run untrusted code:
# build-preview.yml
name: Build Preview Deployment
on:
pull_request:
types: [opened, synchronize]
jobs:
build-preview:
runs-on: ubuntu-latest
name: Build Preview Site and Upload Build Artifact
steps:
- name: Checkout
uses: actions/checkout@v4
# Run your install/build steps here
# ...
# Build example
- name: Build site
run: pnpm build
env:
# if you need environment variables that are _NOT secrets_, apply them here during build
# using GH Action variables
SOME_ENV_VAR: ${{ vars.SOME_ENV_VAR }}
# Uploads the build directory as a workflow artifact
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: preview-build
path: YOUR_BUILD_OUTPUT_DIRECTORY
Then we'll deploy the project to Cloudflare in a privileged environment where we can safely use secrets (i.e. your cloudflare credentials):
# deploy-preview.yml
name: Upload Preview Deployment
on:
workflow_run:
workflows: ['Build Preview Deployment']
types:
- completed
permissions:
actions: read
deployments: write
contents: read
pull-requests: write
jobs:
deploy-preview:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
name: Deploy Preview to Cloudflare Pages
steps:
# Downloads the build directory from the previous workflow
- name: Download build artifact
uses: actions/download-artifact@v4
id: preview-build-artifact
with:
name: preview-build
path: build
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Deploy to Cloudflare Pages
uses: AdrianGonz97/refined-cf-pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
githubToken: ${{ secrets.GITHUB_TOKEN }}
projectName: YOUR_PROJECT_NAME
deploymentName: Preview
directory: ${{ steps.preview-build-artifact.outputs.download-path }}
If your project does use runtime secrets, then the deployment job can be fitted with an environment
field that requires manual approval before each deployment.
Manual approval using environments needs to be setup at the repo level, as described in this Melt UI PR under the "Make the Preview
environment protected" step.
Example: Preview Deployment WITH RUNTIME SECRETS
# build-preview.yml
name: Build Preview Deployment
on:
pull_request:
types: [opened, synchronize]
jobs:
build-preview:
environment: Preview # The name of the environment that requires manual approval before each deployment
runs-on: ubuntu-latest
name: Build Preview Site and Upload Build Artifact
steps:
- name: Checkout
uses: actions/checkout@v4
# Run your install/build steps here
# ...
# Build example
- name: Build site
run: pnpm build
env:
# If you need environment variables that are **NOT secrets**,
# apply them here during build using GH Action Variables
SOME_ENV_VAR: ${{ vars.SOME_ENV_VAR }}
# Uploads the build directory as a workflow artifact
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: preview-build
path: YOUR_BUILD_OUTPUT_DIRECTORY
# deploy-preview.yml
name: Upload Preview Deployment
on:
workflow_run:
workflows: ['Build Preview Deployment']
types:
- completed
permissions:
actions: read
deployments: write
contents: read
pull-requests: write
jobs:
deploy-preview:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
name: Deploy Preview to Cloudflare Pages
steps:
# Downloads the build directory from the previous workflow
- name: Download build artifact
uses: actions/download-artifact@v4
id: preview-build-artifact
with:
name: preview-build
path: build
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Deploy to Cloudflare Pages
uses: AdrianGonz97/refined-cf-pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
githubToken: ${{ secrets.GITHUB_TOKEN }}
projectName: YOUR_PROJECT_NAME
deploymentName: Preview
directory: ${{ steps.preview-build-artifact.outputs.download-path }}
In the off chance that you need build-time secrets (which you should try to avoid for previews), then you'll need to use the pull_request_target
event with manual approvals before each deployment.
Important
With this method, each PR needs to be reviewed thoroughly before deployment approval to ensure that secrets are not being exposed via malicious code. Use with discretion.
Example: Preview Deployment WITH BUILD-TIME SECRETS
name: Preview Deployment
on:
pull_request_target:
jobs:
deploy-preview:
environment: Preview # The name of the environment that requires manual approval before each deployment
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
deployments: write
name: Deploy Preview to Cloudflare Pages
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
# Run your install/build steps here
# ...
# Build example
- name: Build site
run: pnpm build
env:
SOME_SECRET: ${{ secrets.SOME_SECRET }} # Uses some secret during build
- name: Deploy to Cloudflare Pages
uses: AdrianGonz97/refined-cf-pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
githubToken: ${{ secrets.GITHUB_TOKEN }}
projectName: YOUR_PROJECT_NAME
directory: YOUR_BUILD_OUTPUT_DIRECTORY
deploymentName: Preview
The branch name is used by Cloudflare Pages to determine if the deployment is production or preview. Read more about git branch build controls.
If you are in a Git workspace, Wrangler will automatically pull the branch information for you. You can override this
manually by adding the argument branch: YOUR_BRANCH_NAME
.
By default Wrangler will run in the root package directory. If your app lives in a monorepo and you want to run Wrangler from its directory, add workingDirectory: YOUR_PACKAGE_DIRECTORY
.
Name | Description |
---|---|
id |
The ID of the pages deployment |
url |
The URL of the pages deployment |
alias |
The alias, if it exists, otherwise the deployment URL |
environment |
The environment that was deployed to |