How to Upload Recurring Website Screenshots to Dropbox Automatically
Capture a website on a schedule and save each screenshot to Dropbox with Playwright, Python, and the Dropbox API.
To upload recurring website screenshots to Dropbox automatically, connect three steps: use a browser automation script to capture the page, schedule that script to run at your chosen interval, and upload each image through the Dropbox API. The example below uses Playwright with Python and Dropbox’s /2/files/upload endpoint. It captures the visible browser viewport; set FULL_PAGE to capture the full scrollable page instead.
Dropbox’s built-in screenshot sync is different: it saves screenshots taken on supported Mac and Windows computers. It is not documented as a service that opens a website on a timer. For recurring website captures, you need a scheduled job as well as a capture and upload step.
1. Set up Dropbox API access
- In the Dropbox App Console, create an app and choose its access type: an app folder or the full Dropbox, depending on where the screenshots should go.
- Enable the
files.content.writepermission for the app. - Authorize the app using Dropbox OAuth 2.0. For a production job, use Dropbox’s current OAuth guidance to obtain and manage credentials. Do not commit a long-lived access token to a public repository or print it in logs.
- Choose a destination folder, such as
/Website Captures. Confirm that the authorized app can write to it.
Dropbox describes API v2 as “a set of HTTP endpoints that help your app integrate with Dropbox.” Its file upload endpoint accepts file bytes in the request body and requires the files.content.write scope.
2. Install Playwright and save credentials
The script uses Python 3, Playwright, and Requests. Install the packages and the browser binaries:
python -m pip install playwright requests
python -m playwright install chromium
Set environment variables for the page, Dropbox access token, and destination folder. For example, in a Unix-like shell:
export TARGET_URL='https://example.com'
export DROPBOX_ACCESS_TOKEN='your-authorized-access-token'
export DROPBOX_FOLDER='/Website Captures'
export FULL_PAGE='false'
Use your real target URL and securely provision the token in the environment where the job runs. For Windows, configure the equivalent environment variables in the shell or scheduler you use.
3. Capture and upload with Python
Save this as capture_to_dropbox.py. Each run creates a timestamped PNG, checks that the local file exists and is nonempty, then uploads it to Dropbox. The upload uses autorename so an existing path does not silently replace an earlier capture.
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
import requests
from playwright.sync_api import sync_playwright
TARGET_URL = os.environ.get("TARGET_URL", "https://example.com")
ACCESS_TOKEN = os.environ["DROPBOX_ACCESS_TOKEN"]
DROPBOX_FOLDER = os.environ.get("DROPBOX_FOLDER", "/Website Captures").rstrip("/")
FULL_PAGE = os.environ.get("FULL_PAGE", "false").lower() in {"1", "true", "yes"}
OUTPUT_DIR = Path(os.environ.get("OUTPUT_DIR", "captures"))
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
local_path = OUTPUT_DIR / f"website-{stamp}.png"
try:
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1440, "height": 900}, device_scale_factor=1)
response = page.goto(TARGET_URL, wait_until="networkidle", timeout=60000)
if response is not None and response.status >= 400:
raise RuntimeError(f"Page returned HTTP {response.status}: {TARGET_URL}")
page.screenshot(path=str(local_path), full_page=FULL_PAGE, type="png")
browser.close()
if not local_path.is_file() or local_path.stat().st_size == 0:
raise RuntimeError("Screenshot file is missing or empty")
dropbox_path = f"{DROPBOX_FOLDER}/{local_path.name}" if DROPBOX_FOLDER else f"/{local_path.name}"
headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
"Content-Type": "application/octet-stream",
"Dropbox-API-Arg": requests.utils.json.dumps({
"path": dropbox_path,
"mode": "add",
"autorename": True,
"mute": True,
"strict_conflict": False,
}),
}
with local_path.open("rb") as image_file:
upload = requests.post(
"https://content.dropboxapi.com/2/files/upload",
headers=headers,
data=image_file,
timeout=120,
)
upload.raise_for_status()
print(f"Uploaded {local_path} to Dropbox: {upload.json().get('path_display', dropbox_path)}")
except Exception as error:
print(f"Capture or upload failed: {error}", file=sys.stderr)
raise
if __name__ == "__main__":
main()
The example uses a fixed 1440 × 900 viewport. Change the width, height, or device scale factor in browser.new_page() to suit the page you are tracking. wait_until="networkidle" waits for network activity to settle, but some sites keep connections open or load content later. In that case, use wait_until="domcontentloaded" and wait for a specific selector or a deliberate short delay before capture.
Viewport or full-page capture?
| Setting | What it captures | Use it when |
|---|---|---|
full_page=False |
The visible viewport | You want a stable snapshot of the first screen or a fixed-size comparison. |
full_page=True |
The full scrollable page | You need the entire page in one image and can accommodate taller, potentially larger files. |
Full-page capture can produce a much taller image than viewport capture. Very long pages may take longer to render and can create larger files. Check the actual file size before relying on a workflow that processes or stores many captures.
4. Schedule the script
Run the script manually once while checking both the local PNG and its Dropbox destination. Then configure a scheduler to run it at the desired cadence. A scheduler must run the job on a machine or hosted environment that is available at the scheduled time.
Example: cron on Linux or macOS
This example runs every day at 08:00. Replace the paths with the absolute path to your Python interpreter and script. Configure the required environment variables for cron using your system’s supported secure method; interactive shell variables are not automatically guaranteed to be present in scheduled jobs.
0 8 * * * /absolute/path/to/python /absolute/path/to/capture_to_dropbox.py >> /absolute/path/to/capture.log 2>&1
For another cadence, change the cron schedule. For example, */30 * * * * runs every 30 minutes. Check your scheduler’s timezone: the execution time may use the host’s timezone rather than the timezone you expect.
Scheduled services and Windows
A hosted scheduled event or a desktop scheduler can run the same script. Configure the working directory, Python environment, environment variables, and log destination explicitly. On a local computer, sleep, shutdown, network loss, or a logged-out environment can prevent the job from running. Hosted execution avoids needing your personal computer to be on, but adds the setup and operating cost of the chosen compute service.
5. Dropbox upload choices and file naming
The example chooses a UTC timestamp in every local and Dropbox filename, such as website-20261004T080000Z.png. This preserves a history of captures. The API’s mode, autorename, and path options determine what happens when a name already exists; do not assume repeated uploads to one fixed path preserve earlier versions. If you intentionally want a single rolling file, choose overwrite behavior explicitly and understand that it replaces the prior path’s contents.
The simple /2/files/upload endpoint is for files no larger than 150 MiB. Dropbox directs larger transfers to an upload session. Ordinary page screenshots will often be much smaller, but inspect the image produced by your target page rather than assuming a maximum size. A full-page capture or unusual page can produce a large file.
6. Optional upload with cURL
If another tool produces the screenshot file, you can upload that file directly with cURL. Set DROPBOX_ACCESS_TOKEN and replace the local filename and destination. This request creates a new file path; use a unique remote name for each run if you want a history.
curl -X POST 'https://content.dropboxapi.com/2/files/upload' \
-H "Authorization: Bearer $DROPBOX_ACCESS_TOKEN" \
-H 'Content-Type: application/octet-stream' \
-H 'Dropbox-API-Arg: {"path":"/Website Captures/website-20261004T080000Z.png","mode":"add","autorename":true,"mute":true}' \
--data-binary '@/absolute/path/to/website.png'
7. Optional upload with Node.js
This is the upload half of the workflow for a screenshot already saved at website.png. Pair it with a browser capture step and schedule the Node.js process as described above. The upload endpoint accepts the binary file body, with metadata passed in Dropbox-API-Arg.
import { readFile } from 'node:fs/promises';
const token = process.env.DROPBOX_ACCESS_TOKEN;
if (!token) throw new Error('Set DROPBOX_ACCESS_TOKEN');
const bytes = await readFile('./website.png');
const remotePath = `/Website Captures/website-${new Date().toISOString().replace(/[:.]/g, '-')}.png`;
const response = await fetch('https://content.dropboxapi.com/2/files/upload', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/octet-stream',
'Dropbox-API-Arg': JSON.stringify({
path: remotePath,
mode: 'add',
autorename: true,
mute: true,
}),
},
body: bytes,
});
if (!response.ok) {
throw new Error(`Dropbox upload failed (${response.status}): ${await response.text()}`);
}
console.log(`Uploaded to ${remotePath}`);
8. Reliability, performance, and cost
- Record each run: log the capture time, target URL, local file size, Dropbox response path, and error details. Never include access tokens in logs.
- Use sensible timeouts: page navigation and HTTP upload can hang on slow networks. Set explicit timeouts and decide how many retries are appropriate. A retry should use an idempotent or unique destination name so it does not unexpectedly replace an earlier capture.
- Watch for dynamic pages: a page can return before client-side content, fonts, or images finish rendering. Wait for a known selector or appropriate page state when the screenshot must include specific content.
- Keep browser dependencies maintained: Playwright browser binaries and the Python package need to remain compatible. Revisit the scheduled environment when updating either.
- Plan storage and retention: timestamped images accumulate. Decide how long to keep them and how much Dropbox storage the capture frequency and image sizes require.
- Estimate compute from the cadence: each scheduled run starts a browser, loads the site, renders the page, and transfers a file. More frequent runs and full-page images use more execution time and bandwidth. The actual cost depends on where the script runs and the page being captured.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Dropbox returns 401 |
Missing, expired, or invalid access token. | Reauthorize through the app’s OAuth flow and update the secret where the scheduled job reads it. |
Dropbox returns 403 |
The app lacks files.content.write, or its access type does not include the destination. |
Enable the scope, complete any required authorization again, and verify the selected app-folder or full-Dropbox access and path. |
Dropbox returns 409 |
The API rejected the requested path or reported a path-related conflict. | Read the Dropbox error response, verify the folder exists and the path is valid, then choose a unique name or the intended conflict mode. |
| Upload fails on a large image | The simple upload endpoint has a 150 MiB limit. | Use Dropbox’s upload-session flow for files above that limit, or capture a smaller viewport image. |
| Scheduled run cannot find Python or Playwright | The scheduler uses a different PATH, interpreter, or working directory than your terminal. | Use absolute paths, install dependencies in the scheduled environment, and configure the working directory explicitly. |
| Screenshot is blank or incomplete | The page has not rendered its content yet, a navigation failed, or the site blocks automated access. | Check navigation status and logs, wait for a page-specific selector, and confirm automation is permitted. Do not treat a successful file write as proof that the page rendered correctly. |
| New uploads appear under unexpected names | autorename changed the name to avoid a conflict. |
Generate a unique timestamped name for each run and inspect the returned path_display. |
| It runs at the wrong time or not at all | Scheduler timezone, environment, machine availability, or permissions differ from the interactive session. | Check scheduler logs, timezone, job permissions, secret availability, and whether the host was running at the scheduled time. |
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. It can capture a page with one request; you can then upload the returned image bytes to Dropbox in your scheduled job. Its capture can remove cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. See the ScreenshotNeo 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
Upload shot.webp to Dropbox using the cURL or Python upload step above, and schedule that combined job. Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.
FAQ
Can Dropbox take a website screenshot on a schedule by itself?
The documented screenshot sync feature saves screenshots taken on supported Mac and Windows computers. For a website opened and captured automatically on a timer, use a browser capture job and a scheduler, then upload the file to Dropbox.
Should I use a viewport or full-page image?
Use a viewport capture when you need a consistent visible region. Use full-page capture when the entire document matters, keeping in mind that the resulting image can be much taller and larger.
Can the script capture a page that requires login?
It can be adapted to use an authenticated browser context, but credentials and session data must be handled securely. Follow the target site’s access rules and avoid putting passwords or cookies in source code or logs.
Where can I read the official API references?
Start with the Dropbox API documentation for upload endpoint details and the Playwright Page API for navigation and screenshots. Dropbox’s screenshot sync help page explains its desktop feature.


