Capture a website screenshot in GitHub Actions.

Add one workflow step to capture a public webpage as PNG or JPEG, then keep the image as a run artifact. The browser runs behind the Latchshot API, so the runner does not install or cache Chromium.

Public pages only · MIT-licensed Action · Updated July 20, 2026

Copy the complete workflow.

This manually triggered workflow asks for a URL and whether the page needs a bounded lazy-content sweep, then uploads the PNG for 14 days. The Action exposes the final path, render time, quota remaining, navigation state, and scroll state as step outputs.

.github/workflows/screenshot.ymlComplete workflow
name: Capture website screenshot

on:
  workflow_dispatch:
    inputs:
      url:
        description: Public webpage URL
        required: true
        default: https://example.com
      full_page:
        description: Capture the bounded full page
        required: true
        type: boolean
        default: false
      scroll_page:
        description: Activate lazy content; requires full_page
        required: true
        type: boolean
        default: false

permissions:
  contents: read

jobs:
  capture:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Capture webpage
        id: screenshot
        uses: BaiqingL/latchshot-action@v1
        with:
          api_key: ${{ secrets.LATCHSHOT_API_KEY }}
          url: ${{ inputs.url }}
          output: webpage.png
          width: "1440"
          height: "900"
          full_page: ${{ inputs.full_page }}
          scroll_page: ${{ inputs.scroll_page }}

      - name: Upload screenshot
        uses: actions/upload-artifact@v4
        with:
          name: webpage-capture
          path: ${{ steps.screenshot.outputs.path }}
          if-no-files-found: error
          retention-days: 14

No checkout step is required when the workflow only creates the screenshot artifact. Pin to the moving major tag, @v1, for compatible updates or to a full release tag when the workflow requires an immutable dependency.

Need a key for the first run?

The Free plan returns a key with 100 successful renders each UTC calendar month. No credit card or approval queue is required.

Store the API key as an Actions secret.

  1. Open the target repository on GitHub.
  2. Choose Settings → Secrets and variables → Actions.
  3. Create a repository secret named LATCHSHOT_API_KEY.
  4. Paste the Free or paid key once, then run the workflow from the Actions tab.

The workflow passes the secret through the Action input and into an HTTPS authorization header. Do not put the key in the URL, workflow source, artifact name, or job summary. GitHub does not pass repository secrets to workflows triggered from forks.

Keep the artifact and its diagnostics together.

A successful step writes the requested image and exposes five outputs. Record them in a later step when the workflow needs evidence about how the capture completed.

OutputMeaningExample
pathRequested output pathwebpage.png
render_msServer-side render duration1842
quota_remainingSuccessful renders left this month87
navigationComplete navigation or usable timed-out fallbackcomplete
scrollBounded sweep completed or remained offcomplete

Only a successful render consumes direct-plan quota. A failed step does not produce an artifact, and if-no-files-found: error prevents a green workflow with missing evidence.

Reveal lazy-loaded sections before the artifact.

A full-page frame does not automatically change the page's scroll state. When lower sections appear only after movement, request both options and require the scroll diagnostic to be complete.

Capture stepBounded lazy-content sweep
- name: Capture lazy full page
  id: screenshot
  uses: BaiqingL/latchshot-action@v1
  with:
    api_key: ${{ secrets.LATCHSHOT_API_KEY }}
    url: https://example.com/catalog
    output: catalog.png
    full_page: true
    scroll_page: true

- name: Require completed sweep
  env:
    SCROLL: ${{ steps.screenshot.outputs.scroll }}
  run: test "$SCROLL" = "complete"

The sweep is deterministic and returns to the top before capture. It stops at 48 steps, five seconds, 20,000 CSS pixels high, or 5,000 pixels wide. An unsafe or unfinished sweep fails explicitly instead of labeling a partial page complete.

Keep custom browser work in a browser.

The Action does not accept scripts, selectors, clicks, custom speed or direction, sessions, private targets, or multi-step interaction. The lazy-loading guide shows real production before/after evidence and the provider boundary.

Schedule a fixed public page.

Replace the manual trigger with a UTC cron schedule and a fixed URL for recurring documentation, status-page, or release evidence. Scheduled workflows run from the default branch and GitHub may delay them during busy periods.

Workflow excerptWeekdays at 06:17 UTC
on:
  schedule:
    - cron: "17 6 * * 1-5"

# Keep the capture step from the complete workflow.
with:
  api_key: ${{ secrets.LATCHSHOT_API_KEY }}
  url: https://example.com/status
  output: status-page.png

For recurring jobs, check quota_remaining, retain the previous successful artifact outside GitHub when history matters, and avoid retrying permanent 400 or 401 responses.

Choose hosted rendering when the page is public.

Moving Chromium out of the runner is a specific tradeoff, not a universal replacement for browser automation.

DecisionHosted Latchshot ActionBrowser in your runner
Browser setupNo install or cache in the workflowInstall, cache, or maintain an image
Reachable pagesPublic HTTP/HTTPS onlyCan reach the runner's private environment
CustomizationBounded capture inputs and one lazy sweepArbitrary scripts and sessions
Operating modelExternal API key and quotaRunner minutes and browser upkeep

Use a browser in your own runner for authenticated previews, private deployments, multi-step interaction, cookies, or arbitrary page scripts. Use the hosted Action when the input is a public URL and the desired output is a bounded screenshot artifact.

Make failures predictable.

  • Targets must be public HTTP or HTTPS pages on ports 80 or 443.
  • Private, loopback, link-local, special-use, and mixed public/private DNS answers are rejected.
  • Viewport dimensions, full-page height, delay, timeout, retries, concurrency, and queue depth are bounded.
  • scroll_page requires full_page and never accepts custom scroll logic or page actions.
  • Cookies, login sessions, arbitrary scripts, CAPTCHA solving, proxy rotation, and anti-bot bypass are not supported.
  • The Action retries transient transport failures twice and treats invalid requests or credentials as permanent errors.

The public Action source is MIT licensed and its validation workflow is visible. The hosted renderer is a separate metered service. Read the benchmark method and beta boundaries before making the capture a release gate.

Start with 100 successful artifacts.

The Free plan renews to 100 successful renders each UTC calendar month. Launch is $19 for 2,500 per month, Build is $49 for 12,000, and Scale is $149 for 50,000. Automatic overages are disabled, and failed direct renders do not consume quota.

The current supported-page benchmark is 197/200 with 2.42-second p50 and 7.68-second p95 end to end. It is engineering evidence, not an SLA.

Need to prove a lazy full page?

Compare the exact production off/on artifacts, limits, diagnostics, and provider choices before changing a release workflow.

See the proof