DESCRIPTION and NAMESPACE Files
Every R package sitting in your library has two files at its root that you rarely touch once they’re set up, but which R reads every single time your package loads. The DESCRIPTION file and the NAMESPACE file are the twin foundations of any R package. DESCRIPTION tells R what your package is, who built it, and what it depends on. NAMESPACE tells R which functions your package exposes to users and which functions it borrows from other packages.
Getting these wrong does not produce a helpful error. Instead you get silent failures — functions that seem to vanish, conflicts between packages, or mysterious “object not found” errors. This guide covers what both files contain, how they work together, and the mistakes that trip up even experienced package authors.
The DESCRIPTION File
The DESCRIPTION file is a package manifest written in DCF (Debian Control Format). Each field takes the form Field: value, records are separated by blank lines, and continuation lines are indented with a single space. R CMD build and R CMD check parse this format, so the structure matters more than aesthetics.
Required Fields
Every package on CRAN needs five fields:
- Package — the package name. Must exactly match the folder name or R CMD build will refuse to create the tarball.
- Title — a one-line description. R CMD check enforces title case and no trailing period.
- Description — a longer paragraph explaining what the package does. This is what appears on the CRAN page.
- Version — a three-part number in
MAJOR.MINOR.PATCHformat. During active development, dev versions typically start at0.0.0.9000. - License — use an SPDX identifier. Common choices are
GPL-3,MIT,Apache-2.0, orCC0for public domain. You can also useGPL-3 + file LICENSEif you keep a separate LICENSE file.
Package: mypackage
Title: What My Package Does in Title Case
Version: 0.1.0
Description: A longer description of what this package does, who it's for,
and why it exists. This field can span multiple lines when each continuation
is indented with a space.
License: MIT
Author Information with Authors@R
The Authors@R field lets you describe package authors using the person() function. This is the modern replacement for the old Author and Maintainer fields.
Authors@R: person(
"Jane", "Doe",
email = "jane@example.com",
role = c("aut", "cre"),
comment = c(ORCID = "0000-0000-0000-0000")
)
Valid role codes include aut for author, cre for maintainer (the person R will email when CRAN has a problem), ctb for contributor, cph for copyright holder, and dtc for data contributor. Chain multiple person() calls for teams:
Authors@R: c(
person("Jane", "Doe", role = c("aut", "cre")),
person("John", "Smith", role = "ctb")
)
Declaring Dependencies
The dependency fields are where DESCRIPTION causes the most confusion. There are five of them, and the differences between Imports and Depends trip up nearly every new package author.
| Field | What it does | When to use it |
|---|---|---|
Imports | Loads the package into the namespace at library time. The package is available but not attached to the search path. | Packages your code actively calls. This is the right choice for most dependencies. |
Depends | Loads and attaches the package, so it appears in search(). | Almost never. It pollutes the global namespace and causes hard-to-diagnose conflicts. |
Suggests | Available during development and testing, but not installed for normal users. | Testthat, rmarkdown for vignettes, packages only used in optional features. |
Enhances | Packages your package augments or extends. | Rarely used. |
LinkingTo | Declares that your C or C++ code #includes headers from another package. | Only if you have compiled code depending on another package’s C headers. |
The Imports versus Depends distinction deserves your attention. When you list a package in Imports, it gets loaded into your package’s namespace — your code can call its functions — but it does not appear in the user’s search path. This is good. When you use Depends, the package appears in search(), which means it can mask functions from other packages and produce confusing behaviour. Modern R package development uses Imports almost exclusively.
Imports:
magrittr,
rlang (>= 1.0.0),
purrr
Suggests:
testthat (>= 3.0.0),
knitr,
rmarkdown
Version numbers in dependencies use >= to specify a minimum version. R CMD check will refuse to install the package if the user’s version of a dependency is too old.
The NAMESPACE File
If DESCRIPTION is the package’s birth certificate, NAMESPACE is the front door — it controls what walks in and out. Without a NAMESPACE file, R exports nothing. Your package technically loads, but users cannot access a single function.
Export Directives
The export() directive makes a function available to package users:
export(mean)
export(summarise_group)
You can export everything matching a pattern with exportPattern(), but this has a dangerous default. The common pattern ^[[:alnum:]]+$ matches any alphanumeric function name, which includes internal helpers like .helper_function() that you probably meant to keep private.
A safer approach is to prefix private helpers with a dot and export only what you explicitly name:
export(mean)
export(summarise_group)
exportPattern("^[^\\.]")
This exports everything that does not start with a dot. If you must export pattern-matched names, write the regex deliberately rather than blindly copying the roxygen2 default.
Import Directives
When your package calls functions from another package, you declare those imports in NAMESPACE:
importFrom(magrittr, "%>%")
importFrom(rlang, .data)
importFrom(pkg, fun) is almost always the right choice. It imports exactly the function you need, keeps your namespace readable, and makes dependencies explicit. A bare import(pkg) imports everything the other package exports, which defeats the purpose of namespacing and risks unexpected conflicts.
# The wrong approach — imports everything
import(magrittr)
# The right approach — imports only what you use
importFrom(magrittr, "%>%")
importFrom(dplyr, filter, select, mutate)
Letting roxygen2 Handle NAMESPACE
Writing NAMESPACE by hand is tedious and error-prone. The roxygen2 package generates it from special comments in your R source files. Add #' @export above any function you want to export, and roxygen2 writes the corresponding export() line. Use #' @importFrom pkg fun for imports.
#' Calculate group means
#'
#' @param data A data frame
#' @param group Column to group by
#' @export
#' @importFrom dplyr group_by summarise
group_mean <- function(data, group) {
data |>
group_by({{ group }}) |>
summarise(result = mean(result, na.rm = TRUE))
}
Running devtools::document() or roxygen2::roxygenise() regenerates the NAMESPACE file from these comments. Never edit NAMESPACE directly if you’re using roxygen2 — your changes will be overwritten on the next run.
How They Work Together
DESCRIPTION and NAMESPACE serve different phases of the package lifecycle. When a user runs install.packages() or devtools::install(), R reads DESCRIPTION, downloads dependencies, and places your package in the library. This is the install-time phase.
When a user runs library(mypackage), R reads NAMESPACE, loads your package namespace, and resolves the import and export directives. This is the load-time phase.
The two files must be consistent. If a package appears in DESCRIPTION but has no corresponding importFrom() in NAMESPACE, the package is loaded but none of its exported functions are accessible inside your code. If NAMESPACE references a package not listed in DESCRIPTION, R throws an error at load time.
mypackage/
├── DESCRIPTION ← read at install time: "install these dependencies"
├── NAMESPACE ← read at load time: "wire up these imports and exports"
├── R/
│ └── *.R
└── ...
```text
## Common Mistakes
**Using Depends instead of Imports.** This is the most common DESCRIPTION error. Packages in Depends appear in `search()`, which can mask functions from other packages and produce baffling results. Use Depends only when you genuinely need a package on the search path, which is almost never for modern R code.
**Leaving internal functions exported.** If you use `exportPattern("^[[:alnum:]]+$")` without hiding helpers, your package's internal functions are accessible to users. Prefix private functions with a dot and use `exportPattern("^[^\\.]")` or list exports explicitly.
**Missing importFrom declarations.** The package loads, but calling a dependency's function throws an error. This happens when you use a function from another package in your code but forget to declare the import in NAMESPACE. roxygen2 prevents this by making you declare imports in the source file comments.
**Version numbers without all three parts.** R CMD check requires `MAJOR.MINOR.PATCH`. Writing `1.0` instead of `1.0.0` will fail check.
**Using full import() instead of importFrom().** Importing an entire package namespace clutters your namespace and makes debugging harder. Always import specific functions.
## See Also
- [Package Structure in R](/tutorials/package-structure-r/) — the directory layout of an R package and where these files fit
- [R Basics: Vectors and Types](/tutorials/r-basics-vectors-and-types/) — foundational R concepts you'll use inside your package
- [Functions and Control Flow](/tutorials/functions-and-control-flow/) — writing R functions that go inside packages
## Written
- File: sites/rguides/src/content/tutorials/description-and-namespace.md
- Words: ~1100
- Read time: 6 min
- Topics covered: DESCRIPTION file (DCF format, required fields, Authors@R, dependency types), NAMESPACE file (exports, imports, roxygen2), how they work together, common mistakes
- Verified via: R extensions manual (Writing R Extensions), CRAN incoming check documentation
- Unverified items: none