🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

VBA Insert Rows and Columns in Excel — Shift the Grid the Right Way (and Insert in a Loop Without Chaos)

|

VBA Insert Rows and Columns in Excel — Shift the Grid the Right Way (and Insert in a Loop Without Chaos)

TL;DR — Inserting is the mirror image of deleting. .EntireRow.Insert doesn't conjure empty space out of nothing — it shoves every existing row down to open a gap, exactly the way deleting slides them up. That means the same grid-moves-under-you problem applies: insert inside a top-down loop and the rows you haven't reached keep sliding away from you. Insert a whole row with .EntireRow.Insert; on a partial range, name the direction with Shift:=xlDown (or xlToRight); control which neighbour's formatting the new row copies with CopyOrigin; and to add many rows, insert a multi-row range in one call instead of looping.

' Insert a blank row ABOVE row 5, pushing 5, 6, 7... down by one.
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Data")

ws.Rows(5).EntireRow.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove

Once you've met the delete-rows trap — a forward loop that skips rows because deleting slides the grid up — inserting holds no surprises, because it's the same idea running the other direction. Insert pushes the grid down. Everything that made deleting subtle (the numbers move, whole-row vs partial, doing it once vs in a loop) has an exact insert-side counterpart. This guide walks them in that mirror, so the model you already have does most of the work.

What you'll learn

  • The mental model — inserting pushes the grid down, it doesn't fill empty space
  • Whole-row insert versus a partial range, and why Shift decides the direction
  • CopyOrigin — which neighbour's formatting the inserted row or column inherits
  • Inserting inside a loop without re-processing your own new rows
  • The one-call way to insert many rows at once

The mental model: inserting pushes the grid down

Just as deleting a row removes it and closes the gap, inserting a row opens a gap and pushes everything down to make room. ws.Rows(5).EntireRow.Insert doesn't write into row 5 — it makes a brand-new empty row 5, and the old row 5 becomes row 6, the old 6 becomes 7, and so on all the way to the bottom of the used range. The new row arrives; every existing row below the insertion point moves down by one.

This is the single fact to hold onto, because it's the source of both the everyday behaviour and the loop trap. Insert "above row 5" and you're really saying "make room at 5 and slide the rest down." Columns work identically sideways: Columns("C").Insert opens a new column C and pushes D, E, F to the right. The grid is elastic, and Insert stretches it — which means the address of everything below (or right of) the insertion point just changed.

The rule for partial ranges: name the direction with Shift

When you insert a whole row or column, there's no ambiguity — a whole row can only go down, a whole column can only go right. But insert a partial range and Excel has to guess which way to push the neighbours, and its guess is based on the range's shape:

ws.Range("B5:D5").Insert Shift:=xlDown      ' push B:D down, leave columns A and E alone
ws.Range("B5:B8").Insert Shift:=xlToRight   ' push these cells right

Leave Shift off a partial range and Excel picks a direction from whether the block is wider than it is tall — a rule nobody remembers, which is how you end up with cells shoved sideways when you meant them to go down. Whenever the range isn't a full row or column, state Shift explicitly. And most of the time you don't want a partial insert at all: shifting a sub-block of cells misaligns every column it doesn't include, the same corruption a single-cell delete causes. Reach for .EntireRow.Insert / .EntireColumn.Insert unless you genuinely mean to move a block:

ws.Rows(5).EntireRow.Insert          ' whole row — every column stays in step
ws.Columns("C").EntireColumn.Insert  ' whole column — every row stays in step

The rule that surprises people: CopyOrigin decides the formatting

A freshly inserted row isn't blank of formatting — Excel copies the look of a neighbour, and CopyOrigin chooses which one. The default is xlFormatFromLeftOrAbove, so a new row inherits the format of the row above it. Usually fine — until the row above is a bold, shaded header, and your new data row arrives dressed as a header too:

' New row copies the format of the row BELOW instead of the header above.
ws.Rows(2).EntireRow.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromRightOrBelow

xlFormatFromRightOrBelow tells Excel to take formatting from the row beneath the insertion point — exactly what you want when you're inserting a data row directly under a header. This is a small flag with a visible result: get it wrong and every inserted row wears the wrong clothes; get it right and inserts look native. If you want a genuinely clean row, insert first, then clear the formats explicitly with .ClearFormats.

