Input validation in R with rlang

VerifiedSafe

Validate function inputs in R using rlang's check_* functions to produce clear, actionable error messages.

Sby Skills Guide Bot
DevelopmentIntermediate
7/23/2026
Claude CodeCursorWindsurfCopilotCodex
#r#input-validation#rlang#tidyverse#error-messages

Recommended for

Automated summary

Guides input validation in R functions using rlang's type checkers and the standalone file of check_* helpers, with validation placed at exported entry points.

Strengths

  • Gives a detailed reference of scalar and vector check_* functions and their arguments (allow_na, allow_null, min, max, allow_infinite)
  • Clarifies where to validate: at package boundaries such as exported functions and functions consuming external data, not everywhere
  • Documents companion helpers arg_match(), check_exclusive() and check_required() with realistic error output
  • Follows tidyverse style and flags the usethis::use_standalone("r-lib/rlang", "types-check") prerequisite

Limitations

  • Assumes the standalone file is already installed and rlang imported, without walking through the full setup
  • Covers only rlang's built-in checkers; validating custom classes means writing your own check_ functions
  • Does not address non-package contexts such as standalone R scripts or base R alternatives
When to use it

Use it when writing or reviewing an exported R function that needs clear, actionable argument validation.

When not to use it

Skip it for a one-off script outside a package where adding rlang and a standalone file dependency is overkill.

Security analysis

Safe
Quality score87/100

The skill is pure documentation about R input-validation idioms using rlang's exported check_* helpers and the rlang standalone types-check file. It contains no shell commands, no network calls, no credential/secret handling, no file mutations, and no obfuscated payloads. The only external dependency mentioned is the standard usethis::use_standalone("r-lib/rlang", "types-check") call, and the text explicitly instructs confirming with the user before running it, which is a reasonable guardrail. Execution risk is negligible.

No concerns found

How this analysis is made

Examples

Validate a new exported function
Add input validation to this exported R function using rlang's check_* helpers: `fetch_rows <- function(con, table, limit = 100, cols = NULL) { ... }`. `table` must be a single non-empty string, `limit` a whole number between 1 and 1000, and `cols` a character vector or NULL.
Review existing validation code
Review the validation code at the top of this R function and tell me whether it follows tidyverse style — check that NA and NULL handling are correct and that I'm validating at the right boundary rather than inside internal helpers.
Write a custom check helper
Write a custom check_ function in the style of rlang's check_* helpers that validates a named list of column specs, producing error messages like `check_col_specs(x)` must be a named list of strings, not the number 5. Show how to use the `call` and `arg` arguments correctly.

name: types-check description: Validate function inputs in R using a standalone file of check_* functions. Use when writing exported R functions that need input validation, reviewing existing validation code, or when creating new input validation helpers.

Input validation in R functions

This skill describes tidyverse style for validating function inputs. It focuses on rlang's exported type checkers along with the standalone file of check_* functions. These functions are carefully designed to produce clear, actionable error messages:

check_string(123)
#> Error: `123` must be a single string, not the number 123.

check_number_whole(3.14, min = 1, max = 10)
#> Error: `3.14` must be a whole number, not the number 3.14.

It assumes that the user has already run usethis::use_standalone("r-lib/rlang", "types-check"), and imports rlang in their package. If not, confirm with the user before continuing.

Function reference

Scalars (single values)

Use scalar checkers when arguments parameterise the function (configuration flags, names, single counts), rather than represent vectors of user data. They all assert a single value.

  • check_bool(): Single TRUE/FALSE (use for flags/options)
  • check_string(): Single string (allows empty "" by default)
  • check_name(): Single non-empty string (for variable names, symbols as strings)
  • check_number_whole(): Single integer-like numeric value
  • check_number_decimal(): Single numeric value (allows decimals)

By default, scalar checkers do not allow NA elements (allow_na = FALSE). Set allow_na = TRUE when missing values are allowed.

With the number checkers you can use min and max arguments for range validation, and allow_infinite (default TRUE for decimals, FALSE for whole numbers).

Vectors

  • check_logical(): Logical vector of any length
  • check_character(): Character vector of any length
  • check_data_frame(): A data frame object

By default, vector checkers allow NA elements (allow_na = TRUE). Set allow_na = FALSE when missing values are not allowed.

Optional values: allow_null

Use allow_null = TRUE when NULL represents a valid "no value" state, similar to Option<T> in Rust or T | null in TypeScript:

# NULL means "use default timeout"
check_number_decimal(timeout, allow_null = TRUE)

The tidyverse style guide recommends using NULL defaults instead of missing() defaults, so this pattern comes up often in practice.

Other helpers

These functions are exported by rlang.

  • arg_match(): Validates enumerated choices. Use when an argument must be one of a known set of strings.

    # Validates and returns the matched value
    my_plot <- function(color = c("red", "green", "blue")) {
    color <- rlang::arg_match(color)
    # ...
    }
    
    my_plot("redd")
    #> Error in `my_plot()`:
    #> ! `color` must be one of "red", "green", or "blue", not "redd".
    #> ℹ Did you mean "red"?
    

    Note that partial matching is an error, unlike base::match.arg().

  • check_exclusive() ensures only one of two arguments can be supplied. Supplying both together (i.e. both of them are non-NULL) is an error. Use .require = TRUE if both can be omitted.

  • check_required(): Nice error message if required argument is not supplied.

call and arg arguments

All check functions have call and arg arguments, but you should never use these unless you are creating your own check_ function (see below for more details).

When to validate inputs

