| name | developing-r-packages |
| description | Use when creating, modifying, or maintaining R packages. Use when editing R/ source files, DESCRIPTION, NAMESPACE, roxygen2 documentation, tests, or CI workflows in an R package context. Use when adding functions, bumping versions, managing dependencies, or preparing releases. |
Developing R Packages
Overview
Codified workflow for R package development following the R Packages book (Wickham & Bryan, 2e) and conventions established in BadranSeq. Covers the edit-document-check-commit cycle, dependency management, versioning, and release workflow.
Development Cycle
Every code change follows this sequence before committing:
Edit R/ files → devtools::document() → devtools::check() → git add → git commit
document() regenerates NAMESPACE and man/*.Rd files from roxygen2 comments
check() runs R CMD check locally — expect 0 errors, 0 warnings
- Known NOTEs (license format, timestamp) are acceptable
- Never commit if check fails — fix first, then commit
NAMESPACE & roxygen2
- NAMESPACE is auto-generated by roxygen2 — never edit it manually
- Use
@export to make functions available to users
- Use
@description, @param, @return, @examples for documentation
- Run
devtools::document() after any roxygen comment change
Dependencies
Imports vs Suggests
| Field | Purpose | Access pattern |
|---|
Imports | Required at runtime, auto-installed | pkg::fn() in source code |
Suggests | Optional (testing, extended features) | pkg::fn() — never @importFrom |
Depends | Only for R version floor | R (>= x.y) |
Rules
- Always use
pkg::fn() syntax for external functions — no exceptions
- Only use
@importFrom for operators (e.g., %>%) that can't use ::
- Add dependencies via
usethis::use_package("pkg") (defaults to Imports)
- For Suggests:
usethis::use_package("pkg", type = "Suggests")
- Never call
library() or require() inside package code
Versioning
Format: <major>.<minor>.<patch> — always . separator, always 3 components.
| Component | When to bump | Example |
|---|
| Major | Breaking changes to existing API | 1.0.0 → 2.0.0 |
| Minor | New exported functions (backward-compatible) | 1.3.1 → 1.4.0 |
| Patch | Bug fixes, adjustments to existing functions | 1.3.1 → 1.3.2 |
After bumping: update DESCRIPTION, README.Rmd install instructions, re-knit README.md.
Release Workflow
1. Bump version in DESCRIPTION
2. Update README.Rmd version references
3. devtools::build_readme()
4. devtools::check() # must pass
5. git commit + push
6. git tag -a vX.Y.Z -m "BadranSeq X.Y.Z"
7. git push --tags
8. gh release create vX.Y.Z # with release notes
Data in Packages
- User-facing data:
usethis::use_data(obj) → saved to data/obj.rda
- Document datasets in
R/data.R using roxygen2 (@format, @source) — never @export
- Preparation scripts go in
data-raw/
LazyData: true in DESCRIPTION enables lazy loading
Testing
- Initialize with
usethis::use_testthat(3) (edition 3)
- Match test files to source:
R/foo.R → tests/testthat/test-foo.R
- Create tests with
usethis::use_test("foo")
- Run with
devtools::test() or Ctrl+Shift+T
Lifecycle Badges
- Set up with
usethis::use_lifecycle() (one-time)
- Badge in
@description: `r lifecycle::badge("stable")`
- Only badge functions whose stage differs from the package's stage
- Stages: experimental → stable → deprecated/superseded
pkgdown Site
- Never build locally — CI handles it via GitHub Actions after push
- Fix source files (roxygen, vignettes) and push; CI does the rest
CI (GitHub Actions)
- R CMD check runs on push to main via
.github/workflows/R-CMD-check.yaml
- Standard matrix: macOS, Windows, Ubuntu release, Ubuntu devel
- Drop
oldrel-1 if upstream deps require current R
README
README.Rmd is the source — contains live R code chunks
README.md is the rendered output — never edit directly
- Re-knit with
devtools::build_readme() after changes
Common Mistakes
| Mistake | Fix |
|---|
| Editing NAMESPACE manually | Let roxygen2 generate it via document() |
@importFrom for Suggests packages | Use pkg::fn() instead |
Committing without check() | Always document → check → commit |
library() inside package code | Use pkg::fn() or @importFrom |
| Editing README.md directly | Edit README.Rmd, then build_readme() |
| Building pkgdown locally | Let CI handle it |
Version as 1.3 instead of 1.3.0 | Always use 3 components |