The loop trap, mirrored: insert backwards or re-process your own rows

Inserting in a loop has the same hazard as deleting in a loop, pointed the opposite way. Suppose you scan down and insert a blank separator row after every "Total" row. Insert while moving top-down and the new row pushes your unscanned rows further down — and depending on how you advance, the loop can walk straight into the row it just created and insert again, and again. The grid grows under you.

The clean fix is the same as delete's: loop from the bottom up, so every insert happens below the rows you still have to visit and never disturbs their numbers:

Dim i As Long, lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

For i = lastRow To 2 Step -1
    If ws.Cells(i, "A").Value = "Total" Then
        ws.Rows(i + 1).EntireRow.Insert Shift:=xlDown   ' insert below; rows above are untouched
    End If
Next i

Going backwards, each inserted row lands beneath cells you've already handled, so the rows still waiting keep their addresses. It's the identical discipline from VBA Delete Rows — when a loop changes the number of rows, walk it Step -1.

Inserting many rows at once: resize, don't repeat

Calling .Insert a hundred times to add a hundred rows is slow — each call restructures the grid and recalculates. To insert n contiguous rows, insert an n-row range in a single operation. Grab the target row, .Resize(n) it to the height you want, and insert that:

' Insert 5 blank rows above row 10 in one call.
ws.Rows(10).Resize(5).EntireRow.Insert Shift:=xlDown

Rows(10).Resize(5) is rows 10 through 14; inserting that block opens five rows at once and pushes the rest down together. One structural change instead of five. For scattered inserts across a large sheet, the fastest pattern is often to add a helper column, sort or filter to group the rows, and insert in bulk — the same "do the structural edit once" principle that makes bulk deletes fast. Wrap big inserts in Application.ScreenUpdating = False and manual calculation and they finish instantly.

How ExcelMaster helps

Insert code fails in quiet, cosmetic ways: a partial range shoved the wrong direction because Shift was omitted, new rows wearing a header's bold formatting, a top-down loop that inserts into its own output, a hundred separate inserts that make a report crawl. Each one looks like "the insert didn't quite work" rather than a clear error.

ExcelMaster lets you say what you want — "add a blank row after every subtotal," "insert three rows above the summary," "put a new column between C and D." It writes .EntireRow.Insert (or .EntireColumn.Insert) with the right Shift and CopyOrigin, loops in the safe direction, batches multi-row inserts into one call, and backs up the sheet first. You keep the workbook and the code; you skip the guesswork about which way the grid is about to move.

Frequently asked questions

How do I insert a row in VBA?

Use .EntireRow.Insert on the row you want to push down: ws.Rows(5).EntireRow.Insert creates a new blank row 5 and moves the old row 5 and everything below it down by one. Add Shift:=xlDown for clarity and CopyOrigin to control which neighbour's formatting the new row inherits.

What does the Shift argument do when inserting?

Shift tells Excel which way to push existing cells when you insert a partial range — xlDown moves them down, xlToRight moves them right. It's unnecessary for a whole row or column (those can only go one way) but important for a sub-block, where Excel otherwise guesses the direction from the range's shape.

Why does my inserted row have the wrong formatting?

Inserted rows copy a neighbour's format, chosen by CopyOrigin. The default xlFormatFromLeftOrAbove copies the row above — so inserting under a header makes the new row look like a header. Pass CopyOrigin:=xlFormatFromRightOrBelow to copy the row below instead, or call .ClearFormats on the new row after inserting.

How do I insert multiple rows at once in VBA?

Resize the target row to the count you need and insert that block in one call: ws.Rows(10).Resize(5).EntireRow.Insert inserts five rows above row 10. A single multi-row insert is much faster than calling .Insert five times, because Excel restructures the grid only once.

Should I loop forwards or backwards when inserting rows?

Backwards, for the same reason as deleting. Inserting pushes unscanned rows further down, and a top-down loop can end up inserting into the rows it just created. Looping For i = lastRow To 2 Step -1 keeps every insert below the cells you still have to process, so their row numbers never shift.

Tested in

Tested in: Excel 365 (Windows 11), VBA 7.1 — last verified 2026-08-07.

Related guides: VBA Delete Rows · VBA Hide Columns and Rows · VBA Last Row · VBA Range · VBA For Loop