Validate at entry points, not everywhere.

Input validation should happen at the boundary between user code and your package's internal implementation:

  • Exported functions: Functions users call directly
  • Functions accepting user data: Even internal functions if they directly consume user input, or external data (e.g. unserialised data)

Once inputs are validated at these entry points, internal helper functions can trust the data they receive without checking again.

A good analogy to keep in mind is gradual typing. Think of input validation like TypeScript type guards. Once you've validated data at the boundary, you can treat it as "typed" within your internal functions. Additional runtime checks are not needed. The entry point validates once, and all downstream code benefits.

Exception: Validate when in doubt. Do validate in internal functions if:

  • The cost of invalid data is high (data corruption, security issues)
  • The function or context is complex and you want defensive checks

Example of validating arguments of an exported function:

# Exported function: VALIDATE
#' @export
create_report <- function(title, n_rows) {
  check_string(title)
  check_number_whole(n_rows, min = 1)

  # Now call helpers with validated data
  data <- generate_data(n_rows)
  format_report(title, data)
}

Once data is validated at the entry point, internal helpers can skip validation:

# Internal helper: NO VALIDATION NEEDED
generate_data <- function(n_rows) {
  # n_rows is already validated, just use it
  data.frame(
    id = seq_len(n_rows),
    value = rnorm(n_rows)
  )
}

# Internal helper: NO VALIDATION NEEDED
format_report <- function(title, data) {
  # title and data are already validated, just use them
  list(
    title = title,
    summary = summary(data),
    rows = nrow(data)
  )
}

Note how the data generated by generate_data() doesn't need validation either. Internal code creating data in a trusted way (e.g. because it's simple or because it's covered by unit tests) doesn't require internal checks.

Early input checking

Always validate inputs at the start of user-facing functions, before doing any work:

my_function <- function(x, name, env = caller_env()) {
  check_logical(x)
  check_name(name)
  check_environment(env)

  # ... function body
}

Benefits:

  • This self-documents the types of the arguments
  • Eager evaluation also reduces the risk of confusing lazy evaluation effects

Custom validation functions

Most packages will need one or more unique checker functions. Sometimes it's sufficient to wrap existing check functions with custom arguments. In this case you just need to carefully pass through the arg and call arguments. In other cases, you want a completely new check in which case you can call stop_input_type with your own arguments.

Wrapping existing check_ functions

When creating a wrapper or helper function that calls check_* functions on behalf of another function, you must propagate the caller context. Otherwise, errors will point to your wrapper function instead of the actual entry point.

Without proper propagation, error messages show the wrong function and argument names:

# WRONG: errors will point to check_positive's definition
check_positive <- function(x) {
  check_number_whole(x, min = 1)
}

my_function <- function(count) {
  check_positive(count)
}

my_function(-5)
#> Error in `check_positive()`:  # Wrong! Should say `my_function()`
#> ! `x` must be a whole number larger than or equal to 1.  # Wrong! Should say `count`

With proper propagation, errors correctly identify the entry point and argument:

# CORRECT: propagates context from the entry point
check_positive <- function(x, arg = caller_arg(x), call = caller_env()) {
  check_number_whole(x, min = 1, arg = arg, call = call)
}

my_function <- function(count) {
  check_positive(count)
}

my_function(-5)
#> Error in `my_function()`:  # Correct!
#> ! `count` must be a whole number larger than or equal to 1.  # Correct!

Note how arg and call are part of the function signature. That allows them to be wrapped again by another checking function that can pass down its own context.

Creating a new check_ function

When constructing your own check_ function you can call stop_input_type() to take advantage of the existing infrastructure for generating error messages. For example, imagine we wanted to create a function that checked that the input was a single date:

check_date <- function(x, ..., allow_null = FALSE, arg = caller_arg(x), call = caller_env()) {
  if (!missing(x) && is.Date(x) && length(x) == 1) {
    return(invisible())
  }

  stop_input_type(
    x,
    "a single Date",
    ...,
    allow_null = allow_null,
    arg = arg,
    call = call
  )
}

Note you must always check first that the input is not missing, as stop_input_type() handles this case specially.

Sometimes you want to check if something is a compound type:

check_string_or_bool <- function(x, ..., arg = caller_arg(x), call = caller_env()) {
  if (!missing(x)) {
    if (is_string(x) || isTRUE(x) || isFALSE(x)) {
      return(invisible())
    }
  }

  stop_input_type(
    x, 
    c("a string", "TRUE", "FALSE"),
    ...,
    arg = arg,
    call = call
  )
}

Note that the second argument to stop_input_type() can take a vector, and it will automatically places commas and "and" in the appropriate locations.

Generally, you should place this check_ function close to the function that is usually used to construct the object being checked (e.g. close to the S3/S4/S7 constructor.)

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/input-validation-in-r-with-rlang-mrx3xzoh/download' --output '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md.part' && mv '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md.part' '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.

Claude Code
test -f '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md' && test -s '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/types-check' && cp -R '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh' '.claude/skills/types-check'

Cursor
test -f '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md' && test -s '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/types-check' && cp -R '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh' '.cursor/skills/types-check'

Codex
test -f '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md' && test -s '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/types-check' && cp -R '.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh' '.agents/skills/types-check'

OpenSkills

OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.

Official OpenSkills installation
openskills install './.skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh'

openskills read 'input-validation-in-r-with-rlang-mrx3xzoh'

Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.

For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.

openskills sync

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/input-validation-in-r-with-rlang-mrx3xzoh/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills