Prepare R packages for CRAN submission by checking for common ad-hoc requirements not caught by devtools::check(). Use when: (1) Preparing a package for first CRAN release, (2) Preparing a package update for CRAN resubmission, (3) Reviewing a package to ensure CRAN compliance, (4) Responding to CRAN reviewer feedback. Covers documentation requirements, DESCRIPTION field standards, URL validation, examples, and administrative requirements.
apm install @posit-dev/cran-extrachecks[](https://apm-p1ls2dz87-atlamors-projects.vercel.app/packages/@posit-dev/cran-extrachecks)---
name: cran-extrachecks
description: >
Prepare R packages for CRAN submission by checking for common ad-hoc requirements not caught by devtools::check(). Use when: (1) Preparing a package for first CRAN release, (2) Preparing a package update for CRAN resubmission, (3) Reviewing a package to ensure CRAN compliance, (4) Responding to CRAN reviewer feedback. Covers documentation requirements, DESCRIPTION field standards, URL validation, examples, and administrative requirements.
metadata:
author: Garrick Aden-Buie (@gadenbuie)
version: "1.0"
license: MIT
---
# CRAN Extra Checks
Help R package developers prepare packages for CRAN submission by systematically checking for common ad-hoc requirements that CRAN reviewers enforce but `devtools::check()` doesn't catch.
## Workflow
1. **Initial Assessment**: Ask user if this is first submission or resubmission
2. **Run Standard Checklist**: Work through each item systematically (see below)
3. **Identify Issues**: As you review files, note specific problems
4. **Propose Fixes**: Suggest specific changes for each issue found
5. **Implement Changes**: Make edits only when user approves
6. **Verify**: Confirm all changes are complete
## Standard CRAN Preparation Checklist
Work through these items systematically:
1. **Create NEWS.md**: Run `usethis::use_news_md()` if not already present
2. **Create cran-comments.md**: Run `usethis::use_cran_comments()` if not already present
3. **Review README**:
- Ensure it includes install instructions that will be valid when the package is accepted to CRAN (usually `install.packages("pkgname")`).
- Check that it does not contain relative links. This works on GitHub but will be flagged by CRAN. Use full URLs to package documentation or remove the links.
- Does the README clearly explain the package purpose and functionality?
- **Important**: If README.Rmd exists, edit ONLY README.Rmd (README.md will be overwritten), then run `devtools::build_readme()` to re-render README.md
4. **Proofread DESCRIPTION**: Carefully review `Title:` and `Description:` fields (see detailed guidance below)
5. **Check function documentation**: Verify all exported functions have `@return` and `@examples` (see detailed guidance below)
6. **Verify copyright holder**: Check that `Authors@R:` includes a copyright holder with role `[cph]`
7. **Review bundled file licensing**: Check licensing of any included third-party files
8. **Run URL checks**: Use `urlchecker::url_check()` and fix any issues
## Detailed CRAN Checks
### Documentation Requirements
**Return Value Documentation (Strictly Enforced)**
CRAN now strictly requires `@return` documentation for all exported functions. Use the roxygen2 tag `@return` to document what the function returns.
- Required even for functions marked `@keywords internal`
- Required even if function returns nothing - document as `@return None` or similar
- Must be present for every exported function
Example:
```r
# Missing @return - WILL BE REJECTED
#' Calculate sum
#' @export
my_sum <- function(x, y) {
x + y
}
# Correct - includes @return
#' Calculate sum
#' @param x First number
#' @param y Second number
#' @return A numeric value
#' @export
my_sum <- function(x, y) {
x + y
}
# For functions with no return value
#' Print message
#' @param msg Message to print
#' @return None, called for side effects
#' @export
print_msg <- function(msg) {
cat(msg, "\n")
}
```
**Examples for Exported Functions**
If your exported function has a meaningful return value, it will almost definitely require an `@examples` section. Use the roxygen2 tag `@examples`.
- Required even for functions marked `@keywords internal`
- Exceptions exist for functions used purely for side effects (e.g., creating directories)
- Examples must be executable
**Un-exported Functions with Examples**
If you write roxygen examples for un-exported functions, you must either:
1. Call them with `:::` notation: `pkg:::my_fun()`
2. Use `@noRd` tag to suppress `.Rd` file creation
**Using `\dontrun{}` Sparingly**
`\dontrun{}` should only be used if the example really cannot be executed (e.g., missing additional software, API keys, etc.).
- If showing an error, wrap the call in `try()` instead
- Consider custom predicates (e.g., `googlesheets4::sheets_has_token()`) with `if ()` blocks
- Sometimes `interactive()` can be used as the condition
- Lengthy examples (> 5 sec) can use `\donttest{}`
**Never Comment Out Code in Examples**
```r
# BAD - Will be rejected
#' @examples
#' # my_function(x) # Don't do this!
```
CRAN's guidance: "Examples/code lines in examples should never be commented out. Ideally find toy examples that can be regularly executed and checked."
**Guarding Examples with Suggested Packages**
Use `@examplesIf` for entire example sections requiring suggested packages:
```r
#' @examplesIf rlang::is_installed("dplyr")
#' library(dplyr)
#' my_data %>% my_function()
```
For individual code blocks within examples:
```r
#' @examples
#' if (rlang::is_installed("dplyr")) {
#' library(dplyr)
#' my_data %>% my_function()
#' }
```
### DESCRIPTION Title Field
CRAN enforces strict Title requirements:
**Use Title Case**
Capitalize all words except articles like 'a', 'the'. Use `tools::toTitleCase()` to help format.
**Avoid Redundancy**
Common phrases that get flagged:
- "A Toolkit for" → Remove
- "Tools for" → Remove
- "for R" → Remove
Examples:
```r
# BAD
Title: A Toolkit for the Construction of Modeling Packages for R
# GOOD
Title: Construct Modeling Packages
# BAD
Title: Command Argument Parsing for R
# GOOD
Title: Command Argument Parsing
```
**Quote Software/Package Names**
Put all software and R package names in single quotes:
```r
# GOOD
Title: Interface to 'Tiingo' Stock Price API
```
**Length Limit**
Keep titles under 65 characters.
### DESCRIPTION Description Field
**Never Start With Forbidden Phrases**
CRAN will reject descriptions starting with:
- "This package"
- Package name
- "Functions for"
```r
# BAD
Description: This package provides functions for rendering slides.
Description: Functions for rendering slides to different formats.
# GOOD
Description: Render slides to different formats including HTML and PDF.
```
**Expand to 3-4 Sentences**
Single-sentence descriptions are insufficient. Provide a broader description of:
- What the package does
- Why it may be useful
- Types of problems it helps solve
```r
# BAD (too short)
Description: Render slides to different formats.
# GOOD
Description: Render slides to different formats including HTML and PDF.
Supports custom themes and progressive disclosure patterns. Integrates
with 'reveal.js' for interactive presentations. Designed for technical
presentations and teaching materials.
```
**Quote Software Names, Not Functions**
```r
# BAD
Description: Uses 'case_when()' to process data.
# GOOD
Description: Uses case_when() to process data with 'dplyr'.
```
Software, package, and API names get single quotes (including 'R'). Function names do not.
**Expand All Acronyms**
All acronyms must be fully expanded on first mention:
```r
# BAD
Description: Implements X-SAMPA processing.
# GOOD
Description: Implements Extended Speech Assessment Methods Phonetic
Alphabet (X-SAMPA) processing.
```
**Publication Titles Only in Double Quotes**
Only use double quotes for publication titles, not for phrases or emphasis:
```r
# BAD
Description: Handles dates like "the first Monday of December".
# GOOD
Description: Handles dates like the first Monday of December.
```
### URL and Link Validation
**All URLs Must Use HTTPS**
CRAN requires `https://` protocol for all URLs. HTTP links will be rejected.
```r
# BAD
URL: http://paleobiodb.org/
# GOOD
URL: https://paleobiodb.org/
```
**No Redirecting URLs**
CRAN rejects URLs that redirect to other locations. Example rejection:
```
Found the following (possibly) invalid URLs:
URL: https://h3geo.org/docs/core-library/coordsystems#faceijk-coordinates
(moved to https://h3geo.org/docs/core-library/coordsystems/)
```
**Use urlchecker Package**
```r
# Find redirecting URLs
urlchecker::url_check()
# Automatically update to final destinations
urlchecker::url_update()
```
**Ignore URLs That Will Exist After Publication**
Some URLs that don't currently resolve will exist once the package is published on CRAN. These should NOT be changed:
- CRAN badge URLs (e.g., `https://cran.r-project.org/package=pkgname`)
- CRAN status badges (e.g., `https://www.r-pkg.org/badges/version/pkgname`)
- CRAN check results (e.g., `https://cranchecks.info/badges/pkgname`)
- Package documentation URLs on r-universe or pkgdown sites that deploy after release
When `urlchecker::url_check()` flags these URLs, leave them as-is. They are aspirational URLs that will work once the package is on CRAN.
**Check for Invalid File URIs**
Relative links in README must exist after package build. Common issue:
```
Found the following (possibly) invalid file URI:
URI: CODE_OF_CONDUCT.md
From: README.md
```
This occurs when files are in `.Rbuildignore`. Solutions:
1. Remove file from `.Rbuildignore`
2. Use `usethis::use_code_of_conduct()` which generates sections without relative links
### Administrative Requirements
**Copyright Holder Role**
Always add `[cph]` role to Authors field, even if you're the only author:
```r
# Required
Authors@R: person("John", "Doe", role = c("aut", "cre", "cph"))
```
**Posit-Supported Packages**
For packages in Posit-related GitHub organizations (posit-dev, rstudio, r-lib, tidyverse, tidymodels) or maintained by someone with a @posit.co email address, include Posit Software, PBC as copyright holder and funder:
```r
Authors@R: c(
person("Jane", "Doe", role = c("aut", "cre"),
email = "jane.doe@posit.co"),
person("Posit Software, PBC", role = c("cph", "fnd"),
comment = c(ROR = "03wc8by49"))
)
```
**LICENSE Year**
Update LICENSE year to current submission year:
```r
# If LICENSE shows 2024 but submitting in 2026
# Update: 2024 → 2026
```
**Method References**
CRAN may ask:
> If there are references describing the methods in your package, please add these in the description field...
If there are no references, reply to the email explaining this. Consider adding a preemptive note in `cran-comments.md`:
```markdown
## Method References
There are no published references describing the methods in this package.
The package implements original functionality for [brief description].
```
## Key Files to Review
Work through these files systematically:
- **DESCRIPTION**: Title, Description, Authors@R, URLs, License year
- **R/*.R**: Function documentation (`@return`, `@examples`, `@examplesIf`, `@noRd`)
- **README.Rmd** (if exists): Edit this file (NOT README.md), then run `devtools::build_readme()`
- **README.md**: Review for install instructions, relative links, URLs. Only edit directly if no README.Rmd exists
- **cran-comments.md**: Preemptive notes for reviewers
- **NEWS.md**: Version notes for this release
- **.Rbuildignore**: Files referenced in README
## Common Fix Patterns
**DESCRIPTION Title:**
```r
# Before
Title: A Toolkit for the Construction of Modeling Packages for R
# After
Title: Construct Modeling Packages
```
**DESCRIPTION Description:**
```r
# Before
Description: This package provides functions for rendering slides.
# After
Description: Render slides to different formats including HTML and PDF.
Supports custom themes and progressive disclosure. Integrates with
'reveal.js' for interactive presentations.
```
**Function Documentation:**
```r
# Before - Missing @return
#' Calculate total
#' @param x Values
#' @export
calc_total <- function(x) sum(x)
# After - Complete documentation
#' Calculate total
#' @param x Numeric values to sum
#' @return A numeric value representing the sum
#' @examples
#' calc_total(1:10)
#' @export
calc_total <- function(x) sum(x)
```
## Useful Tools
- `tools::toTitleCase()` - Format titles with proper capitalization
- `urlchecker::url_check()` - Find problematic URLs
- `urlchecker::url_update()` - Fix redirecting URLs
- `usethis::use_news_md()` - Create NEWS.md
- `usethis::use_cran_comments()` - Create cran-comments.md
- `devtools::build_readme()` - Re-render README.md from README.Rmd
- `usethis::use_code_of_conduct()` - Add CoC without relative links
- `usethis::use_build_ignore()` - Ignore files in R package build
- `usethis::use_package()` - Add a package dependency to DESCRIPTION
- `usethis::use_tidy_description()` - Tidy up DESCRIPTION formatting
## Final Verification Checklist
Use this checklist to ensure nothing is missed before submission:
### Files and Structure
- [ ] `NEWS.md` exists and documents changes for this version
- [ ] `cran-comments.md` exists with submission notes
- [ ] If README.Rmd exists, it was edited (not README.md) and `devtools::build_readme()` was run
- [ ] README includes valid install instructions (`install.packages("pkgname")`)
- [ ] README has no relative links (all links are full URLs or removed)
### DESCRIPTION File
- [ ] `Title:` uses title case
- [ ] `Title:` has no redundant phrases ("A Toolkit for", "Tools for", "for R")
- [ ] `Title:` quotes all software/package names in single quotes
- [ ] `Title:` is under 65 characters
- [ ] `Description:` does NOT start with "This package", package name, or "Functions for"
- [ ] `Description:` is 3-4 sentences explaining purpose and utility
- [ ] `Description:` quotes software/package/API names (including 'R') but NOT function names
- [ ] `Description:` expands all acronyms on first mention
- [ ] `Description:` uses double quotes only for publication titles
- [ ] `Authors@R:` includes copyright holder with `[cph]` role
- [ ] For Posit packages: Includes `person("Posit Software, PBC", role = c("cph", "fnd"), comment = c(ROR = "03wc8by49"))`
- [ ] LICENSE year matches current submission year
### Function Documentation
- [ ] All exported functions have `@return` documentation
- [ ] All exported functions with meaningful returns have `@examples`
- [ ] No example sections use commented-out code
- [ ] Examples avoid `\dontrun{}` unless truly necessary
- [ ] Examples requiring suggested packages use `@examplesIf` or `if` guards
- [ ] Un-exported functions with examples use `:::` notation or `@noRd`
### URLs and Links
- [ ] `urlchecker::url_check()` was run
- [ ] All URLs use https protocol (no http links)
- [ ] No redirecting URLs (except aspirational CRAN badge URLs)
- [ ] Aspirational URLs (CRAN badges, etc.) are left as-is
- [ ] No relative links in README that reference `.Rbuildignore` files
### Optional but Recommended
- [ ] If concerns about method references, added preemptive note to `cran-comments.md`
- [ ] Reviewed bundled file licensing if including third-party files