Skip to contents

usethis::use_standalone is designed for R package development, to share useful functions between packages without creating hard dependencies. pkgtools extends this concept to analysis projects — plain Git repositories that don’t have package infrastructure (no DESCRIPTION, no namespace).

This vignette explains when and how to use standalone files, how they are managed, and how pkgtools integrates them into your workflow.

When to Use Standalone Files

Standalone files are useful when:

  • You have utility functions reused across multiple projects
  • Creating a full package is overkill for the amount of shared code
  • You want the utility code to be version-locked into each project (improving reproducibility)
  • Your projects are managed by renv or have no dependency manager at all

Standalone files are not suitable when:

  • You need formal package exports and NAMESPACE management
  • The codebase is large enough to warrant a dedicated package
  • You want CRAN or r-universe deployment

Project Structure

A standalone file repository looks like this:

my-standalone-repo/
├── README.md
├── R/
│   ├── standalone-utils.R
│   ├── standalone-plot-utils.R
│   └── standalone-data-utils.R

Each .R file in R/ whose name starts with standalone- is a candidate standalone file. The file contains:

  1. A YAML metadata block between # --- dividers
  2. R code with no package dependencies (or with dependencies declared in YAML)

Example standalone file:

# Standalone file: do not edit by hand
# Source: https://github.com/author/repo/blob/HEAD/R/standalone-utils.R
# Generated by: pkgtools::use_standalone("author/repo", "utils")
# ----------------------------------------------------------------------
#
# ---
# repo: author/repo
# file: standalone-utils.R
# last-updated: '2025-01-15'
# license: https://unlicense.org
# imports:
# - dplyr
# - rlang
# ---

#' My utility function
#'
#' @param x input vector
#' @return processed output
my_util <- function(x) {
  dplyr::filter(x, !is.na(.))
}

