| name | R Package Structure |
| description | Understanding R package directory layout, package states, required and optional files, and build configuration patterns |
R Package Structure
Overview
This skill covers the fundamental structure of R packages, from directory layout to the five distinct package states. Understanding package structure is critical for proper package development.
The Five Package States
R packages exist in five different states throughout their lifecycle:
- Source: The development state - what you work on. A directory with DESCRIPTION, R/, etc.
- Bundled: A compressed
.tar.gz file created by R CMD build. Single file for distribution.
- Binary: Platform-specific compiled package (
.tgz on macOS, .zip on Windows). No source code.
- Installed: Decompressed into a library directory. What
library() loads from.
- In-memory: Loaded into R's namespace system via
library() or loadNamespace().
Understanding these states helps you know:
- What files belong in source but not in bundles (
.Rbuildignore)
- Why some files exist in installed packages but not source
- When code executes (build time vs load time)
Required Files and Directories
DESCRIPTION
The package metadata file. Every package MUST have this.
Package: mypackage
Title: What the Package Does (One Line, Title Case)
Version: 0.1.0
Authors@R:
person("First", "Last", , "email@example.com", role = c("aut", "cre"),
comment = c(ORCID = "YOUR-ORCID-ID"))
Description: What the package does (one paragraph).
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.1
Imports:
dplyr (>= 1.0.0),
rlang (>= 1.0.0)
Suggests:
testthat (>= 3.0.0),
knitr,
rmarkdown
Config/testthat/edition: 3
NAMESPACE
The namespace file. Controls what your package exports and imports.
CRITICAL: Never edit this file by hand! Use roxygen2 to generate it.
export(my_function)
exportPattern("^[^\\.]")
importFrom(dplyr,filter)
importFrom(rlang,"%||%")
R/
Directory containing all your R code files. Required if your package has any functions.
Rules for R/ directory:
- Only
.R files (case-sensitive on some platforms)
- Files sourced in alphabetical order during build
- No subdirectories (all files in flat structure)
- File names should be descriptive of content
- Special file:
R/zzz.R for .onLoad() and .onAttach()
R/
├── data.R # Data documentation
├── import-standalone.R # Standalone imported utilities
├── my-package-package.R # Package-level documentation
├── utils.R # Utility functions
├── main-feature.R # Main functionality
└── zzz.R # .onLoad and .onAttach hooks
man/
Directory containing documentation files (.Rd format). Required for packages with documentation.
CRITICAL: Never edit .Rd files by hand! Use roxygen2 to generate them.
man/
├── mypackage-package.Rd # Package overview
├── my_function.Rd # Function documentation
└── my_data.Rd # Data documentation
Optional Directories
data/
Contains exported data objects (.rda or .RData files).
usethis::use_data(my_dataset, overwrite = TRUE)
Important details:
- Binary
.rda format only (use save() with compress = "xz" for best compression)
- Accessed by users with
data(my_dataset) or direct reference
- Requires documentation in
R/data.R
- LazyData: true in DESCRIPTION means data loads without
data() call
- CRAN limit: <5MB total, <1MB per subdirectory
tests/
Contains all package tests.
tests/
├── testthat/
│ ├── helper-data.R # Test helpers (loaded before tests)
│ ├── setup.R # Setup run before tests
│ ├── test-feature1.R # Test files (must start with "test-")
│ └── test-feature2.R
└── testthat.R # Entry point (loads testthat and runs tests)
Setup with:
usethis::use_testthat(3)
vignettes/
Long-form documentation and tutorials.
vignettes/
├── articles/ # Articles (not installed with package)
│ └── supplementary.Rmd
└── introduction.Rmd # Vignettes (installed with package)
usethis::use_vignette("introduction")
usethis::use_article("supplementary")
Key differences:
- Vignettes: Installed with package, in CRAN bundle
- Articles: pkgdown only, not in bundle (saves size)
inst/
Files to be installed as-is. Only inst/ contents are copied to installation.
inst/
├── CITATION # How to cite the package
├── extdata/ # Example/raw data files
│ ├── example.csv
│ └── sample.json
├── scripts/ # Helper scripts
│ └── setup.R
└── templates/ # Template files
└── report.Rmd
Access inst/ files with:
system.file("extdata", "example.csv", package = "mypackage")
CRITICAL: Do NOT put a DESCRIPTION or NAMESPACE file in inst/ - this will break your package!
data-raw/
Scripts for creating package data objects. Not included in bundle.
data-raw/
├── DATASET.R # Script to create data/DATASET.rda
└── prepare_examples.R # Script for inst/extdata/ files
usethis::use_data_raw("DATASET")
Pattern:
library(tidyverse)
DATASET <- read_csv("source.csv") %>%
clean_names() %>%
filter(year >= 2020)
usethis::use_data(DATASET, overwrite = TRUE)
src/
Compiled code (C, C++, Fortran).
src/
├── Makevars # Unix build configuration
├── Makevars.win # Windows build configuration
├── mycode.cpp # Source files
└── RcppExports.cpp # Auto-generated (Rcpp)
Build Control Files
.Rbuildignore
Files/directories to exclude from package bundle. Uses regex patterns (NOT glob patterns).
^.*\.Rproj$ # RStudio project files
^\.Rproj\.user$ # RStudio user files
^data-raw$ # Data preparation scripts
^LICENSE\.md$ # Full license (keep LICENSE)
^README\.Rmd$ # Source (keep README.md)
^\.github$ # GitHub-specific files
^_pkgdown\.yml$ # pkgdown config
^docs$ # pkgdown output
^pkgdown$ # pkgdown extras
^\.httr-oauth$ # OAuth credentials
^\.secrets$ # Secrets directory
^\.env$ # Environment files
^cran-comments\.md$ # CRAN submission notes
^revdep$ # Reverse dependency checks
^\.lintr$ # Linter configuration
^\.pre-commit-config\.yaml$ # Pre-commit hooks
Pattern rules:
- Regex, not glob: use
^ for start, $ for end
- Escape dots:
\. not .
- Case-sensitive
- Test patterns with
usethis::use_build_ignore("pattern")
.gitignore
Files to exclude from version control.
# R specific
.Rproj.user
.Rhistory
.RData
.Ruserdata
# Build artifacts
/*.tar.gz
/*.zip
/check/
/revdep/
# Documentation
/docs/
/Meta/
/doc/
# Package specific
.httr-oauth
.secrets/
.env
# OS specific
.DS_Store
Thumbs.db
.github/
GitHub-specific files (excluded from bundle via .Rbuildignore).
.github/
├── workflows/
│ ├── R-CMD-check.yaml # CI checks
│ ├── test-coverage.yaml # Code coverage
│ └── pkgdown.yaml # Deploy docs
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE/
└── PULL_REQUEST_TEMPLATE.md
What Goes Where?
inst/ vs Root Directory
Common confusion: LICENSE, README, NEWS files
# Correct structure:
LICENSE # Machine-readable (CRAN requires this at root)
LICENSE.md # Human-readable (at root, in .Rbuildignore)
inst/CITATION # Citation info (needs to be installed)
README.md # User-facing (at root, included in bundle)
README.Rmd # Source (at root, in .Rbuildignore)
NEWS.md # At root (automatically used by pkgdown)
Internal vs External Data
data/ # Exported data (users can load)
└── dataset.rda
R/sysdata.rda # Internal data (your functions use, users cannot load)
inst/extdata/ # Raw data files (users access via system.file())
└── example.csv
Package-level Files
mypackage/
├── DESCRIPTION # Package metadata (required)
├── NAMESPACE # Auto-generated by roxygen2 (required)
├── LICENSE # License file (required for most licenses)
├── README.md # Package overview (highly recommended)
├── NEWS.md # Change log (recommended)
├── .Rbuildignore # Build exclusions
├── .gitignore # Git exclusions
├── mypackage.Rproj # RStudio project (in .Rbuildignore)
├── R/ # R code (required)
├── man/ # Documentation (auto-generated)
├── tests/ # Tests (highly recommended)
├── vignettes/ # Long-form docs (recommended)
├── data/ # Data (if needed)
├── data-raw/ # Data preparation (in .Rbuildignore)
├── inst/ # Installed files (if needed)
├── src/ # Compiled code (if needed)
└── .github/ # GitHub files (in .Rbuildignore)
File Organization Patterns
Small Package (<10 functions)
R/
├── mypackage-package.R # Package docs
├── main.R # Main functions
├── utils.R # Utilities
└── zzz.R # .onLoad if needed
Medium Package (10-50 functions)
R/
├── mypackage-package.R
├── feature1.R # Grouped by feature
├── feature2.R
├── feature3.R
├── utils.R
├── utils-feature1.R # Feature-specific utils
├── data.R
└── zzz.R
Large Package (50+ functions)
R/
├── mypackage-package.R
├── aaa-imports.R # Package-level imports (aaa = loaded first)
├── class-feature1.R # S3/R6 class definitions
├── feature1-methods.R # Methods for feature1
├── feature1-utils.R # Utilities for feature1
├── feature2-core.R
├── feature2-helpers.R
├── generics.R # Generic function definitions
├── import-standalone-*.R # Standalone imports
├── utils.R
├── data.R
└── zzz.R
Common Pitfalls
1. Editing NAMESPACE or .Rd Files Manually
Problem: These are auto-generated by roxygen2.
Solution: Always use roxygen2 comments in R files.
my_function <- function(x) {
}
2. Using Subdirectories in R/
Problem: R/ does not support subdirectories.
Solution: Use file naming conventions instead.
# WRONG:
R/
└── feature1/
├── core.R
└── utils.R
# RIGHT:
R/
├── feature1-core.R
└── feature1-utils.R
3. Forgetting .Rbuildignore for Development Files
Problem: Development files included in package bundle, inflating size.
Solution: Add patterns to .Rbuildignore.
usethis::use_build_ignore("data-raw")
usethis::use_build_ignore(".github")
4. Putting Data in inst/ Instead of data/
Problem: Data in inst/extdata/ is for raw files, not R objects.
Solution: Use correct location for data type.
usethis::use_data(my_dataset)
5. Wrong File Extension Case
Problem: script.r instead of script.R (matters on Linux).
Solution: Always use uppercase .R for R code files.
# WRONG: R/utils.r
# RIGHT: R/utils.R
6. Exceeding CRAN Size Limits
Problem: Package too large for CRAN (<5MB total, <1MB per subdirectory).
Solution: Compress data, move examples to inst/extdata/, suggest large dependencies.
usethis::use_data(dataset, compress = "xz", overwrite = TRUE)
devtools::build()
7. Including Credentials or Secrets
Problem: API keys, OAuth tokens in package code.
Solution: Use .Rbuildignore and .gitignore, document user-side credential management.
# .gitignore and .Rbuildignore:
.httr-oauth
.secrets
.env
inst/secrets/
8. Incorrect inst/ Usage
Problem: Misunderstanding what inst/ is for.
Solution: Remember: inst/ contents are copied to package root at installation.
# During development:
inst/extdata/file.csv
# After installation:
extdata/file.csv # (inst/ is stripped)
# Access:
system.file("extdata", "file.csv", package = "mypackage")
9. Missing LazyData Declaration
Problem: Data in data/ but users must call data() to load it.
Solution: Add to DESCRIPTION.
LazyData: true
10. Mixing up README Files
Problem: Having both README.md and README.Rmd without .Rbuildignore.
Solution: Keep only README.md in bundle.
# Correct setup:
README.Rmd # Source (in .Rbuildignore)
README.md # Generated (included in bundle)
# .Rbuildignore:
^README\.Rmd$
Quick Reference
Creating New Package
usethis::create_package("~/mypackage")
usethis::use_git()
usethis::use_github()
usethis::use_testthat(3)
usethis::use_mit_license()
usethis::use_roxygen_md()
usethis::use_build_ignore(c("data-raw", ".github"))
Common usethis Helpers
usethis::use_data(dataset)
usethis::use_data_raw("dataset")
usethis::use_r("function")
usethis::use_test("function")
usethis::use_vignette("intro")
usethis::use_package("dplyr")
usethis::use_build_ignore("file")
Checking Package Structure
devtools::check()
devtools::build()
devtools::install()
pkgbuild::build(path = ".", dest_path = tempdir())
Resources