Keeping documentation screenshots current requires treating them as infrastructure, not content. This guide covers five methods — from manual audits to fully automated CI/CD capture — with working code, a cost comparison, and a decision framework based on your screenshot count and release cadence.
Step 1: audit your screenshot inventory
Before choosing a method, you need to know the scale of the problem. Every team I have worked with overestimates how current their screenshots are and underestimates the total count.
Run this audit:
# Count all image files in your docs
find docs/ -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.webp" \) | wc -l
# List images with last-modified dates, sorted oldest first
find docs/ -type f -name "*.png" -printf '%T+ %p\n' | sortThen calculate your staleness rate by comparing each screenshot against the live product. A screenshot is "stale" if any visible UI element — button label, layout, color, navigation — differs from the current production interface. On a first audit, most teams find 30–50% staleness.
Finally, calculate your visual debt balance: stale screenshots × average minutes to manually update ÷ 60 = hours of visual debt. At $75/hour fully loaded, that is your cost to reach zero.
When I audited five B2B SaaS documentation sites during early Reshot development, the average screenshot count was 87, with a staleness rate of 38%. That translates to 33 stale screenshots, or roughly 6.6 hours of manual update work per audit cycle.
The 5 methods for keeping screenshots current
Method 1: scheduled manual audits
The simplest approach. Add a recurring calendar event — weekly, biweekly, or per release — and manually walk through every documentation page, comparing screenshots against the live product. Recapture any that are stale.
- Works for: teams with fewer than 20 screenshots and monthly release cadences
- Breaks at: 20+ screenshots or biweekly releases — the time cost exceeds what anyone will actually spend
- Cost per cycle: 15–25 screenshots × 12 minutes = 3–5 hours
The core problem: manual audits require human initiative every cycle. They are the first task that gets dropped when a deadline tightens.
Method 2: batch capture scripts
Write a Node.js or Python script that opens a headless browser, navigates to a list of URLs, and saves screenshots to your docs image directory. Run the script before each release.
import { chromium } from 'playwright';
import { readFileSync } from 'fs';
const targets = JSON.parse(readFileSync('screenshot-targets.json', 'utf-8'));
const browser = await chromium.launch();
for (const target of targets) {
const page = await browser.newPage();
await page.setViewportSize(target.viewport);
await page.goto(target.url, { waitUntil: 'networkidle' });
if (target.waitFor) {
await page.waitForSelector(target.waitFor);
}
const element = target.selector
? await page.locator(target.selector)
: page;
await element.screenshot({ path: `docs/images/${target.name}.png` });
await page.close();
}
await browser.close();
console.log(`Captured ${targets.length} screenshots`);- Works for: 20–50 screenshots with stable selectors
- Breaks at: scripts become their own maintenance burden — selectors change on UI refactors, waits need tuning, and no one updates the target list when new docs pages are added
- Cost per cycle: ~2 minutes of CI time + ongoing script maintenance
Method 3: open-source CLI tools
Tools like shot-scraper (Python, by Simon Willison) and Heroshot (Node.js) provide a config-driven abstraction over Playwright. You define targets in YAML, and the CLI handles browser lifecycle, viewport iteration, and file output.
Kong's engineering team documented their use of shot-scraper in a docs-as-code pipeline. Their approach: screenshot definitions live alongside documentation source files in the same repo, and capture runs as part of the docs build.
# shots.yml
- url: https://app.example.com/dashboard
output: docs/images/dashboard.png
width: 1280
height: 800
selector: ".main-content"
wait: 2000
- url: https://app.example.com/settings
output: docs/images/settings.png
width: 1280
height: 800- Works for: 50–100 screenshots, teams comfortable with CLI tooling
- Breaks at: no hosted image URLs, no approval workflows, no freshness tracking — you still manage file storage and deployment yourself
- Cost per cycle: 1–3 minutes CI time, near-zero maintenance if selectors are stable
Method 4: end-to-end test integration
If your product already runs Playwright, Cypress, or TestCafe end-to-end tests, you can capture documentation screenshots within those test runs. This is the approach Camunda's engineering team pioneered — they extended their existing TestCafe E2E suite to take screenshots at the right moment and commit them to the docs repo.
Camunda reported that their user guide contained 94 screenshots, and that this approach reduced their per-release docs update from one to two days of manual work to an automated step in the test pipeline. They also added dynamic annotations — programmatically overlaying labels onto screenshots during the test, so the annotations update correctly when element positions change.
- Works for: teams with comprehensive E2E test coverage that maps closely to documentation scenarios
- Breaks at: docs scenarios and test scenarios diverge — a test verifies functionality, but a docs screenshot needs to show a specific UI state that may not align with any test path
- Cost per cycle: zero incremental cost if tests already run, but initial alignment effort is significant
Method 5: managed screenshot platform
Managed platforms like Reshot handle the full lifecycle: config-driven capture, headless browser execution, CDN hosting with stable URLs, approval workflows, and freshness tracking. The key difference from methods 1–4 is stable delivery — your docs pages embed a URL that always serves the latest capture, eliminating the need to commit files or redeploy docs.
- Works for: 25+ screenshots, teams that need hosted URLs, approval workflows, or freshness tracking
- Breaks at: cost-sensitive teams with very small screenshot counts may not need the overhead
- Cost per cycle: zero manual effort; platform handles capture, hosting, and cache invalidation
Full implementation: automated capture in GitHub Actions
Here is a complete, working GitHub Actions workflow that captures screenshots on every push to main and commits them back to the repo. This uses Method 2 (batch scripts) and can be adapted for any tool.
name: Update documentation screenshots
on:
push:
branches: [main]
schedule:
- cron: '0 8 * * 1' # Monday 8am UTC fallback
jobs:
capture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Playwright with system deps
run: |
npm init -y
npm install playwright
npx playwright install chromium --with-deps
- name: Run screenshot capture
run: node capture.mjs
env:
APP_URL: ${{ vars.APP_URL }}
APP_USERNAME: ${{ secrets.APP_USERNAME }}
APP_PASSWORD: ${{ secrets.APP_PASSWORD }}
- name: Check for changes
id: diff
run: |
git diff --name-only docs/images/ > /tmp/changed.txt
if [ -s /tmp/changed.txt ]; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
- name: Commit updated screenshots
if: steps.diff.outputs.changed == 'true'
run: |
git config user.name "screenshot-bot"
git config user.email "bot@reshot.dev"
git add docs/images/
git commit -m "docs: update screenshots [automated]"
git pushKey details in this workflow:
--with-depsinstalls system libraries (libgbm, libatk, libnss3) that Playwright's Chromium requires on Ubuntu. Without it, the browser fails to launch silently — the most common setup error.- Diff check before commit avoids empty commits when screenshots have not changed, keeping the git history clean.
- Schedule fallback catches any screenshots that were not updated by push-triggered runs — for example, if the product UI changed but the docs repo did not receive a push.
- Secrets for authentication — the
APP_USERNAMEandAPP_PASSWORDenvironment variables are used by the capture script to log into authenticated views. For a more robust approach to auth, see capturing screenshots behind login.
Tracking freshness over time
Automation solves the capture problem, but you still need visibility into whether your screenshots are current. A screenshot captured six months ago from a staging environment that has since diverged from production is automated but not fresh.
Track freshness with three data points per screenshot:
- Last captured — the timestamp of the most recent automated capture
- Last changed — the timestamp when the captured image actually differed from the previous version (detected via pixel hash comparison)
- Product version at capture — the release tag or commit SHA of the product when the screenshot was taken
A simple freshness check script:
#!/bin/bash
# Flag screenshots older than 30 days
echo "Screenshots not updated in 30+ days:"
find docs/images/ -name "*.png" -mtime +30 -printf ' %T+ %p\n' | sort
echo ""
echo "Total screenshots: $(find docs/images/ -name '*.png' | wc -l)"
echo "Stale (>30 days): $(find docs/images/ -name '*.png' -mtime +30 | wc -l)"For teams using a managed screenshot platform, freshness tracking is built in — the platform records every capture, diffs against previous versions, and flags screenshots that have not changed despite product updates.
Frequently asked questions
How often should documentation screenshots be updated? Screenshots should be updated on every release that changes the UI. For products releasing biweekly, that means biweekly screenshot refreshes. Manual processes cannot sustain this cadence — automation is the only way to keep screenshots current at modern release velocities without dedicating significant engineering time.
How many screenshots can one person maintain manually? Based on observed workflows, one person can manually maintain roughly 15–25 screenshots per release cycle before the time cost becomes unsustainable. At 10–15 minutes per screenshot including navigation, capture, cropping, annotation, and commit, 25 screenshots consumes 4–6 hours per release.
What tools automate documentation screenshot updates? The main options are Playwright or Puppeteer scripts for custom setups, shot-scraper for a Python CLI approach, Heroshot for an open-source Node CLI, and Reshot for a managed platform with CDN delivery and approval workflows.
Do I need to redeploy my docs site when screenshots update? With static-file approaches where screenshots are committed to your repo, yes — every screenshot update requires a docs rebuild and deploy. With CDN-based tools like Reshot, no — screenshots are served from URLs that always resolve to the latest capture, so docs pages never need redeployment to show current visuals.
How do I handle screenshots that require a logged-in state? All headless browser tools support authentication. The standard approach is to store credentials as CI/CD secrets and run a login flow before capture, though bypassing login with localhost capture is more reliable. See capturing screenshots behind login for working code.

