# Developing R Packages

> 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.

- Skill: `wolf5996/developing-r-packages` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add wolf5996/developing-r-packages`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wolf5996/developing-r-packages/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: wolf5996 (https://skillmd.com/u/wolf5996)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/wolf5996/developing-r-packages

---


# 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 |

