Automating Ghost Routes & Redirects Deployment

Ghost clearly documents how to automate theme deployment with GitHub Actions, but deploying routes & redirects automatically is not as straightforward.

This lack of straightforwardness is quite unfortunate in my view, particularly because custom routes are coupled to your theme; and redirects are somewhat coupled to routes since they both deal with the URLs your site can respond to.

Routes and redirects can (officially) only be deployed via the Ghost admin user interface. The "download-modify-upload" workflow is cumbersome and means you can't (easily) store these critical files in source control.

Even if you try to upload to the relevant endpoints with a JWT valid for the rest of the admin API, those endpoints will reject the upload. You must use a cookie to authenticate for these uploads.

To automate deployment of routes and redirects with GitHub Actions, then, you'll need to set two new secrets for your repository: GHOST_CI_USER and GHOST_CI_PASSWORD, corresponding to the login credentials for a user with admin privileges on the site. (If you haven't set GHOST_ADMIN_API_URL per the theme deployment instructions, do that too.)

Then, add two new jobs to the GitHub Actions workflow:

  deploy-routes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy Routes
        run: |
          mkdir -p ${{ runner.temp }}/ghost
          curl -c ${{ runner.temp }}/ghost/cookie.txt \
            -d 'username=${{ secrets.GHOST_CI_USER }}' \
            -d 'password=${{ secrets.GHOST_CI_PASSWORD }}' \
            -H "Origin: ${{ secrets.GHOST_ADMIN_API_URL }}" \
            -H "Accept-Version: v3.0" \
            ${{ secrets.GHOST_ADMIN_API_URL }}/ghost/api/admin/session/
          curl -b ${{ runner.temp }}/ghost/cookie.txt \
            -F "routes=@./routes.yaml" \
            -H "Accept-Version: v3.0" \
            -H "Origin: ${{ secrets.GHOST_ADMIN_API_URL }}" \
            ${{ secrets.GHOST_ADMIN_API_URL }}/ghost/api/admin/settings/routes/yaml/
  
  deploy-redirects:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy Redirects
        run: |
          mkdir -p ${{ runner.temp }}/ghost
          curl -c ${{ runner.temp }}/ghost/cookie.txt \
            -d 'username=${{ secrets.GHOST_CI_USER }}' \
            -d 'password=${{ secrets.GHOST_CI_PASSWORD }}' \
            -H "Origin: ${{ secrets.GHOST_ADMIN_API_URL }}" \
            -H "Accept-Version: v3.0" \
            ${{ secrets.GHOST_ADMIN_API_URL }}/ghost/api/admin/session/
          curl -b ${{ runner.temp }}/ghost/cookie.txt \
            -F "redirects=@./redirects.yaml" \
            -H "Accept-Version: v3.0" \
            -H "Origin: ${{ secrets.GHOST_ADMIN_API_URL }}" \
            ${{ secrets.GHOST_ADMIN_API_URL }}/ghost/api/admin/redirects/upload/

Strictly speaking, you could have a single job upload both files; I just split it into two jobs here so a failure uploading one won't affect the other.

You can see this in practice in the GitHub repository for this site; here's the commit where I introduced routes & redirects deployment.