# Add Recipe Step

> Create a new preprocessing step for the recipes package following tidymodels conventions

- Skill: `tidymodels/add-recipe-step` (Agent Skill, multi-file: 29 files)
- Install (CLI): `npx skillmds@latest add tidymodels/add-recipe-step`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tidymodels/add-recipe-step/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tidymodels (https://skillmd.com/u/tidymodels)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tidymodels/add-recipe-step

---


# Add Recipe Step

Guide for developing new preprocessing steps that extend the recipes package.
This skill provides best practices, complete code templates, and testing
patterns for creating custom recipe steps.

--------------------------------------------------------------------------------

## Two Development Contexts

This skill supports **two distinct development contexts**:

### 🆕 Extension Development (Default)

**Creating a new R package** that extends recipes with custom steps.

- ✅ Use this for: New packages, standalone steps, CRAN submissions

- ⚠️ **Constraint**: Must use `recipes::` prefix for all functions

### 🔧 Source Development (Advanced)

**Contributing directly to recipes** via pull requests.

- ✅ Use this for: Contributing to tidymodels/recipes repository

- ✨ **Benefit**: Can use internal functions directly (no prefix needed)

--------------------------------------------------------------------------------

## Getting Started

**INSTRUCTIONS FOR CLAUDE:** Run the verification script first to determine the
development context:

```bash
Rscript -e 'source(Sys.glob(path.expand("~/.claude/plugins/cache/tidymodels-skills/tidymodels-dev/*/tidymodels/shared-references/scripts/verify-setup.R"))[1])'
```

**Then follow the appropriate path based on the output:**

- **Output: "All checks for source development complete."** → Go to [Source
  Development Guide](references/source-guide.md)

- **Output: "All checks for extension development complete." (no warnings)** →
  Go to [Extension Development Guide](references/extension-guide.md)

- **Output: Shows "Warning - [UUID]" messages** → Go to [Extension
  Prerequisites](references/package-extension-prerequisites.md) to resolve
  warnings first

--------------------------------------------------------------------------------

## Implementation Workflow

**INSTRUCTIONS FOR CLAUDE:** Load references progressively based on step type.

### Step 1: Read Step Type Reference ONLY

Determine step type from user requirements, then read ONLY that reference:

- **Modifies existing columns?** Read
  [modify-in-place-steps.md](references/modify-in-place-steps.md) ONLY

- **Creates new columns?** Read
  [create-new-columns-steps.md](references/create-new-columns-steps.md) ONLY

- **Filters/removes rows?** Read
  [row-operation-steps.md](references/row-operation-steps.md) ONLY

Do NOT read all three references. Read only the one needed for this step type.

### Step 2: Read Additional References If Needed

Read other references ONLY if specifically mentioned or needed:

- **User asks about helpers?** Read
  [helper-functions.md](references/helper-functions.md)

- **User mentions tunable?** Read
  [optional-methods.md](references/optional-methods.md)

- **Troubleshooting?** Read troubleshooting references

Default: Don't pre-load optional references.

--------------------------------------------------------------------------------

## Overview

Creating a custom recipe step provides:

- Integration with the recipes preprocessing pipeline

- Automatic handling of variable selection and roles

- Support for case weights

- Consistent prep/bake workflow

- Integration with tune for hyperparameter optimization

- Proper handling of grouped data frames

- Sparse data support (when applicable)

## Repository Access (Optional but Recommended)

**INSTRUCTIONS FOR CLAUDE:** Check if `repos/recipes/` exists in the current
working directory. Use this to guide development:

**If `repos/recipes/` exists:**

- ✅ Use it as a reference throughout development

- Read source files (e.g., `repos/recipes/R/center.R`) to study implementation
  patterns

- Read test files (e.g., `repos/recipes/tests/testthat/test-center.R`) for
  testing patterns

- Reference these files when answering complex questions or solving problems

- Look at actual code structure, validation patterns, and edge case handling

**If `repos/recipes/` does NOT exist:**

- Suggest cloning the repository using the scripts in [Repository Access
  Guide](references/package-repository-access.md)

- This is **optional but strongly recommended** for high-quality development

- If the user declines, reference files using GitHub URLs:

  - Format: `https://github.com/tidymodels/recipes/blob/main/R/[file-name].R`

  - Example: https://github.com/tidymodels/recipes/blob/main/R/center.R

  - This allows users to click through to see implementations

**When to use repository references:**

- Complex implementation questions (e.g., "How does recipes handle variable
  roles?")

- Debugging issues (compare user's code to working implementation)

- Understanding patterns (study similar steps)

- Test design (see how recipes tests edge cases)

- Architecture decisions (understand internal structure)

See [Repository Access Guide](references/package-repository-access.md) for setup
instructions.

## Quick Navigation

**Development Guides:**

- [Extension Development Guide](references/extension-guide.md) - Creating new
  packages that extend recipes

- [Source Development Guide](references/source-guide.md) - Contributing PRs to
  recipes itself

**Reference Files:**

- [Step Architecture](references/step-architecture.md) - Three-function pattern,
  prep/bake workflow, step types

- [Modify-in-Place Steps](references/modify-in-place-steps.md) - Transform
  existing columns

- [Create-New-Columns Steps](references/create-new-columns-steps.md) - Generate
  new columns

- [Row-Operation Steps](references/row-operation-steps.md) - Filter or remove
  rows

- [Optional Methods](references/optional-methods.md) - tunable(),
  required_pkgs(), sparsity methods

- [Helper Functions](references/helper-functions.md) - recipes helper function
  reference

**Shared References (Extension Development):**

- [Extension Prerequisites](references/package-extension-prerequisites.md) -
  Extension prerequisites

- [Development Workflow](references/package-development-workflow.md) - Fast
  iteration cycle

- [Testing Patterns (Extension)](references/package-testing-patterns.md) -
  Extension testing guide

- [Roxygen Documentation](references/package-roxygen-documentation.md) -
  Documentation templates

- [Package Imports](references/package-imports.md) - Managing dependencies

- [Best Practices (Extension)](references/package-best-practices.md) - Extension
  code style

- [Troubleshooting (Extension)](references/package-troubleshooting.md) -
  Extension issues

**Source Development Specific:**

- [Testing Patterns (Source)](references/testing-patterns-source.md) - Using
  internal helpers

- [Best Practices (Source)](references/best-practices-source.md) - Using
  internal functions

- [Troubleshooting (Source)](references/troubleshooting-source.md) -
  Source-specific issues

## Development Workflow

See [Development Workflow](references/package-development-workflow.md) for
complete details.

**Fast iteration cycle (run repeatedly):**

1. `devtools::document()` - Generate documentation
2. `devtools::load_all()` - Load your package
3. `devtools::test()` - Run tests

**Final validation (run once at end):**

4. `devtools::check()` - Full R CMD check

**WARNING:** Do NOT run `check()` during iteration. It takes 1-2 minutes and is
unnecessary until you're done.

## Understanding Recipe Steps

See [Step Architecture](references/step-architecture.md) for complete details.

### The Three-Function Pattern

Every recipe step consists of three functions:

1. **Step constructor** (e.g., `step_center()`) - User-facing function

   - Captures user arguments

   - Uses `enquos(...)` to capture variable selections

   - Returns recipe with step added via `add_step()`

2. **Step initialization** (e.g., `step_center_new()`) - Internal constructor

   - Minimal function with no defaults

   - Calls `step(subclass = "name", ...)` to create S3 object

3. **S3 methods** - Required methods for every step:

   - `prep.step_*()` - Estimates parameters from training data

   - `bake.step_*()` - Applies transformation to new data

   - `print.step_*()` - Displays step in recipe summary

   - `tidy.step_*()` - Returns step information as tibble

### The prep/bake Workflow

**prep() - Training phase:**

- Resolves variable selections (e.g., `all_numeric()` → actual column names)

- Validates column types

- Computes statistics/parameters from training data

- Stores learned parameters in step object

- Returns updated step with `trained = TRUE`

**bake() - Application phase:**

- Takes trained step and new data

- Validates required columns exist

- Applies transformation using stored parameters

- Returns transformed data

**Example workflow:**

```r
# Define recipe with step
rec <- recipe(mpg ~ ., data = mtcars) |>
  step_center(all_numeric_predictors())

# prep() trains the step (calculates means)
trained_rec <- prep(rec, training = mtcars)

# bake() applies the step (subtracts means)
new_data <- bake(trained_rec, new_data = mtcars)
```

## Step Type Decision Tree

Choose the appropriate template based on what your step does:

```
┌─────────────────────────────────────────────────────────────┐
│ What does your step do?                                    │
└─────────────────────────────────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
        ▼                   ▼                   ▼
   Transform           Create new          Remove/filter
   existing            columns             rows
   columns
        │                   │                   │
        ▼                   ▼                   ▼
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  MODIFY IN  │     │   CREATE    │     │     ROW     │
│    PLACE    │     │ NEW COLUMNS │     │  OPERATION  │
└─────────────┘     └─────────────┘     └─────────────┘
        │                   │                   │
        ▼                   ▼                   ▼
  role = NA           role =            skip = TRUE
  No keep_cols    "predictor"          (usually)
                  keep_original_cols
        │                   │                   │
        ▼                   ▼                   ▼
  Examples:           Examples:           Examples:

  - center            - dummy             - filter

  - scale             - pca               - sample

  - normalize         - interact          - naomit

  - log               - poly              - slice
```

**Decision guide:**

- **Modify-in-place**: Transforms existing columns → [Modify-in-Place
  Steps](references/modify-in-place-steps.md)

- **Create new columns**: Generates new columns from existing →
  [Create-New-Columns Steps](references/create-new-columns-steps.md)

- **Row operations**: Filters or removes rows → [Row-Operation
  Steps](references/row-operation-steps.md)

## Complete Example: Modify-in-Place Step (Centering)

This example shows all required components for a modify-in-place step **using
extension development patterns** (with `recipes::` prefix).

**For source development**, see [Source Development
Guide](references/source-guide.md) for examples using internal functions
directly.

**Reference implementation:** `R/center.R` in recipes repository

### 1. Step constructor

```r
# R/center.R

#' Center numeric variables
#'
#' `step_center()` creates a *specification* of a recipe step that will
#' normalize numeric data to have a mean of zero.
#'
#' @inheritParams step_normalize
#' @param ... One or more selector functions to choose variables for this step.
#'   See [recipes::selections()] for more details.
#' @param role Not used by this step since no new variables are created.
#' @param na_rm A logical value indicating whether NA values should be removed
#'   when computing means.
#' @param means A named numeric vector of means. This is `NULL` until computed
#'   by [prep()].
#'
#' @return An updated version of `recipe` with the new step added to the
#'   sequence of any existing operations.
#'
#' @family normalization steps
#' @export
#'
#' @details
#' Centering data means that the average of the variable is subtracted from the
#' data. `step_center` estimates the variable means from the data used in the
#' `training` argument of [prep()]. [bake()] then applies the centering to new
#' data sets using these means.
#'
#' # Tidying
#'
#' When you [`tidy()`][recipes::tidy.recipe()] this step, a tibble is returned
#' with columns `terms`, `value`, and `id`:
#'
#' \describe{
#'   \item{terms}{character, the selectors or variables selected}
#'   \item{value}{numeric, the means}
#'   \item{id}{character, id of this step}
#' }
#'
#' # Case weights
#'
#' This step performs an unsupervised operation that can utilize case weights.
#' As a result, case weights are used with frequency weights as well as
#' importance weights. For more information, see the documentation in
#' [recipes::case_weights] and the examples on `tidymodels.org`.
#'
#' @examplesIf rlang::is_installed("modeldata")
#' data(biomass, package = "modeldata")
#'
#' biomass_tr <- biomass[biomass$dataset == "Training", ]
#' biomass_te <- biomass[biomass$dataset == "Testing", ]
#'
#' rec <- recipe(
#'   HHV ~ carbon + hydrogen + oxygen + nitrogen + sulfur,
#'   data = biomass_tr
#' )
#'
#' center_trans <- rec |>
#'   step_center(carbon, hydrogen)
#'
#' center_obj <- prep(center_trans, training = biomass_tr)
#'
#' transformed_te <- bake(center_obj, biomass_te)
#'
#' biomass_te[1:10, names(transformed_te)]
#' transformed_te
#'
#' tidy(center_trans, number = 1)
#' tidy(center_obj, number = 1)
step_center <- function(
  recipe,
  ...,
  role = NA,
  trained = FALSE,
  means = NULL,
  na_rm = TRUE,
  skip = FALSE,
  id = recipes::rand_id("center")
) {
  recipes::add_step(
    recipe,
    step_center_new(
      terms = rlang::enquos(...),
      trained = trained,
      role = role,
      means = means,
      na_rm = na_rm,
      skip = skip,
      id = id,
      case_weights = NULL
    )
  )
}

step_center_new <- function(terms, role, trained, means, na_rm, skip, id,
                            case_weights) {
  recipes::step(
    subclass = "center",
    terms = terms,
    role = role,
    trained = trained,
    means = means,
    na_rm = na_rm,
    skip = skip,
    id = id,
    case_weights = case_weights
  )
}
```

### 2. prep() method

```r
#' @export
prep.step_center <- function(x, training, info = NULL, ...) {
  # 1. Resolve variable selections to actual column names
  col_names <- recipes::recipes_eval_select(x$terms, training, info)

  # 2. Validate column types
  recipes::check_type(training[, col_names], types = c("double", "integer"))

  # 3. Extract case weights if applicable
  wts <- recipes::get_case_weights(info, training)
  were_weights_used <- recipes::are_weights_used(wts, unsupervised = TRUE)
  if (isFALSE(were_weights_used)) {
    wts <- NULL
  }

  # 4. Compute means for each column
  means <- vapply(
    training[, col_names],
    function(col) {
      if (is.null(wts)) {
        mean(col, na.rm = x$na_rm)
      } else {
        weighted.mean(col, w = as.double(wts), na.rm = x$na_rm)
      }
    },
    numeric(1)
  )

  # 5. Check for issues
  inf_cols <- col_names[is.infinite(means)]
  if (length(inf_cols) > 0) {
    cli::cli_warn(
      "Column{?s} {.var {inf_cols}} returned Inf or NaN. \\
      Consider checking your data before preprocessing."
    )
  }

  # 6. Return updated step with trained = TRUE
  step_center_new(
    terms = x$terms,
    role = x$role,
    trained = TRUE,
    means = means,
    na_rm = x$na_rm,
    skip = x$skip,
    id = x$id,
    case_weights = were_weights_used
  )
}
```

### 3. bake() method

```r
#' @export
bake.step_center <- function(object, new_data, ...) {
  # 1. Get column names from trained step
  col_names <- names(object$means)

  # 2. Validate required columns exist in new data
  recipes::check_new_data(col_names, object, new_data)

  # 3. Apply transformation
  for (col_name in col_names) {
    new_data[[col_name]] <- new_data[[col_name]] - object$means[[col_name]]
  }

  # 4. Return modified data
  new_data
}
```

### 4. print() and tidy() methods

```r
#' @export
print.step_center <- function(x, width = max(20, options()$width - 30), ...) {
  title <- "Centering for "
  recipes::print_step(
    x$columns,
    x$terms,
    x$trained,
    title,
    width,
    case_weights = x$case_weights
  )
  invisible(x)
}

#' @rdname tidy.recipe
#' @export
tidy.step_center <- function(x, ...) {
  if (recipes::is_trained(x)) {
    res <- tibble::tibble(
      terms = names(x$means),
      value = unname(x$means)
    )
  } else {
    term_names <- recipes::sel2char(x$terms)
    res <- tibble::tibble(
      terms = term_names,
      value = rlang::na_dbl
    )
  }
  res$id <- x$id
  res
}
```

### 5. Tests

```r
# tests/testthat/test-center.R

test_that("centering works correctly", {
  rec <- recipe(mpg ~ ., data = mtcars) |>
    step_center(disp, hp)

  prepped <- prep(rec, training = mtcars)
  results <- bake(prepped, mtcars)

  # Check means are approximately zero
  expect_equal(mean(results$disp), 0, tolerance = 1e-7)
  expect_equal(mean(results$hp), 0, tolerance = 1e-7)

  # Check tidy output
  trained_tidy <- tidy(prepped, 1)
  expect_equal(trained_tidy$value, c(mean(mtcars$disp), mean(mtcars$hp)))
})

test_that("centering handles NA correctly", {
  df <- mtcars
  df$disp[1:3] <- NA

  # With na_rm = TRUE (default)
  rec_remove <- recipe(mpg ~ ., data = df) |>
    step_center(disp, na_rm = TRUE)
  prepped <- prep(rec_remove, training = df)
  results <- bake(prepped, df)

  # Mean should be computed ignoring NAs, then subtracted
  expect_true(all(is.na(results$disp[1:3])))
  expect_false(any(is.na(results$disp[4:nrow(df)])))
})

test_that("centering validates input types", {
  df <- data.frame(
    x = 1:5,
    y = letters[1:5]
  )

  rec <- recipe(~ ., data = df) |>
    step_center(y)  # Character column

  expect_error(prep(rec, training = df))
})

test_that("centering works with case weights", {
  df <- mtcars[1:10, ]
  df$weights <- c(rep(1, 5), rep(10, 5))  # Heavy weight on last 5

  rec <- recipe(mpg ~ ., data = df) |>
    step_center(disp)

  # Without weights
  rec_unweighted <- prep(rec, training = df)

  # With weights (need to add case_weights role)
  df_weighted <- df
  df_weighted$weights <- hardhat::importance_weights(df_weighted$weights)

  rec_weighted <- recipe(mpg ~ ., data = df_weighted) |>
    update_role(weights, new_role = "case_weights") |>
    step_center(disp)
  rec_weighted <- prep(rec_weighted, training = df_weighted)

  # Weighted and unweighted should differ
  expect_false(
    tidy(rec_unweighted, 1)$value[1] == tidy(rec_weighted, 1)$value[1]
  )
})
```

**Reference test pattern:** `tests/testthat/test-center.R` in recipes repository

See [Testing Patterns](references/package-testing-patterns.md) for comprehensive
testing guide.

## Implementation Guide by Step Type

### Modify-in-Place Steps

**Use for:** Transform existing columns without creating new ones.

**Pattern:** role = NA, no keep_original_cols parameter

**Complete guide:** [Modify-in-Place Steps](references/modify-in-place-steps.md)

**Key points:**

- Preserve existing column roles with `role = NA`

- Use `recipes_eval_select()` to resolve selections

- Validate with `check_type()` in prep()

- Apply transformation in place in bake()

**Examples:** center, scale, normalize, log

**Reference implementations:**

- Simple transformations: `R/center.R`, `R/scale.R`, `R/normalize.R`

- Math transformations: `R/log.R`, `R/sqrt.R`, `R/logit.R`

- With parameters: `R/BoxCox.R` (power transformation with lambda)

### Create-New-Columns Steps

**Use for:** Generate new columns from existing ones.

**Pattern:** role = "predictor", keep_original_cols parameter

**Complete guide:** [Create-New-Columns
Steps](references/create-new-columns-steps.md)

**Key points:**

- Assign role to new columns with `role = "predictor"`

- Include `keep_original_cols` parameter (default FALSE)

- Use `remove_original_cols()` helper in bake()

- Consider implementing `.recipes_estimate_sparsity()` for sparse columns

**Examples:** dummy, pca, interact, poly

**Reference implementations:**

- Encoding: `R/dummy.R` (one-hot encoding)

- Dimension reduction: `R/pca.R`, `R/ica.R`

- Feature engineering: `R/interact.R`, `R/poly.R`

### Row-Operation Steps

**Use for:** Filter or remove rows from data.

**Pattern:** skip = TRUE by default

**Complete guide:** [Row-Operation Steps](references/row-operation-steps.md)

**Key points:**

- Default `skip = TRUE` since row ops usually only for training

- prep() typically doesn't learn parameters

- bake() applies filtering logic

- Respect skip parameter in bake()

**Examples:** filter, sample, naomit, slice

**Reference implementations:**

- Filtering: `R/filter.R`, `R/filter_missing.R`

- Sampling: `R/sample.R`

- Row removal: `R/naomit.R`, `R/slice.R`

## Helper Functions

See [Helper Functions](references/helper-functions.md) for complete reference.

**Essential helpers:**

- `recipes_eval_select()` - Convert selections to column names (prep)

- `check_type()` - Validate column types (prep)

- `check_new_data()` - Verify columns exist (bake)

- `get_case_weights()` - Extract case weights (prep)

- `are_weights_used()` - Check if weights apply (prep)

- `remove_original_cols()` - Handle keep_original_cols (bake)

- `print_step()` - Standard printing (print)

- `sel2char()` - Convert selections to strings (tidy)

## Optional Methods

See [Optional Methods](references/optional-methods.md) for complete details.

**Optional S3 methods:**

- `tunable()` - Declare parameters for tune package

- `required_pkgs()` - Declare external package dependencies

- `.recipes_preserve_sparsity()` - Indicate sparse preservation

- `.recipes_estimate_sparsity()` - Estimate sparsity of new columns

## Documentation

See [Roxygen Documentation](references/package-roxygen-documentation.md) for
complete templates.

**Required roxygen tags:**

```r
#' @inheritParams step_center
#' @param ... One or more selector functions
#' @param role Role for new variables (or NA)
#' @param trained Logical for training status
#' @param [params] Step-specific parameters
#' @return Updated recipe object
#' @family [category] steps
#' @export
```

## Testing

See [Testing Patterns (Extension)](references/package-testing-patterns.md) for
comprehensive guide.

**Required test categories:** 1. **Correctness**: Step transforms data correctly
2. **Variable selection**: Works with all_numeric(), all_predictors(), etc. 3.
**NA handling**: Both na_rm = TRUE and FALSE 4. **Case weights**: Weighted and
unweighted differ 5. **Infrastructure**: Works in full recipe pipeline 6. **Edge
cases**: Empty data, all same values, etc.

## Package-Specific Patterns (Source Development)

If you're contributing to recipes itself, you have access to internal functions
and conventions not available in extension development.

### File Naming Conventions

Recipes organizes steps by category:

- Normalization: `R/center.R`, `R/scale.R`, `R/normalize.R`

- Encoding: `R/dummy.R`, `R/novel.R`, `R/other.R`

- Dimension reduction: `R/pca.R`, `R/ica.R`

- Row operations: `R/filter.R`, `R/sample.R`

- Tests: `tests/testthat/test-center.R`

### Internal Functions Available

When developing recipes itself, you can use functions directly (no `recipes::`
prefix):

- `recipes_eval_select()` - Variable selection

- `get_case_weights()`, `are_weights_used()` - Case weight handling

- `check_type()` - Column type validation

- `check_new_data()` - Verify columns exist in new data

- `remove_original_cols()` - Handle keep_original_cols

- `print_step()` - Standard printing

- `sel2char()` - Convert selections to strings

### Documentation Patterns

Recipes uses extensive parameter inheritance:

```r
#' @inheritParams step_normalize
#' @template step-return
#' @template case-weights-unsupervised
```

### The Three-Function Pattern (Source)

```r
# 1. Constructor (exported)
step_center <- function(recipe, ..., role = NA, ...) {
  add_step(recipe, step_center_new(...))
}

# 2. Initialization (internal)
step_center_new <- function(terms, role, trained, ...) {
  step(subclass = "center", ...)
}

# 3. Methods (all exported)
prep.step_center <- function(x, training, info = NULL, ...) {
  # Use internal functions directly
  col_names <- recipes_eval_select(x$terms, training, info)
  check_type(training[, col_names], types = c("double", "integer"))
  # ...
}
```

**Complete source development guide:** [Source Development
Guide](references/source-guide.md)

--------------------------------------------------------------------------------

## Best Practices

See [Best Practices](references/package-best-practices.md) for complete guide.

**Key principles:**

- Use base pipe `|>` not magrittr pipe `%>%`

- Prefer for-loops over `purrr::map()` for better error messages

- Use `cli::cli_abort()` for error messages

- Validate early (in prep), trust data in bake

- Use recipes helpers instead of reimplementing

## Troubleshooting

See [Troubleshooting (Extension)](references/package-troubleshooting.md) for
complete guide.

**Common issues:**

- "No visible global function definition" → Add to package imports

- "Object not found" in tests → Use `devtools::load_all()` before testing

- Column selection not working → Check `recipes_eval_select()` usage

- Case weights ignored → Check conversion of hardhat weights

## Related Skills

- [add-yardstick-metric](../add-yardstick-metric/SKILL.md) - Custom recipe steps
  may generate outputs that need custom metrics

- [add-dials-parameter](../add-dials-parameter/SKILL.md) - Recipe steps often
  have tunable parameters that can be optimized

- [add-parsnip-model](../add-parsnip-model/SKILL.md) - Preprocessed data flows
  into model specifications

- [add-parsnip-engine](../add-parsnip-engine/SKILL.md) - Recipe steps prepare
  data for model engines

## Next Steps

**For Extension Development (creating new packages):**

1. **Extension prerequisites:** [Extension
   Prerequisites](references/package-extension-prerequisites.md) - START HERE

**For Source Development (contributing to recipes):**

1. **Start here:** [Source Development Guide](references/source-guide.md)
2. **Clone repository:** See [Repository
   Access](references/package-repository-access.md)
3. **Study existing steps:** Browse `R/center.R`, `R/dummy.R`, `R/pca.R`, etc.
4. **Follow package conventions:** File naming, internal functions,
   three-function pattern
5. **Test with internal helpers:** See [Testing Patterns
   (Source)](references/testing-patterns-source.md)
6. **Submit PR:** See [Source Development Guide](references/source-guide.md) for
   PR process

