Building Documentation Websites with pkgdown in R
Building documentation websites for R packages with pkgdown turns your help files, vignettes, and README into a full-featured static site. The package reads your DESCRIPTION file for metadata, parses your roxygen comments for function reference pages, and assembles everything into a browsable website with a navigation bar, a reference index, article listings, and a home page. The output is pure static HTML, which deploys cheaply and reliably on GitHub Pages, Netlify, or any web server.
This tutorial walks through the full workflow of building pkgdown documentation: 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 of pkgdown. The package receives frequent updates that occasionally change the layout and CSS classes in the generated output, so running an older version can produce sites that look noticeably different from the current release. Specifying the CRAN repository explicitly ensures you fetch the package from R’s primary mirror rather than your local CRAN mirror, which may lag behind the latest version by a day or two:
install.packages("pkgdown", repos = "https://cran.rstudio.com")
Now that pkgdown is installed, the next step is running it against your package. The build_site() function reads your package’s DESCRIPTION file to extract metadata, parses the roxygen documentation blocks in your R source files to generate the function reference pages, and processes any vignettes in the vignettes directory into standalone article pages. On a freshly configured package with all the prerequisites in place, the command 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. The markers tell pkgdown which section to extract:
<!-- pkgdown: home: start -->
Inside the opening marker, include your package title, CI badges, and a short quick-start code snippet that shows users how to install and load the package. pkgdown renders everything between the two markers as the site’s landing page, so place only the content you want visitors to see first here. Close the section with the end marker before any content you want to exclude from the generated site home:
<!-- pkgdown: home: end -->
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:
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 element inserts a thin visual divider between the standard built-in pages and any custom external links you add, which keeps the navbar organized when your package links to external resources. You can also nest links inside dropdown menus, which is useful for grouping related pages like changelogs, FAQs, and community links under a single parent entry without cluttering the top-level navbar:
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 your package’s vignettes converted into HTML pages. pkgdown scans the vignettes/ directory, renders each .Rmd file to a standalone page, and places it under the /articles/ path on your site. This gives users browsable, formatted documentation for your package workflows without requiring them to open raw R Markdown files. Create a vignette as you normally would with usethis::use_vignette("getting-started"):
usethis::use_vignette("getting-started")
This command creates the file vignettes/getting-started.Rmd with a skeleton R Markdown template. Edit it as you would any normal R Markdown document, adding prose, code chunks, and output. The title you set in the YAML frontmatter block becomes the article title displayed on the pkgdown site, so make it descriptive enough to tell readers what the vignette covers:
---
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...
Writing the vignette produces the content, but pkgdown does not automatically surface it in the site navigation. You must explicitly list the vignette in the articles section of your _pkgdown.yml configuration file. This registration tells pkgdown to include the rendered article in the articles index page, in the navbar dropdown, and in the sitemap:
articles:
- title: "User Guides"
contents:
- getting_started
- advanced_usage
Listing articles explicitly gives you control over their grouping and the order they appear on the articles index page. Any vignette files not listed in the configuration still get rendered, but they land in a generic catch-all section that lumps everything together. After adding a new vignette or modifying an existing one, rebuild the articles to update the rendered output:
pkgdown::build_articles()
The reference page is the most visited section of any pkgdown site. It lists every documented function, dataset, and S3 method in your package, with links to detailed help pages. By default, pkgdown includes all objects that have roxygen documentation blocks and arranges them alphabetically. You can organize the reference into logical groups that match your package’s API surface, making it easier for users to find related functions. Group objects using roxygen family tags in your source files:
#' @family geocoding functions
geocode <- function(address) {
# ...
}
Once you have added family tags to your source files, reference those families in the _pkgdown.yml configuration to control how the reference page groups and labels them. Each family becomes a titled section on the reference index, with an optional description that appears below the section heading. Organizing functions this way turns a flat alphabetical list into a browsable API reference that mirrors how users think about your package’s capabilities:
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 version heading becomes the top section of the news page, with older versions displayed below in separate sections. Each version block gets its own expandable toggle on the rendered site so users can drill into the changes for any specific release.
Once your news page looks correct, you will want to preview the full site locally before deploying. During active development, rebuild only the parts you have changed to save time:
# 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
Setting the destination to publish tells pkgdown to write the generated site into that directory instead of the default docs/ folder. This aligns with Netlify’s convention of serving from a publish directory and keeps your build output separate from any docs/ directory you might already have. Next, create a Netlify configuration file 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
Setting the url field tells pkgdown where the site lives once deployed, which it uses to generate absolute URLs in sitemaps, Open Graph tags, and canonical link elements. For hosting on GitHub Pages with a custom domain, you also need to tell GitHub which domain to serve by adding a CNAME file to your repository:
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.
Next steps
Now that you understand building pkgdown sites, explore these related topics to deepen your knowledge and apply these techniques in more complex scenarios.
See also
- /guides/r-writing-r-packages-devtools/ — Set up and develop R packages with devtools, the foundation that pkgdown builds on
- /tutorials/r-package-development/roxygen2-documentation/ — Document your functions with roxygen2 so pkgdown has rich reference pages to render
- /tutorials/r-package-development/package-structure-r/ — Understand the package structure pkgdown expects before configuring your site