tibble add_row() in R: Append Rows to a Data Frame
The add_row() function in the tibble package appends one or more rows to an existing tibble using name-value pairs, with optional .before and .after arguments to control insert position.
add_row(df, x = 4, y = "d") # append one row at end add_row(df, x = c(4, 5), y = c("d", "e")) # append several rows add_row(df, x = 0, y = "first", .before = 1) # insert at top add_row(df, x = 9, .after = 2) # missing cols become NA add_row(df, y = "z") # only one col supplied add_row(df) # append a fully-NA row df |> add_row(x = 4, y = "d") # pipe-friendly
Need explanation? Read on for examples and pitfalls.
What add_row() does in one sentence
add_row() returns a new tibble with extra rows appended or inserted. You pass the source tibble plus one name-value pair per column you want to populate. Missing columns fill with NA. The original tibble is not modified; assignment back is required to persist the change.
The function lives in the tibble package and works on any tibble or data frame. It mirrors add_column() for the column-axis case and complements dplyr::bind_rows() for the multi-frame case. Where bind_rows() joins two tibbles, add_row() is for the smaller, surgical task of adding a handful of rows you describe inline.
Syntax
add_row() takes the tibble first, then name-value pairs, then optional position arguments. Vectors recycle to match the longest input, so passing scalars for some columns and vectors for others is fine as long as lengths align.
The full signature is:
add_row(.data, ..., .before = NULL, .after = NULL)
Arguments:
.data: the source tibble or data frame....: name-value pairs. Names must match existing column names; values can be scalars or vectors..before: integer index. The new rows are inserted before this row..after: integer index. The new rows are inserted after this row.
If both .before and .after are NULL (the default), new rows are appended at the end. Supplying both is an error. Columns omitted from ... are filled with NA of the appropriate type.
add_row() for readable row-append chains. Because add_row() returns a new tibble, it composes with the native pipe: df |> add_row(x = 4, y = "d") |> add_row(x = 5, y = "e"). The pattern reads top-to-bottom in the order rows arrive, which is easier to scan than nested function calls.Six common patterns
1. Append a single row at the end
The new row goes at position nrow(df) + 1. Column order in the call does not matter; values land in the column whose name matches.
2. Append several rows at once
Each vector contributes one row's worth of data per position. All vectors must be the same length or length 1 (which recycles).
3. Insert at a specific position with .before or .after
.before = 1 inserts above row 1. .after = nrow(df) is identical to the default append. Use one or the other, not both.
4. Let missing columns become NA
You do not have to fill every column. This is convenient when most fields are unknown at insert time and will be back-filled later.
5. Append a placeholder row with no values
Calling add_row() with only .data appends a single row where every column is NA. Useful for reserving a slot you will populate via assignment.
6. Vector recycling and scalar columns
Length-1 inputs recycle to match the longest input. Mismatched non-scalar lengths error rather than recycle silently.
add_row() vs bind_rows() vs rbind()
Use add_row() for inline row additions with named values. Where add_row() shines is the small, hand-written case where you know the values and want them visible in source. For combining two frames, reach for dplyr::bind_rows(). For base R, rbind() still works but is fussier about types and column matching.
| Behavior | add_row() |
bind_rows() |
rbind() |
|---|---|---|---|
| Source | tibble package | dplyr | base R |
| Inputs | name-value pairs | tibbles or lists | data frames |
| Missing columns | Fill with NA | Fill with NA | Error |
| Extra columns | Error | Add column with NA | Error |
| Position control | .before, .after |
Append only | Append only |
| Type coercion | Per column | Per column | Strict matching |
| Returns | tibble | tibble | data.frame |
When to use which:
- Use
add_row()to splice in a few hand-written rows where source readability matters. - Use
bind_rows()to stack two or more tibbles whose schemas mostly overlap. - Use
rbind()for legacy code that already uses base R data frames end-to-end.
add_row() returns a new tibble; it does not mutate. Forgetting to assign the result back is the single most common mistake. The original tibble stays unchanged after add_row(df, x = 4). To persist the row, write df <- add_row(df, x = 4) or pipe into a chain that re-binds at the end. Tibble functions follow this rule consistently, which makes them safe to compose but trips up users coming from spreadsheet or SQL INSERT semantics.Common pitfalls
Pitfall 1: column names that do not exist in .data. add_row() will not invent new columns. A typo or extra name throws an error.
Pitfall 2: type mismatch. Values must coerce to the existing column type, otherwise the call errors. Adding a string to an integer column is rejected, not silently converted.
.before and .after cannot both be set. Passing both arguments produces an error. Pick one, or omit both to append at the end. The arguments are also positional integers, not row identifiers, so renumbering after a previous insert shifts the meaning of every index.Pitfall 3: forgetting to assign back. add_row() returns a new tibble. The original is untouched.
Try it yourself
Try it: Take mtcars, convert it to a tibble, then insert one row at position 1 with mpg = 50, cyl = 4, and the remaining columns set to NA. Save the result to ex_mtcars.
Click to reveal solution
Explanation: as_tibble() converts mtcars from a data frame to a tibble so add_row() can act on it. Only mpg and cyl are named, so the other nine columns receive NA automatically. .before = 1 places the new row above the original first row.
Related tibble functions
After add_row(), look at:
add_column(): extend an existing tibble with new columns instead of new rows.tibble()andtribble(): build tibbles from scratch, column-by-column or row-by-row.as_tibble(): convert a data frame, list, or matrix into a tibble.dplyr::bind_rows(): stack two or more tibbles vertically, accepting different column sets.dplyr::rows_update(): modify existing rows in place rather than appending new ones.
For the full reference, see the official tibble documentation.
FAQ
How do you add a row to a tibble in R?
Call add_row() from the tibble package with the source tibble as the first argument, then one name-value pair per column you want to fill. Example: add_row(df, x = 4, y = "d") appends a single row where x = 4 and y = "d". Columns you do not name receive NA. The function returns a new tibble, so assign the result back: df <- add_row(df, x = 4, y = "d") to persist the change.
What is the difference between add_row() and bind_rows()?
add_row() accepts name-value pairs and is designed for inline, hand-written rows: add_row(df, x = 4, y = "d"). dplyr::bind_rows() accepts whole tibbles or data frames and stacks them: bind_rows(df1, df2). Use add_row() when you know the values and want them visible in source; use bind_rows() when you already have the rows as another tibble. Both fill missing columns with NA, but add_row() rejects unknown column names while bind_rows() creates them.
Can add_row() insert at a specific position in R?
Yes. Pass .before = N to insert above row N, or .after = N to insert below row N. Example: add_row(df, x = 0, y = "first", .before = 1) inserts at the top. Without either argument, the new rows append at the end. You cannot supply both .before and .after in the same call; the function will error. Indices are based on the source tibble, so each call evaluates against the row numbering at that moment.
Why does add_row() error with "can't add columns"?
add_row() only fills columns that already exist in the source tibble. Passing a name that does not match an existing column triggers the error New rows can't add columns. Check the spelling of the column name and inspect the columns with names(df). To add a new column, use add_column() or dplyr::mutate() instead. To stack a tibble that has extra columns, use dplyr::bind_rows(), which auto-fills missing columns on either side with NA.
Does add_row() modify the original tibble?
No. add_row() returns a new tibble; the original is unchanged. This is the standard tidyverse pattern: functions are pure, and changes only persist when you assign the result back. Write df <- add_row(df, x = 4) or chain through a pipe: df <- df |> add_row(x = 4) |> add_row(x = 5). Treating add_row() like a spreadsheet INSERT that mutates in place is the most common bug; the result vanishes if not assigned.