Keep your Packages Up to Date
I like to keep the packages in my library up to date. At the same time, I would like to know what has changed in a package before I install an update. Furthermore, I do not want to build packages from source. In this article, I share my approach and the associated code.
Recently, Dirk Eddelbüttel wrote a post about The Two Cultures of Deploying Statistical Software in which he promotes keeping your R packages up-to-date.
R^4 Post #049: The Two Cultures of Deploying Statistical Software On how software may get installed but rarely updated dirk.eddelbuettel.com/blog/2025/06... #rstats #r2u
— Dirk Eddelbuettel (@eddelbuettel.com) June 6, 2025 at 3:50 AM
[image or embed]
I was pleased to see this because I feel the same way and it reminded me that I wanted to share the code I developed for it. That’s what I’m going to do below.
First, let’s keep it simple. If you want to update your packages, you can simply run update.packages()
as advertised by Dirk.
I have several reasons why I do not simply do this:
I don’t want to install packages from source because I don’t want to run into any long compilation processes
I want to know before installation for which packages updates are available and
I want to know what has changed via the associated package NEWS.
update.packages()
only helps me to a limited extent. I can see in the console afterwards which packages have been installed and I have - theoretically - the possibility to retrieve the package news via news()
. But honestly, who does that? Have you ever heard of news()
before today?
So I wrote my own helper. My function update_my_pkgs()
lives in my personal R package and offers me the following advantages.
As shown in Figure 1, I can see in an overview
for which packages an update is available and how big the version jump is,
the host of the file (e.g. if I have set repos other than CRAN via
options(“repos”)
)a hyperlink to the NEWS page of the respective package so that I can quickly find out about changes.

Also, only the binary packages are shown by default, so I don’t accidentally start installing from source. This also means that it can take a few days for a CRAN update to appear in the list, because the binaries available there may still belong to the old version.
Before installing updates, you are asked interactively whether the updates should really be installed. If yes, then the installation starts in a background job so that the session is not blocked. This is the point at which I actually call update.packages()
!
And if you are a fan of the package manager pak, you also have the option of running the installation via it.
Now enough of the long explanations. Figure 2 shows a live example running the code. The corresponding code is given below.
Since I do not publish the code via a package, I do not document arguments or check dependencies. It is therefore important to know that the code only works if the following packages are available on your computer: cli
, curl
, glue
, job
, pak
, purrr
, rlang
<- function(
update_my_pkgs lib.loc = .libPaths()[1],
repos = getOption("repos"),
type = c("binary", "source"),
with_pak = TRUE,
...
) {# make sure all required packages are installed
# it's ok to remove this line for slightly faster performance
::check_installed(c("cli", "curl", "glue", "job", "pak", "purrr", "rlang"))
rlang
<- rlang::arg_match(type)
type <- old.packages(lib.loc = lib.loc, repos = repos, type = type, ...)
old <- as.data.frame(old)
behind
# Exit early if no packages are behind
if (nrow(behind) == 0) {
::cli_alert_success("All {.val {type}} packages are up-to-date!")
clireturn(invisible(NULL))
}
# The following runs only if there are packages behind
::cli_alert_info(
cli"There {cli::qty(nrow(behind))}{?is a/are} {type} \\
{cli::qty(nrow(behind))}update{?s} for the following {cli::no(nrow(behind))} \\
package{?s} available:"
)
# This prints an overview of packages that are behind. The form is
# {pkg}: {installed version} -> {repo version} at {source url} see {NEWS url}
# or {repo url}
::cat_bullet(
cliformat(behind$Package),
": ",
format(behind$Installed),
" -> ",
format(behind$ReposVer),
" at ",
::style_hyperlink(
cliformat(
<- purrr::map_chr(
host $Repository,
behind~ curl::curl_parse_url(.x)$host
)
),$Repository
behind
)," see ",
# create hyperlink to news file of the CRAN mirror in r-universe
# or directly in the r-universe repo
::style_hyperlink(
cli"NEWS",
ifelse(
<- grepl("r-universe\\.dev", behind$Repository),
runiverse ::glue("https://{host}/{behind$Package}/NEWS"),
glue::glue("https://cran.r-universe.dev/{behind$Package}/NEWS")
glue
)
)," or ",
# create hyperlink to CRAN or to r-universe
::style_hyperlink(
cliifelse(runiverse, "RUNIV", "CRAN"),
::glue("https://{host}/{behind$Package}"),
glue::glue("https://cran.r-project.org/package={behind$Package}")
glue
)
)::cat_rule()
cli
# Ask user if they would like to update now and wait for answer
::cli_alert_info("Install {cli::qty(nrow(behind))}{?it/them} now? (Y/n)")
cli<- readline()
ans
# If the user would like to update the "behind" packages, we will run
# the update in a background job
# The arg `with_pak` decides whether the update will run with the help
# of the pak package or with base R
if (tolower(ans) %in% c("", "y", "yes", "yeah", "yep")) {
::empty(
job
{if (isFALSE(with_pak)) {
update.packages(
lib.loc = lib.loc,
repos = repos,
ask = FALSE,
oldPkgs = old,
type = type
)else if (isTRUE(with_pak)) {
} # pak has no repos argument so we have to set it
# through options
options(repos = repos)
::pak(old[, 1])
pak
}
},import = c("lib.loc", "repos", "old", "type", "with_pak"),
title = "Updating Your Package(s)..."
)
}# return behind invisibly
invisible(behind)
}
Finally, Peter Dalgaard - an R Core developer - provides us with another scenario for when update.packages()
should not be used.
#rstats folks: If you have a habit of updating your library after an R upgrade by copying the old one and run update.packages(). --> DON'T DO THAT <-- Especially not with 4.5.0. You will overwrite base packages with ones from a previous version and as these are not on CRAN, things go bad.
— Peter Dalgaard (@pdalgd.bsky.social) April 24, 2025 at 7:29 PM