ReadTheDocs Pull Request Previews: What Works, What Costs, and How to Configure Them
ReadTheDocs PR Previews give you a live rendered URL for every pull request. Here's how to enable them in .readthedocs.yaml, wire them into CI checks, and the free-tier limits that catch teams off guard.
19 Apr 2026, 22:23 UTC

The Problem: Reviewing Documentation Changes Blind
You open a pull request that rewrites the installation guide, adds a new API reference page, and tweaks the theme. The diff shows reStructuredText or Markdown source, but you can't see how it actually renders. Does the new table overflow on mobile? Did the custom Sphinx extension break the sidebar? Merging without a rendered preview is guessing.
ReadTheDocs Pull Request Previews solve this by automatically building a live version of your docs for every PR and serving it at a unique URL like https://myproject.readthedocs.io/en/pr-42/. The preview updates on every push. This post covers how to enable them, what they cost on the free tier, and where they fall short.
How PR Previews Work
When you connect a repository (GitHub, GitLab, or Bitbucket) to ReadTheDocs and enable PR previews, the platform registers a webhook. On each PR event—open, synchronize, reopen—ReadTheDocs spins up a build using the same environment, dependencies, and configuration as your main branch. The build artifact is deployed to a subpath keyed by PR number. The preview URL is posted back to the PR as a comment (on GitHub/GitLab) and appears in the ReadTheDocs dashboard.
Key point: the preview uses the exact build image and .readthedocs.yaml from the PR branch. If the PR upgrades Sphinx or adds a new extension, the preview validates that change before merge. This catches broken builds that would otherwise land on latest.
Enabling Previews: YAML vs Dashboard
You can toggle previews in the project admin dashboard (Admin → Pull Requests), but the YAML approach is version-controlled and reviewable. In .readthedocs.yaml (config version 2), add:
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
jobs:
post_create_environment:
- pip install -r requirements.txt
post_install:
- pip install -e .
# PR preview configuration
pr_preview:
enabled: true
# Optional: retain previews longer than 7 days (paid plans only)
# retention_days: 30
The pr_preview.enabled: true flag is the minimum. The retention_days option only works on paid plans; on the free tier previews are purged 7 days after the PR closes or merges. If you prefer the dashboard, the setting is a simple checkbox—but the YAML ensures every contributor sees the same config.
Integrating Previews into Your Review Workflow
Three practical patterns:
- Require the preview link in PR descriptions. Add a PR template checklist item: "Preview URL: https://project.readthedocs.io/en/pr-/". Reviewers click before approving.
- Gate merges with a CI check. Use the ReadTheDocs GitHub App (installed via the dashboard) which posts a status check "ReadTheDocs Preview Build" that passes only when the preview succeeds. Branch protection rules can require this check.
- Automated visual diff. For teams with design review, tools like
chromaticorpercycan screenshot the preview URL and compare againstlatest. This catches CSS regressions the build log won't show.
Example GitHub Actions snippet that waits for the preview URL and posts it as a comment (useful if the webhook comment is delayed):
name: Wait for ReadTheDocs Preview
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
preview-url:
runs-on: ubuntu-latest
steps:
- name: Poll for preview
uses: actions/github-script@v7
with:
script: |
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number
});
const previewComment = comments.find(c =>
c.user.login === 'readthedocs' && c.body.includes('preview')
);
if (previewComment) {
core.setOutput('url', previewComment.body.match(/https?://\S+/)[0]);
}
Trade-offs and Limits You'll Hit
| Constraint | Free Tier | Paid Plans |
|---|---|---|
| Private repository previews | Not available | Available |
| Monthly build minutes | 1,000 shared across all builds | Higher limits, purchasable add-ons |
| Preview retention | 7 days after PR close/merge | Configurable via retention_days |
| Build timeout | 15 minutes default | Up to 60 minutes |
| Concurrent builds | 1 | Multiple |
Large projects (many pages, heavy extensions, PDF output) often exceed the 15-minute timeout. Mitigations: split the build into multiple Sphinx projects, disable PDF for previews (formats: [htmlzip, epub] only on main), or cache the virtual environment via cache: key: "{{ .Environment.PYTHON_VERSION }}-{{ checksum "requirements.txt" }}" in the YAML.
Security note: preview URLs for public repositories are publicly accessible. Never commit API keys, internal hostnames, or real user data in documentation source—even in a PR that hasn't merged. The preview builds from the PR branch, so any secret in that branch becomes visible at the preview URL.
Webhook reliability: GitHub rate limits or network blips can cause the preview build to never trigger. The ReadTheDocs dashboard (Builds → Pull Requests) shows the webhook delivery status. If a preview is missing, use the "Rebuild" button on the PR build row rather than pushing a dummy commit.
Try It on a Test Project First
Before rolling out to your main repo, create a throwaway ReadTheDocs project connected to a fork. Enable pr_preview: enabled: true in .readthedocs.yaml, open a PR that adds a page, and verify:
- The preview build appears in the dashboard with status "success".
- The generated URL loads and reflects the PR changes.
- The build log shows the same extensions and theme as your production build.
If that works, add the preview URL requirement to your PR template and the status check to branch protection. You'll catch rendering regressions before they hit latest—without guessing.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.