R Package Structure

· 6 min read · Updated March 29, 2026 · intermediate
r packages devtools cran

Every R package follows a fixed directory layout. This isn’t a style preference — it is a convention defined by the R packaging system and enforced by R CMD check. Packages accepted by CRAN, Bioconductor, and other repositories all share the same structure because the installation and check machinery expects it. Understanding this layout makes you a better package developer and a better R user, since you can always predict where to look for code, documentation, or data inside any package you install.

The DESCRIPTION File

The DESCRIPTION file is the package manifest. It tells R everything about your package: who wrote it, what version it is, what it does, and what it depends on. R CMD check reads it first.

Package: mypackage
Title: What the Package Does
Version: 0.1.0
Authors@R: person("Jane", "Doe", , "jane@example.com", role = c("aut", "cre"))
Description: A short paragraph explaining what the package does.
License: MIT
Imports:
  dplyr,
  tidyr
Suggests:
  testthat
Depends: R (>= 4.0)

The Package name must match the directory name exactly. Title is a one-line summary in title case. Version follows the familiar three-number format. The Authors@R field is the modern way to credit authors — aut lists contributors and cre marks the maintainer.

The dependency fields cause the most confusion. Imports lists packages required at runtime; they are loaded but not attached to the search path. Suggests lists packages only needed during development, testing, or vignette building — things like testthat, knitr, and rmarkdown. Depends is where you put the minimum R version. Avoid listing other packages in DependsImports is almost always the better choice.

The NAMESPACE File

The NAMESPACE file controls what your package exports to users and what it imports from other packages. Without exports, users cannot access your functions. Without imports, your package fails the moment it tries to call an external function.

export(my_function)
exportPattern("^[^\\.]")

import(dplyr)
importFrom(dplyr, filter, select)

export(my_function) makes a specific function available to users. exportPattern("^[^\\.]") is a shortcut that exports every function whose name does not start with a dot. import(dplyr) imports everything dplyr exports — convenient but imprecise. importFrom(dplyr, filter, select) is better practice: it imports only the specific functions you name, keeping your namespace clean.

In practice, you rarely write NAMESPACE by hand. devtools::document() reads roxygen2 comments in your R/ files and regenerates NAMESPACE automatically.

The R/ Directory

All R source code lives in R/. Each file typically groups related functions together — utils.R for helpers, core.R for the main exported functions, internal.R for functions users should not access.

# R/utils.R

#' Add one to a number
#' @param x A numeric vector.
#' @return A numeric vector with each element incremented by one.
#' @export
add_one <- function(x) {
  x + 1
}

Exported functions get roxygen2 comments above them. Unexported internal functions start with a dot by convention — .helper() is internal use only. Running devtools::document() turns those roxygen2 comments into .Rd documentation files in man/.

The man/ Directory

Documentation files in man/ are generated from roxygen2 comments in R/. You do not write .Rd files by hand — they get overwritten every time you run devtools::document(). The workflow is: write your function with roxygen2 comments, run document(), and the documentation appears.

The tests/ Directory

Formal tests live in tests/testthat/. The testthat package is the standard testing framework for R packages.

# tests/testthat/test-utils.R

library(testthat)
library(mypackage)

test_that("add_one works correctly", {
  expect_equal(add_one(1), 2)
  expect_equal(add_one(c(1, 2)), c(2, 3))
  expect_equal(add_one(numeric()), numeric())
})

R CMD check runs your test suite automatically. A test runner file called testthat.R at the top of tests/ orchestrates everything.

The inst/ Directory

inst/ holds files that get copied verbatim into the installed package’s top-level directory. Use it for sample data files, configuration files, fonts, or any static asset your package needs at runtime. Access files with system.file("myfile", package = "mypackage"). Anything that is not R code or a shipped dataset but needs to exist at runtime belongs in inst/.

The data/ Directory

data/ stores R binary files (.rda) containing datasets shipped with the package. These are the kind you access with data(mtcars). They are lazy-loaded automatically in installed packages. For raw data files you want users to access at runtime, put them in inst/extdata/ instead.

The src/ Directory

src/ holds compiled code — C, C++, or Fortran. Most R packages do not need this. It exists for performance-critical operations where R’s interpreted code is too slow. If your package does not use compiled code, you do not need a src/ directory at all.

The vignettes/ Directory

Vignettes are R Markdown files that provide narrative documentation — tutorials, use-case walkthroughs, and explanations that go beyond what a function reference can offer. Place .Rmd files in vignettes/ and they get built into HTML or PDF during devtools::check() or devtools::build_vignettes(). The rendered output ends up in inst/doc/ after installation. Vignettes are the gold standard for showing how a package works in practice.

The LICENSE File

LICENSE contains the full license text. If you specify License: MIT in DESCRIPTION, the LICENSE file must contain the actual MIT license text. CRAN requires the full text for GPL-family licenses, and it is standard practice for MIT and BSD licenses too.

The Development Loop

Two devtools functions drive the package development workflow.

devtools::load_all() simulates installing the package without actually installing it. It loads all code from R/, runs roxygen2, and sets up a temporary namespace. It is fast — designed for interactive development where you want to test changes immediately.

devtools::load_all(path = ".")

When you are ready to validate everything, devtools::check() runs the full R CMD check suite. This is a comprehensive validation: DESCRIPTION consistency, NAMESPACE validity, code quality, test execution, compiled code compilation, and vignette building. Running check() before submitting to CRAN or sharing your package is not optional — it is how you find the problems that will get your package rejected.

Putting It Together

Here is how the pieces connect. On disk, a package looks like this:

mypackage/
  DESCRIPTION       # Package manifest
  NAMESPACE         # Imports and exports
  R/                # Source code
  man/              # Auto-generated documentation
  tests/
    testthat/
    testthat.R
  inst/
    extdata/
  data/
  vignettes/
  src/
  LICENSE

When the package is built and installed, the contents of inst/ are copied to the package root, vignettes are rendered to inst/doc/, and everything in data/ becomes lazy-loadable. R CMD check enforces this layout. Files or directories outside this structure produce warnings or errors.

The R package structure is a convention that makes R packages portable and reproducible. The same layout works whether you are building a small personal utility, a package for your team, or something you intend to publish on CRAN.

See Also