Back to journal
·5 min readPages CMSGitHub ActionsCI/CDDebuggingTIL

TIL: Pages CMS Actions Need a Matching GitHub Actions Input

I hit a small but annoying issue while connecting Pages CMS with GitHub Actions. Pages CMS could trigger the workflow, but GitHub rejected the request because the workflow didn’t define the payload input that Pages CMS sends when it runs a manual action.

ShareCopy failed

I wanted a small change in the way my content workflow works.

When I edit a post from Pages CMS, I don’t always want the site to deploy right away. Sometimes I just want to save a draft-like change, review the generated Markdown, or group a few small edits before publishing.

The default flow felt a bit too automatic for that.

So the goal was simple: Pages CMS should still commit content changes to the repository, but the deployment should happen only when I manually click a Deploy site action inside the Pages CMS dashboard.

In theory, that sounds like a clean setup. Pages CMS handles content. GitHub Actions handles the build. Cloudflare handles the deployment.

In practice, there were a few small config details that made the setup fail.

The first part was the Pages CMS configuration. The content collections were already defined in .pages.yml, including blog posts, portfolio items, media uploads, and site settings.

The important change was in the commit templates:

settings:
  commit:
    templates:
      create: "Create {path} (via Pages CMS) [skip ci]"
      update: "Update {path} (via Pages CMS) [skip ci]"
      delete: "Delete {path} (via Pages CMS) [skip ci]"
      rename: "Rename {oldPath} to {newPath} (via Pages CMS) [skip ci]"

That [skip ci] part is doing the boring but useful work.

Pages CMS still creates commits when content changes. The difference is that these commits don’t trigger the normal CI workflow. That means saving a post in the CMS won’t immediately deploy the site.

After that, I added a manual action at the root of .pages.yml:

actions:
  - name: deploy-site
    label: Deploy site
    workflow: deploy-cloudflare.yml
    ref: main
    cancelable: false
    confirm:
      title: Deploy site?
      message: This will publish the latest committed content to Cloudflare Pages.
      button: Deploy

This action shows a Deploy site button in the Pages CMS dashboard. When clicked, it asks for confirmation and then triggers the GitHub Actions workflow called deploy-cloudflare.yml.

That part matters. The workflow value must match the actual file name inside .github/workflows/.

So if the workflow file is here:

.github/workflows/deploy-cloudflare.yml

Then the Pages CMS action needs this:

workflow: deploy-cloudflare.yml

Once the action appeared in the dashboard, I thought the setup was done.

It wasn’t.

When I clicked the action, GitHub returned this error:

Unexpected inputs provided: ["payload"]

At first, this was confusing. I didn’t add a payload field to the Pages CMS action. There was no custom payload in my .pages.yml file either.

The missing detail is that Pages CMS sends a payload input when it triggers a GitHub Actions workflow. GitHub Actions is strict here. If a workflow_dispatch request sends an input, that input must be declared in the workflow file.

So the fix was inside the GitHub Actions workflow, not inside Pages CMS.

The workflow needed to support both normal pushes to main and manual runs from Pages CMS:

name: Deploy Worker
run-name: Deploy site from Pages CMS

on:
  push:
    branches:
      - main
  workflow_dispatch:
    inputs:
      payload:
        description: Pages CMS payload as JSON
        required: false
        type: string

permissions:
  contents: read
  deployments: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
      CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

    steps:
      - uses: actions/checkout@v6

      - uses: pnpm/action-setup@v4
        with:
          version: 10.33.2

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm build
        env:
          VITE_GA_MEASUREMENT_ID: ${{ secrets.VITE_GA_MEASUREMENT_ID }}
          VITE_EMAILJS_SERVICE_ID: ${{ secrets.VITE_EMAILJS_SERVICE_ID }}
          VITE_EMAILJS_TEMPLATE_ID: ${{ secrets.VITE_EMAILJS_TEMPLATE_ID }}
          VITE_EMAILJS_PUBLIC_KEY: ${{ secrets.VITE_EMAILJS_PUBLIC_KEY }}

      - name: Deploy Worker
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ env.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ env.CLOUDFLARE_ACCOUNT_ID }}
          packageManager: pnpm
          command: deploy

The small but important part is this:

workflow_dispatch:
  inputs:
    payload:
      description: Pages CMS payload as JSON
      required: false
      type: string

Without it, GitHub receives the request from Pages CMS and rejects it before the workflow even starts.

The slightly odd thing is that the workflow doesn’t need to use the payload value. It just needs to accept it. That’s enough to make the dispatch valid.

After that change, the full flow worked:

  1. I edit a blog post in Pages CMS.
  2. Pages CMS commits the Markdown file with [skip ci].
  3. GitHub Actions does not deploy on that content commit.
  4. I click Deploy site in Pages CMS when I’m ready.
  5. Pages CMS triggers deploy-cloudflare.yml.
  6. GitHub Actions builds the project and deploys it with Wrangler.

There was one more weird UI moment. An older Pages CMS action stayed stuck on a message like:

Waiting for GitHub to start "Deploy site"…

But GitHub Actions had already run and finished correctly for the newer attempt.

In that case, the old Pages CMS status was just noise from the broken run. GitHub Actions was the source of truth. Once the workflow accepted the payload input, new deploy actions worked as expected.

The main takeaway is small but useful: when Pages CMS triggers a GitHub workflow, it sends a payload.

GitHub doesn’t ignore unknown inputs.

So the workflow has to define that input, even if the job never reads it.

It’s one of those tiny CI/CD issues that feels bigger than it is while you’re debugging it. The final fix was just a few lines of YAML.

ShareCopy failed