How to schedule a website screenshot on the first day of each month
Capture a website on the first day of every month with a hosted scheduler or a Playwright script and a scheduled workflow.
To schedule a website screenshot for the first day of each month, set up either a hosted screenshot service with a monthly schedule or a browser automation script that a scheduler runs on day 1. For a self-managed workflow, Playwright captures the image and a scheduler such as GitHub Actions triggers the script. Choose the time and timezone explicitly, decide whether to capture the viewport, full page, or one element, and save each result with a date in its filename.
Capture and scheduling are separate jobs in a self-managed setup: Playwright takes the screenshot, while your scheduler decides when the script runs. Hosted services can combine both, but check their current schedule controls, timezone behavior, plan limits, and storage options before relying on them. [Playwright screenshot guide](https://playwright.dev/docs/screenshots), [Site-Shot scheduling guide](https://site-shot.com/blog/how-to-automatically-take-a-screenshot-of-a-website-every-day/), [Add Screenshots](https://addscreenshots.com/)
Choose a monthly screenshot workflow
Use a hosted service if you prefer to configure a recurring job rather than operate browser dependencies and a scheduler. Use a self-managed workflow if you want the capture code and destination under your control.
| Decision | Hosted service | Self-managed workflow |
|---|---|---|
| Setup | Configure a capture job in the service; confirm current plan restrictions. | Maintain a capture script, scheduled run, browser dependencies, and output destination. |
| Scheduling | Check that monthly scheduling supports the first calendar day and your intended timezone. | Configure a scheduler for day 1 and verify its timezone and day-of-month behavior. |
| Capture scope | Check available viewport, full-page, and browser settings. | Playwright supports viewport, full-page, and element screenshots. |
| Storage and delivery | Check retention, export, and delivery options. | Choose and maintain your own destination. |
| Consistency | Check whether the service keeps browser settings stable. | Keep browser version, operating system, viewport, and other rendering conditions stable. |
Vendor schedule features and plan details can change. The reviewed Site-Shot and Add Screenshots pages describe recurring scheduling, including monthly options; verify their current service pages before choosing one. [Site-Shot guide](https://site-shot.com/blog/how-to-automatically-take-a-screenshot-of-a-website-every-day/), [Add Screenshots](https://addscreenshots.com/)
Set up a self-managed monthly capture with Playwright
This example uses Node.js and Playwright. It captures a full-page PNG and writes a dated file. The capture script itself does not schedule anything: a separate scheduler must run it on the first day of the month.
1. Install Playwright
npm init -y
npm install playwright
npx playwright install chromium
2. Create the capture script
Save as capture.mjs. Set TARGET_URL to the page you want to archive. The date in the filename uses UTC, so it remains unambiguous across environments.
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
const targetUrl = process.env.TARGET_URL ?? 'https://example.com';
const outputDir = process.env.OUTPUT_DIR ?? 'screenshots';
const width = Number(process.env.VIEWPORT_WIDTH ?? 1440);
const height = Number(process.env.VIEWPORT_HEIGHT ?? 1000);
if (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) {
throw new Error('Viewport dimensions must be positive numbers');
}
const date = new Date().toISOString().slice(0, 10);
const file = `${outputDir}/website-${date}.png`;
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width, height },
deviceScaleFactor: 1
});
const response = await page.goto(targetUrl, {
waitUntil: 'networkidle',
timeout: 60_000
});
if (!response || !response.ok()) {
throw new Error(`Navigation failed: ${response?.status() ?? 'no response'}`);
}
await page.screenshot({ path: file, fullPage: true, animations: 'disabled' });
console.log(`Saved ${file}`);
} finally {
await browser.close();
}
networkidle is not suitable for every site: analytics, streaming content, or long polling can keep a page active. If it times out, use waitUntil: 'domcontentloaded' and wait for a meaningful selector with page.waitForSelector(), or use a deliberate fixed delay for a known page. Avoid relying on a delay alone when page readiness can be detected directly.
3. Schedule the script
For GitHub Actions, save this as .github/workflows/monthly-screenshot.yml. GitHub Actions scheduled workflows use UTC cron schedules; confirm the current GitHub documentation for schedule behavior and any repository or plan constraints before depending on a run time. This example runs at 09:00 UTC on the first day of each month, installs Chromium, captures the page, and retains the generated file as a workflow artifact.
name: Monthly website screenshot
on:
schedule:
- cron: '0 9 1 * *'
workflow_dispatch:
jobs:
capture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: node capture.mjs
env:
TARGET_URL: https://example.com
OUTPUT_DIR: screenshots
- uses: actions/upload-artifact@v4
with:
name: monthly-website-screenshot
path: screenshots/*.png
retention-days: 90
Change the cron hour and minute to your desired UTC time. If you need a local-time schedule that observes daylight-saving changes, use a scheduler with an explicit timezone setting or convert the desired local time carefully; verify what happens when the local offset changes. GitHub Actions cron is only the trigger: it does not by itself guarantee permanent archive storage. Download artifacts or add a storage step appropriate for your retention needs.
4. Confirm the first run and output
- Run the workflow manually once and inspect the PNG for missing content, cookie overlays, and incorrect dimensions.
- Confirm the scheduled trigger is enabled and points to the expected branch.
- Check the scheduler’s timezone and how it handles the first day of the month.
- Confirm the output destination retains files long enough and filenames do not overwrite prior months.
Capture only the part of the page you need
Playwright’s page screenshot is the visible viewport by default. Use fullPage: true for all scrollable content. Use a locator screenshot when the archive should contain only one component, such as a pricing table.
// Viewport only
await page.screenshot({ path: 'viewport.png' });
// Full scrollable page
await page.screenshot({ path: 'full-page.png', fullPage: true });
// One element by CSS selector
const pricing = page.locator('.pricing-table');
await pricing.screenshot({ path: 'pricing-table.png' });
For a locator capture, wait for it to exist and be visible first. If the selector matches multiple elements, narrow it so the intended one is captured. Full-page screenshots can be very tall, and some pages load images or content only after scrolling; inspect the result and add page-specific readiness or scrolling logic if needed. [Playwright screenshot guide](https://playwright.dev/docs/screenshots)
Keep monthly images comparable
When the purpose is visual comparison, hold capture conditions steady: use the same browser engine and version, operating system, viewport dimensions, device scale factor, locale, color scheme, and capture scope. Dynamic content such as dates, rotating banners, personalized recommendations, and ads can change even when the underlying site has not.
Playwright warns that rendering can vary by OS, browser version, settings, hardware, power source, and headless mode. Its guidance for consistent screenshots is to run in the same environment as the baseline. [Playwright visual comparisons](https://playwright.dev/docs/test-snapshots)
Do not treat Playwright’s visual regression assertions as a monthly archive system: those assertions compare output with a baseline and require the Playwright test runner. A dated capture workflow is a separate design. [Playwright visual comparisons](https://playwright.dev/docs/test-snapshots)
Hosted scheduling checklist
If you choose a hosted scheduler, configure the job around these questions. Vendor interfaces and plan rules change, so confirm them in the current product documentation or dashboard rather than relying on an old click-by-click guide.
- Target: Can the job capture the exact URL, including query parameters or authenticated content if required?
- Calendar: Does the recurrence mean the first calendar day of each month? Which timezone controls the scheduled time?
- Capture: Can you set viewport dimensions, full-page mode, or a CSS element selector?
- Readiness: Can the service wait for a selector, delay, or page readiness condition?
- Output: Where are screenshots stored, how long are they retained, and can the job deliver them elsewhere?
- Failure handling: Are failed runs visible, and can you tell a missing screenshot from a successful capture?
- Cost: Check current plan limits, run frequency, storage, and any charges tied to retries or delivery.
Site-Shot’s guide describes configuring scheduled captures in a browser with results stored in a library. Add Screenshots describes monthly frequencies and options including reusable browser settings and delivery destinations. These are vendor descriptions; confirm current availability and plan limits directly. [Site-Shot guide](https://site-shot.com/blog/how-to-automatically-take-a-screenshot-of-a-website-every-day/), [Add Screenshots](https://addscreenshots.com/)
Reliability, performance, and cost
Reliability
- Choose an explicit timezone and inspect the scheduler’s day-of-month semantics. There is no universal timezone default across schedulers.
- Make the script fail visibly on navigation errors, missing selectors, and screenshot write failures. A green scheduled run should mean an image was actually created.
- Keep a manual trigger available so you can verify changes without waiting a month.
- Store images somewhere durable if you need an archive beyond the scheduler’s artifact or service retention period.
Performance
A monthly job runs infrequently, so prioritize predictable readiness and stable output over shaving a few seconds. Use an explicit viewport, avoid waiting for network idle on pages that never become idle, and capture only the required scope. Full-page captures and unusually large pages take more time and storage than viewport captures.
Cost
For a self-managed setup, account for the scheduler, browser execution, and storage in the environment you choose; exact costs depend on that provider and retention policy. Hosted service prices, capture quotas, and schedule limits change, so compare current plan pages. The available research does not establish a universal scheduler price or a reliable price comparison across hosted services.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No monthly run appears | Schedule is disabled, workflow is not on the expected branch, or cron/timezone assumptions are wrong. | Check scheduler history and branch settings; verify cron is interpreted in the expected timezone and day-of-month. |
| Run occurs on the wrong local date or hour | The schedule uses UTC or another timezone, or daylight-saving time changed the offset. | Convert the desired time explicitly; use an explicit timezone feature if available and confirm its daylight-saving behavior. |
| Navigation times out | The page is slow, or network-idle waiting never completes due to persistent requests. | Use domcontentloaded plus a selector wait for the content that matters; adjust the timeout only when the page genuinely needs longer. |
| Screenshot is blank or incomplete | Capture began before the key content rendered, or content loads after scrolling. | Wait for a meaningful selector, scroll to trigger lazy content, and inspect whether the page requires authentication or client-side setup. |
| Element screenshot fails | Selector does not match, is hidden, or matches more than the intended element. | Verify the selector in the browser, wait for visibility, and narrow it to one target. |
| Images differ every month despite no design change | Browser environment, viewport, dynamic content, or personalization changed. | Keep rendering conditions fixed and remove or control volatile page content where possible. |
| Old images disappear | Artifact or hosted-library retention expired, or filenames were overwritten. | Check retention settings, use unique dated filenames, and copy files to a durable destination. |
| Job succeeds but no file is available | Output path differs from the artifact upload path, or capture failed before writing. | Use one explicit output directory, upload the matching path, and fail the script if the expected file is absent. |
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. You still need a scheduler to trigger a monthly request, but you can skip installing and maintaining a browser in your own workflow. See the API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Schedule one of these calls for the first day of the month and save the returned image with a unique date in its filename. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed; its MCP server lets AI agents take screenshots; and 1,000 screenshots a month are free with no card, with paid plans starting at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Should I capture the first day at midnight?
Only if midnight matches your use case. Pick a time when the target site is expected to be available, then confirm the scheduler’s timezone and calendar behavior.
Will a monthly screenshot prove what the site showed at every moment?
No. It records a capture at one scheduled time. If you need more points in time, schedule additional captures.
Can I archive a page behind a login?
It depends on the capture environment and the site’s authentication flow. Make sure the chosen service or script can authenticate safely, and avoid putting credentials in publicly visible workflow files or logs.
How do I compare this month’s image with last month’s?
Use the same capture conditions and open both dated files in an image comparison tool. Expect differences from live content and rendering changes as well as actual design edits.


