Building pkgdown Sites
pkgdown turns the standard documentation files in your R package into a full-featured website. It reads your DESCRIPTION, your roxygen comments, your vignettes, and your README, then assembles them into a site with a navbar, a reference index, article listings, and a home page. The output is static HTML, which means it deploys cheaply and reliably on GitHub Pages, Netlify, or any web host.
This tutorial walks through the full workflow: setting up the configuration files, building the site locally, writing additional articles, and deploying to three different hosting targets.
Prerequisites
Your package needs a few things in place before pkgdown can do its job:
- A valid
DESCRIPTIONfile withTitle,Version, andAuthors@Rfields - roxygen2 documentation in your
R/source files (pkgdown generates the reference pages from roxygen blocks) - Optionally, one or more vignettes in
vignettes/
If you have an existing package built with devtools, you likely already have all of this. If not, the R Package Structure tutorial covers the fundamentals.
Installing pkgdown
pkgdown is not a dependency of your package — it is a build tool you install separately:
install.packages("pkgdown")
Always use the latest version. pkgdown develops quickly and the output format changes occasionally:
install.packages("pkgdown", repos = "https://cran.rstudio.com")
Initial Setup
Run pkgdown::build_site() from your package root. On a freshly configured package, this does everything in one step:
pkgdown::build_site()
pkgdown creates a docs/ directory containing the complete static site. It also creates a _pkgdown.yml configuration file in your package root if one does not already exist. That configuration file is where you control the site’s structure, appearance, and content.
The first build reads your DESCRIPTION to populate the navbar and home page automatically. You get a working site immediately, then refine it by editing _pkgdown.yml.
The _pkgdown.yml Configuration File
_pkgdown.yml controls the entire site. Here is a minimal configuration for a package called mypkg:
url: https://user.github.io/mypkg
template:
package: default
bootswatch: flatly
navbar:
structure:
left:
- home
- articles
- reference
- news
right: github
reference:
- title: "Main functions"
contents:
- starts_with("^[^_]")
articles:
- title: "Guides"
contents:
- getting_started
- advanced_usage
Key fields
url is required if you plan to deploy to GitHub Pages or any hosted environment. pkgdown uses it to generate correct canonical URLs and sitemaps. Without it, relative links work fine for local preview but break in production.
template controls the visual theme. The bootswatch field swaps colour schemes. Valid values include flatly, cerulean, journal, readable, spacelab, united, and cosmo. The default template uses Bootstrap 5 and produces clean, readable output without additional dependencies.
navbar defines the top navigation bar. The structure section lists the built-in pages and their order. You can add custom links and dropdown menus here.
reference maps to the auto-generated function reference. The contents field accepts individual function names, starts_with() patterns, and matches() regex patterns.
articles lists the vignettes you want to appear in the articles section. pkgdown reads your vignettes/ directory and matches entries by the filename stem.
Customising the Home Page
The home page (index.md or README.Rmd in the package root) becomes the site’s landing page. pkgdown extracts the content after the badges and places it on the site home.
Write your README as usual, but include a <!-- pkgdown: home: start --> and <!-- pkgdown: home: end --> comment pair to control what appears on the site:
<!-- pkgdown: home: start -->
# mypkg
{mypkg} makes it easy to do X with R.
[](https://cran.r-project.org/package=mypkg)
[](https://github.com/user/mypkg/actions)
## Installation
```r
install.packages("mypkg")
Quick start
library(mypkg)
result <- do_something(data)
Anything outside those markers — badge HTML, build status images, installation instructions that repeat elsewhere — is stripped from the site home. This keeps the generated page clean and focused.
## Customising the Navigation Bar
The navbar is defined in the `navbar` section of `_pkgdown.yml`. Beyond the built-in pages (`home`, `articles`, `reference`, `news`), you can add external links, section headings, and dropdown menus:
```yaml
navbar:
structure:
left:
- home
- articles
- reference
- news
- separator
- text: "FAQ"
href: "https://example.com/faq"
- github
components:
github:
icon: fa-github fa-lg
href: https://github.com/user/mypkg
The separator inserts a visual divider between the built-in pages and any custom links. Dropdown menus are also possible:
navbar:
structure:
left:
- home
- articles
- reference
- dropdown:
text: "More"
menu:
- text: "Changelog"
href: news/index.html
- text: "FAQ"
href: https://example.com/faq
- text: "Support"
href: https://github.com/user/mypkg/issues
Adding Articles
Articles in pkgdown are vignettes. pkgdown renders .Rmd files in vignettes/ to HTML and places them under the /articles/ path of your site.
Create a vignette as you normally would with usethis::use_vignette("getting-started"):
usethis::use_vignette("getting-started")
This creates vignettes/getting-started.Rmd. Edit it as normal R Markdown. The vignette title in the YAML frontmatter becomes the article title on the site:
---
title: "Getting Started with mypkg"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Getting Started with mypkg}
%\VignetteEngine{rmarkdown::html_vignette}
%\VignetteEncoding{UTF-8}
---
# Introduction
This vignette shows you how to...
To expose the vignette in the site, add it to the articles section of _pkgdown.yml:
articles:
- title: "User Guides"
contents:
- getting_started
- advanced_usage
If your vignettes directory contains files not listed in articles, they still appear in a catch-all “Articles” section unless you set dir: ... to a custom location. Listing them explicitly gives you control over grouping and ordering.
After adding or modifying vignettes, rebuild the site:
pkgdown::build_articles()
Controlling the Reference Index
The reference page lists every documented object in your package. By default, pkgdown includes all functions and datasets that have roxygen blocks. You can organise them into groups using roxygen tags:
#' @family geocoding functions
geocode <- function(address) {
# ...
}
Then reference the family in _pkgdown.yml:
reference:
- title: "Geocoding"
desc: "Convert addresses to coordinates"
contents:
- geocode
- reverse_geocode
- title: "Data"
contents:
- starts_with("^data_")
- title: "Internal"
contents:
- matches("^\\._[^_]")
auto: false
The auto: false setting excludes these objects from the auto-generated index. Objects without an explicit family appear in an “Other” group.
The News Page
pkgdown automatically builds a news page from NEWS.md in your package root. Use second-level headings to separate versions:
# mypkg 0.3.0
* Added `geocode()` function for forward geocoding
* Fixed bug in `import_data()` when input has missing values
# mypkg 0.2.0
* New vignette on advanced usage patterns
* Dropped support for R 3.x
The most recent heading becomes the top section of the news page. Each version gets its own expandable section on the rendered site.
Building Locally
During development, build individual parts rather than the full site for speed:
# Build only the reference pages
pkgdown::build_reference()
# Build only the home page
pkgdown::build_home()
# Build only the articles
pkgdown::build_articles()
# Build the news page
pkgdown::build_news()
Run pkgdown::build_site() for a full production build. The output goes into docs/ by default.
Preview the site locally with pkgdown::preview_site(). This opens the docs/index.html file in your browser.
Add docs/ to your .gitignore if you deploy through GitHub Actions (see the next section). If you commit the docs/ directory directly, you do not need the .gitignore entry.
Deploying to GitHub Pages
GitHub Pages serves files from the gh-pages branch or from a docs/ directory on main or gh-pages. The simplest approach for pkgdown uses a GitHub Actions workflow that rebuilds and deploys the site on every push.
Create .github/workflows/pkgdown.yaml:
on:
push:
branches: [main, master]
workflow_dispatch:
name: pkgdown
jobs:
pkgdown:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true
- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: |
any::pkgdown
local::.
- name: Build site
run: pkgdown::build_site_github_pages(new_version = "development")
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages
directory: docs
This workflow uses build_site_github_pages() from pkgdown, which handles the version separation that lets you deploy both a development snapshot and a release snapshot simultaneously. It also handles the commit history on the gh-pages branch correctly.
Enable GitHub Pages in your repository settings under Settings → Pages → Source, and select the gh-pages branch and / (root) directory.
Deploying to Netlify
Netlify deploys from a publish directory. Configure your _pkgdown.yml to output there instead of docs/:
destination: publish
Then create a netlify.toml in your repository root:
[build]
command = "Rscript -e \"pkgdown::build_site()\""
publish = "publish"
Connect your repository in the Netlify UI, set the production branch, and Netlify runs the build command on every push. The generated publish/ directory becomes your site.
One advantage of Netlify is custom subdomain handling built into the dashboard. You do not need to configure anything in _pkgdown.yml beyond the url field.
Deploying to a Subdomain
All three hosting targets support subdomains. Set the url field in _pkgdown.yml to your full subdomain:
url: https://docs.mypackage.com
For GitHub Pages with a custom domain, add a CNAME file inside docs/ (or in the root of your gh-pages branch):
docs.mypackage.com
Then configure the custom domain in GitHub Pages settings. DNS should point to user.github.io via an A record or CNAME.
On Netlify, add the subdomain in Site settings → Domain management → Add custom domain. Netlify handles SSL automatically through Let’s Encrypt.
pkgdown generates absolute URLs when url is set. This ensures that sitemap.xml, Open Graph tags, and canonical links all point to the correct address, which matters for SEO and for social previews when sharing links.
Ignoring Files in the Site
Some files in your package should not appear in the built site. Use the exclude field in _pkgdown.yml:
exclude:
- "^NEWS.md$"
- "^cran-comments.md$"
- "inst/tinytest"
By default, pkgdown already excludes common non-code files like .travis.yml, .Rbuildignore patterns, and files starting with _ or .. The exclude field handles package-specific files.
Using a Custom Template
For more control over the visual design, you can pass a custom pkgdown template package. Several community template packages exist, or you can create your own. To use one from GitHub:
template:
package: myorg/pkgdownthemes
bootswatch: cerulean
includes:
in_header: include/google-analytics.html
The includes field injects arbitrary HTML into the page head, footer, or navbar. Common uses include Google Analytics tracking code, Hotjar snippets, or custom favicons.
Best Practices
Keep vignettes focused. Each vignette should cover one task or concept. Users arriving from a search engine usually want a specific answer quickly. Long narrative vignettes are harder to skim than short focused ones.
Link between reference and articles. Use relative links in your vignettes: [do_something()][do_something] links to the function reference. pkgdown resolves these at build time.
Update the site in your release workflow. Tag a release, then push. The GitHub Actions workflow builds the release snapshot automatically. Your users always see current documentation at the stable URL.
Use semantic versioning for the news. The news page parses version numbers from headings. Inconsistent formatting breaks the grouping. Write {# .version 0.3.0} as an attribute if you need precise control:
# mypkg 0.3.0 {# .version 0.3.0}
Preview before deploying. Run pkgdown::check_pkgdown() to validate links and structure without building the full site. This catches missing references and broken article paths early.
Set destination explicitly for non-GitHub deploys. If you use Netlify, RsConnect, or any target that is not GitHub Pages, set destination: docs (or your chosen directory) explicitly. pkgdown defaults work for GitHub Pages but may not match your deploy configuration.
See Also
- /guides/r-writing-r-packages-devtools/ — Set up and develop R packages with devtools, the foundation that pkgdown builds on
- /tutorials/roxygen2-documentation/ — Document your functions with roxygen2 so pkgdown has rich reference pages to render
- /tutorials/package-structure-r/ — Understand the package structure pkgdown expects before configuring your site