Submitting to CRAN
Submitting a package to CRAN is the moment your work becomes available to every R user in the world. The process is straightforward if you know what the reviewers look for, and painful if you skip steps. This tutorial walks you through preparing your package, running the right checks, and handling reviewer feedback.
Before You Submit
CRAN has a published policy at cran.r-project.org/web/packages/policies.html. Read it. Not skimming it, not bookmarking it for later — actually reading it. The policy covers naming rules, package structure, licensing, and what is considered acceptable behaviour for a CRAN package. Ignorance of the policy is not an excuse that reviewers accept.
Beyond the policy, the single most important thing you can do is run R CMD check until it produces zero errors, zero warnings, and zero notes. Not almost zero. Zero.
# Run checks the way CRAN will run them
R CMD check --as-cran mypackage_1.0.0.tar.gz
If you are using devtools, the equivalent is:
devtools::check()
Any NOTE from R CMD check --as-cran is a potential rejection reason. The reviewers are strict. A NOTE about missing global bindings or undocumented arguments is not a suggestion — it is something you must fix.
Preparing the DESCRIPTION File
The DESCRIPTION file is the first thing a reviewer looks at. It must be accurate and complete.
The Title field must be a sentence without a period at the end. “What the Package Does” is not acceptable. Neither is “This package does X and Y.” Write something like: “Tools for Reconciling Mismatched Data Frames in Financial Analysis.”
The Description field needs at least one paragraph. A single line is not enough. CRAN reviewers will flag it. Explain what the package does, why someone would use it, and give a concrete use case.
Version numbers must follow semantic versioning: MAJOR.MINOR.PATCH. Starting at 1.0.0 is conventional for a first release.
The License field must name a valid license. If you use MIT, include the standard MIT template in a LICENSE file in the package root:
YEAR: 2026
COPYRIGHT HOLDER: Jane Doe
If you are unsure which license to choose, usethis::use_mit_license() or usethis::use_gpl3_license() will set everything up for you.
Documentation and the NAMESPACE
Every exported function needs roxygen2 documentation. Run devtools::document() to regenerate your NAMESPACE file from the @export tags in your code. Never edit NAMESPACE by hand.
Examples in your documentation must run without error. Use \dontrun{} only for code that genuinely cannot be executed at package build time — an interactive prompt, for instance. Lazy use of \dontrun{} to hide broken examples is a common complaint from CRAN reviewers.
Check that you have no undefined references to variables from other packages. If your code uses dplyr::filter(), make sure importFrom(dplyr, filter) appears in your NAMESPACE, or that you have @importFrom dplyr filter in the roxygen block above the function.
Avoiding Common NOTEs
Two NOTEs come up repeatedly and cause rejections.
Global variable bindings. If you reference a column from a data frame inside a ggplot2 call or a dplyr pipeline, use the .data pronoun:
library(ggplot2)
# Causes NOTE: no visible binding for global variable
ggplot(df, aes(x = mycol)) + geom_bar()
# Good — explicit via .data
ggplot(df, aes(x = .data$mycol)) + geom_bar()
Undeclared dependencies. Every package you import from must appear in either Imports: or Suggests: in DESCRIPTION. library(dplyr) in your code without Imports: dplyr in DESCRIPTION is a violation.
Reverse Dependency Checks
Before submitting, check what happens to packages that depend on yours. The revdepcheck package runs your package against all packages on CRAN that list it as a dependency:
remotes::install_github("r-lib/revdepcheck")
revdepcheck::revdep_check()
revdepcheck::revdep_summary()
This takes time — potentially hours for a popular package. Run it before you submit, not after. Catching a regression in a downstream package before CRAN does is a good sign that you are serious about quality.
Platform-Specific Checks
CRAN runs checks on Linux, Windows, and macOS. You are responsible for verifying your package on all three platforms before you submit.
For Windows, use Win Builder. Upload your .tar.gz and you receive an email with the full check output.
For macOS, CRAN offers the pkgcheck service at cran.r-project.org/submit.html#summary. Upload and wait for results.
Linux you can check locally with R CMD check. If you are on macOS or Windows, set up a Linux virtual machine or use a CI pipeline that runs Linux checks.
Writing the cran-comments File
CRAN reviewers read a plain text file called cran-comments in your package root. Always include it. A minimal example:
## CRAN comments for mypackage 1.0.0
First submission.
R CMD check results:
- Linux (R-devel): 0 errors, 0 warnings, 0 notes
- Windows (win-builder, R-devel): 0 errors, 0 warnings, 0 notes
Reverse dependency check: no issues found.
User-facing changes in this version:
- Added my_function() for doing a thing
- Fixed bug in my_other_function() when input is NA
List every platform you tested on, the results, and what is new in this version. If you are resubmitting after reviewer feedback, explain what you changed and why.
The Submission Form
Once your package passes local checks, build the source tarball:
devtools::build()
This produces a .tar.gz file. Go to cran.r-project.org/submit.html, fill in the web form, and upload your tarball.
You will receive an acknowledgement email immediately. After that, wait. Turnaround varies. Simple packages with clean checks can be accepted within hours. Packages that raise questions may take days or require back-and-forth with a reviewer.
Responding to Reviewer Feedback
If a reviewer writes to you about issues, read every word carefully. They will specify exactly what is wrong and on which platform. Do not argue. Do not explain at length why the check result is acceptable. Fix the problem.
Address each point individually in your reply:
Dear CRAN maintainer,
Thank you for your feedback. I have addressed the following:
1. Added .data:: prefix to all ggplot2 column references in R/plot.R
2. Updated Title in DESCRIPTION to no longer end with a period
3. Added importFrom ggplot2 to NAMESPACE via roxygen2
Updated package attached.
Attach the corrected .tar.gz to the same email thread. Multiple review cycles are normal for first submissions. Do not take it personally. CRAN has thousands of packages to maintain and they are looking out for the entire ecosystem.
After Acceptance
Once your package is on CRAN, it will be checked again on a regular schedule. Maintainer email addresses must stay valid — if your email bounces, CRAN will contact you and eventually archive the package.
Keep your package up to date. If you fix a bug, increment the patch version. If you add a breaking change, bump the major version. Release early and release often.
Common Rejection Triggers
To summarise what gets packages rejected:
R CMD checkerrors or warnings, not just notes- A Title in DESCRIPTION that looks like a placeholder
- A Description field that is one line or empty
- Missing or invalid License field
- Broken examples in documentation
- No
cran-commentsfile - Platform-specific paths or system calls without fallbacks
- Internet access at package load time without
Config/Needs/complainin DESCRIPTION
Run through this list before every submission. It will save you time.
Conclusion
Submitting to CRAN is not mysterious, but it demands attention to detail. Run R CMD check --as-cran until it is clean, write honest cran-comments, test on all three platforms, and respond to reviewers promptly. The process rewards precision.
Once your package is live, every R user can install it with install.packages("yourpackagename"). That is worth getting right.
See Also
- Writing R Packages with devtools — the foundation: structuring, documenting, and building your first package
- Automating Tests with testthat — adding a test suite before submission catches bugs early
- Documenting with pkgdown — build a polished site for your CRAN package