The YAML metadata block contains:

  • repo: GitHub repository spec (organisation/project)
  • file: the standalone filename
  • last-updated: date the file was last modified
  • license: license URL (defaults to https://unlicense.org)
  • dependencies: other standalone files in the same repo (for cross-references)
  • imports: R packages required by this file

Importing Standalone Files

Use use_standalone() to import a standalone file into your project:

# Import from a remote GitHub repository:
pkgtools::use_standalone("author/repo", "my-standalone")

# Use a local copy (if a matching repo exists in ~/Git/):
pkgtools::use_standalone("my-standalone-repo", "my-standalone")

This function:

  1. Downloads the standalone file (from remote or local)
  2. Prepends a header with source information and generation command
  3. Renames the file: standalone-foo.R becomes import-standalone-foo.R
  4. Resolves dependencies: if the standalone imports other standalones, they are recursively downloaded
  5. Manages imports: based on your project type:
    • Package projects: adds packages to DESCRIPTION via use_package()
    • renv-managed projects: installs packages and snapshots
    • Plain projects: adds a fenced block to .RProfile that checks for required packages and sources all local standalones

Automatic Header Generation

The imported file begins with a header that documents its provenance:

# Standalone file: do not edit by hand
# Source: https://github.com/author/repo/blob/HEAD/R/standalone-utils.R
# Generated by: pkgtools::use_standalone("author/repo", "utils")
# ----------------------------------------------------------------------
#

This header allows you to:

  • Trace the origin of any shared utility
  • Regenerate the file with the correct import command
  • Identify when the file was last synchronized

Import Management in Non-Package Projects

For projects without a DESCRIPTION file, pkgtools manages imports through a fenced block in .RProfile:

# Check dependencies:
if (!requireNamespace("dplyr", quietly = TRUE)) {
  message("Package `dplyr` must be installed.")
}

# Deal with stats/dplyr issues:
if (!requireNamespace("conflicted", quietly = TRUE)) {
  message("Package `conflicted` must be installed.")
} else {
  conflicted::conflicts_prefer(
    dplyr::filter(),
    dplyr::lag(),
    .quiet = TRUE
  )
}

# Load standalones:
try(source("R/import-standalone-utils.R"))
try(source("R/import-standalone-plot-utils.R"))

This block is managed automatically by pkgtools — adding a new standalone updates the block. It also handles conflicted::conflicts_prefer() for common tidyverse conflicts.

Updating Standalone Files

When editing a standalone file in RStudio, use update_standalone() to refresh its metadata:

# From RStudio with a standalone file open:
pkgtools::update_standalone("author/repo")

This function:

  1. Parses the existing YAML metadata block
  2. Detects any changes to the file
  3. Updates the last-updated timestamp
  4. Recalculates package imports from the code
  5. Prompts for changes via the merge_code() interactive diff viewer

If you haven’t provided a repo argument, the function tries to detect it from the file’s existing metadata or remote.

Syncing to Master

When developing a standalone file, you may want to keep your local copy in sync with a “master” version in a dedicated repository. Use sync_standalone_to_master() to push your local changes back:

# From RStudio with a standalone file open:
pkgtools::sync_standalone_to_master()

This function:

  1. Reads the YAML metadata to find the master repository
  2. Finds the master file path in your local Git directory
  3. Commits any pending changes to the master
  4. Updates the last-updated date
  5. Merges changes if both local and master have been modified

Workflow Example

1. Create standalone in ~/Git/my-standalones/R/standalone-utils.R
2. Import into project with use_standalone("my-standalones", "utils")
3. Edit the imported file in the project
4. Use update_standalone() to refresh metadata
5. Use sync_standalone_to_master() to push changes to master repo
6. Other projects can then import the updated version

Cross-Repository Dependencies

Standalone files can depend on other standalone files within the same repository. This is declared in the YAML metadata:

# ---
# repo: author/repo
# file: standalone-plot-utils.R
# dependencies:
#   - standalone-utils.R
#   - standalone-data-utils.R
# ---

When you import standalone-plot-utils.R, pkgtools automatically:

  1. Downloads the listed dependencies first
  2. Resolves transitive dependencies recursively
  3. Ensures all required standalones are available

This creates a dependency graph of standalone files, similar to package dependencies, but managed entirely through Git repositories.

Standalone Import Management

The standalone_imports() internal function collects all package imports from every standalone file in a project. This is used to:

  • Deduplicate package dependencies
  • Select maximum required version across all standalones
  • Generate the DESCRIPTION or .RProfile import block

For package projects, the imports are added to DESCRIPTION. For renv projects, packages are installed and snapshotted. For plain projects, they appear in the .RProfile fenced block.

Best Practices

  1. Use descriptive names: standalone-plot-utils.R is better than standalone-utils.R
  2. Keep files focused: each standalone should handle one coherent responsibility (e.g., all plot formatting, all data cleaning)
  3. Document dependencies: always list required packages in the YAML imports field
  4. Version lock: by embedding the standalone version in the project, you ensure reproducible analyses even if the master repository changes
  5. Regular syncs: use sync_standalone_to_master() to keep local and master in sync during active development
  6. License clearly: choose a license in the YAML metadata; https://unlicense.org is the default (public domain)

Troubleshooting

“No standalone files found”

This error occurs when the target repository has no files in R/ starting with standalone-. Check that:

  • The file exists at R/standalone-*.R in the repository
  • The repository is accessible (correct host, credentials)

“Local standalone file not found”

When using a local copy, the function searches ~/Git/<repo-spec>/R/. Check that:

  • The Git repository exists in your Git directory
  • The standalone file is in the R/ subdirectory
  • The filename matches standalone-*.R

HTTP/2 Issues

Some Git repositories require HTTP/2 for large file transfers:

Sys.setenv("GIT_CONFIG_PARAMETERS" = "http.version=2")

Or set it globally:

git config --global http.version HTTP/2

Missing Dependencies

If a standalone requires a package that is not installed:

  • Package projects: use_standalone() adds it to DESCRIPTION
  • renv projects: the package is installed and snapshotted
  • Plain projects: a message is displayed when the project is opened, and the fenced block in .RProfile checks at load time
Function Purpose
use_standalone() Import a standalone file
update_standalone() Update metadata in RStudio
sync_standalone_to_master() Push edits to master
standalone_choose() Interactive file selection (internal)
as_standalone_file() Sanitise filename (internal)
standalone_header() Generate header comment (internal)
standalone_imports() Collect imports from all standalones (internal)

Limitations

  • Standalone files work best for small utility functions (under ~200 lines)
  • They don’t support formal S3/S4 method registration
  • Cross-language dependencies (Python, JavaScript) are not managed
  • Large standalone files may slow down project loading in plain projects

Funding

The authors gratefully acknowledge the support of the UK Research and Innovation AI programme of the Engineering and Physical Sciences Research Council EPSRC grant EP/Y028392/1.