VBA Tables and ListObjects

VBA Code Snippets

Tables are one of the most powerful features of Excel.  Controlling them using VBA provides a way to automate that power, which generates a double benefit 🙂

Excel likes to store data within tables.  The basic structural rules, such as (a) headings must be unique (b) only one header row allowed, make tables compatible with more complex tools.  For example, Power Query, Power Pivot, and SharePoint lists all use tables as either a source or an output.  Therefore, it is clearly Microsoft’s intention that we use tables.

However, the biggest benefit to the everyday Excel user is much simpler; if we add new data to the bottom of a table, any formulas referencing the table will automatically expand to include the new data.

Whether you love tables as much as I do or not, this post will help you automate them with VBA.

Tables, as we know them today, first appeared in Excel 2007.  This was a replacement for the Lists functionality found in Excel 2003.  From a VBA perspective, the document object model (DOM) did not change with the upgraded functionality.  So, while we use the term ‘tables’ in Excel, they are still referred to as ListObjects within VBA.

Download the example file

I recommend you download the example file for this post.  Then you’ll be able to work along with examples and see the solution in action, plus the file will be useful for future reference.

Download Icon
Download the file: 0009 VBA tables and ListObjects.zip

Structure of a table

Before we get deep into any VBA code, it’s useful to understand how tables are structured.

Range & Data Body Range

The range is the whole area of the table.

VBA tables - range

The data body range only includes the rows of data, it excludes the header and totals.

VBA tables - data body range

Header and total rows

The header row range is the top row of the table containing the column headers.

VBA tables - header row range

The totals row range, if displayed, includes calculations at the bottom of the table.

VBA tables - totals row range

List columns and list rows

The individual columns are known as list columns.

VBA tables - list columns

Each row is known as a list row.

VBA tables - list rows

The VBA code in this post details how to manage all these table objects.

Referencing the parts of a table

While you may be tempted to skip this section, I recommend you read it in full and work through the examples.  Understanding Excel’s document object model is the key to reading and writing VBA code.  Master this, and your ability to write your own VBA code will be much higher.

Many of the examples in this first section use the select method, this is to illustrate how to reference parts of the table.  In reality, you would rarely use the select method.

Select the entire table

The following macro will select the whole table, including the totals and header rows.

Sub SelectTable()


End Sub

Select the data within a table

The DataBodyRange excludes the header and totals sections of the table.

Sub SelectTableData()


End Sub

Get a value from an individual cell within a table

The following macro retrieves the table value from row 2, column 4, and displays it in a message box.

Sub GetValueFromTable()

MsgBox ActiveSheet.ListObjects("myTable").DataBodyRange(2, 4).value

End Sub

Select an entire column

The macro below shows how to select a column by its position, or by its name.

Sub SelectAnEntireColumn()

'Select column based on position

'Select column based on name

End Sub

Select a column (data only)

This is similar to the macro above, but it uses the DataBodyRange to only select the data; it excludes the headers and totals.

Sub SelectColumnData()

'Select column data based on position

'Select column data based on name

End Sub

Select a specific column header

This macro shows how to select the column header cell of the 5th column.

Sub SelectCellInHeader()


End Sub

Select a specific column within the totals section

This example demonstrates how to select the cell in the totals row of the 3rd column.

Sub SelectCellInTotal()


End Sub

Select an entire row of data

The macro below selects the 3rd row of data from the table.

NOTE – The header row is not included as a ListRow.  Therefore, ListRows(3) is the 3rd row within the DataBodyRange, and not the 3rd row from the top of the table.
Sub SelectRowOfData()


End Sub

Select the header row

The following macro selects the header section of the table.

Sub SelectHeaderSection()


End Sub

Select the totals row

To select the totals row of the table, use the following code.

Sub SelectTotalsSection()


End Sub

OK, now we know how to reference the parts of a table, it’s time to get into some more interesting examples.

Generate accurate VBA code in seconds with AutoMacro


AutoMacro is a powerful VBA code generator that comes loaded with an extensive code library and many other time-saving tools and utilities.

Whether you’re an experienced coder looking to save time, or a newbie just trying to get things to work, AutoMacro is the tool for you.

Creating and converting tables

This section of macros focuses on creating and resizing tables.

Convert selection to a table

The macro below creates a table based on the currently selected region and names it as myTable.  The range is referenced as Selection.CurrentRegion, but this can be substituted for any range object.

If you’re working along with the example file, this macro will trigger an error, as a table called myTable already exists in the workbook.  A new table will still be created with a default name, but the VBA code will error at the renaming step.

Sub ConvertRangeToTable()

tableName As String
Dim tableRange As Range

Set tableName = "myTable"
Set tableRange = Selection.CurrentRegion
ActiveSheet.ListObjects.Add(SourceType:=xlSrcRange, _
    Source:=tableRange, _
    xlListObjectHasHeaders:=xlYes _
    ).Name = tableName

End Sub

Convert a table back to a range

This macro will convert a table back to a standard range.

Sub ConvertTableToRange()


End Sub
NOTE – Unfortunately, when converting a table to a standard range, the table formatting is not removed.  Therefore, the cells may still look like a table, even when they are not – that’s frustrating!!!

Resize the range of the table

To following macro resizes a table to cell A1 – J100.

Sub ResizeTableRange()

ActiveSheet.ListObjects("myTable").Resize Range("$A$1:$J$100")

End Sub

Table styles

There are many table formatting options, the most common of which are shown below.

Change the table style

Change the style of a table to an existing pre-defined style.

Sub ChangeTableStyle()

ActiveSheet.ListObjects("myTable").TableStyle = "TableStyleLight15"

End Sub

To apply different table styles, the easiest method is to use the macro recorder.  The recorded VBA code will include the name of any styles you select.

Get the table style name

Use the following macro to get the name of the style already applied to a table.

Sub GetTableStyleName()

MsgBox ActiveSheet.ListObjects("myTable").TableStyle

End Sub

Apply a style to the first or last column

The first and last columns of a table can be formatted differently using the following macros.

Sub ColumnStyles()

'Apply special style to first column
ActiveSheet.ListObjects("myTable").ShowTableStyleFirstColumn = True

'Apply special style to last column
ActiveSheet.ListObjects("myTable").ShowTableStyleLastColumn = True

End Sub

Adding or removing stripes

By default, tables have banded rows, but there are other options for this, such as removing row banding or adding column banding.

Sub ChangeStripes()

'Apply column stripes
ActiveSheet.ListObjects("myTable").ShowTableStyleColumnStripes = True

'Remove row stripes
ActiveSheet.ListObjects("myTable").ShowTableStyleRowStripes = False

End Sub

Set the default table style

The following macro sets the default table style.

Sub SetDefaultTableStyle()

'Set default table style
ActiveWorkbook.DefaultTableStyle = "TableStyleMedium2"

End Sub

Looping through tables

The macros in this section loop through all the tables on the worksheet or workbook.

Loop through all tables on a worksheet

If we want to run a macro on every table of a worksheet, we must loop through the ListObjects collection.

Sub LoopThroughAllTablesWorksheet()

'Create variables to hold the worksheet and the table
Dim ws As Worksheet
Dim tbl As ListObject

Set ws = ActiveSheet
'Loop through each table in worksheet
For Each tbl In ws.ListObjects

    'Do something to the Table....

Next tbl

End Sub

In the code above, we have set the table to a variable, so we must refer to the table in the right way.  In the section labeled ‘Do something to the table…, insert the action to be undertaken on each table, using tbl to reference the table.

For example, the following will change the table style of every table.

tbl.TableStyle = "TableStyleLight15"

Loop through all tables in a workbook

Rather than looping through a single worksheet, as shown above, the macro below loops through every table on every worksheet.

Sub LoopThroughAllTablesWorkbook()

'Create variables to hold the worksheet and the table
Dim ws As Worksheet
Dim tbl As ListObject

'Loop through each worksheet
For Each ws In ActiveWorkbook.Worksheets

    'Loop through each table in worksheet
    For Each tbl In ws.ListObjects

        'Do something to the Table....

    Next tbl

Next ws

End Sub

As noted in the section above, we must refer to the table using its variable.  For example, the following will display the totals row for every table.

tbl.ShowTotals = True

Adding & removing rows and columns

The following macros add and remove rows, headers, and totals from a table.

Add columns into a table

The following macro adds a column to a table.

Sub AddColumnToTable()

'Add column at the end

'Add column at position 2
ActiveSheet.ListObjects("myTable").ListColumns.Add Position:=2
End Sub

Add rows to the bottom of a table

The next macro will add a row to the bottom of a table

Sub AddRowsToTable()

'Add row at bottom

'Add row at the first row
ActiveSheet.ListObjects("myTable").ListRows.Add Position:=1
End Sub

Delete columns from a table

To delete a column, it is necessary to use either the column index number or the column header.

Sub DeleteColumnsFromTable()

'Delete column 2

'Delete a column by name
End Sub

Delete rows from a table

In the table structure, rows do not have names, and therefore can only be deleted by referring to the row number.

Sub DeleteRowsFromTable()

'Delete row 2

'Delete multiple rows
End Sub

Add total row to a table

The total row at the bottom of a table can be used for calculations.

Sub AddTotalRowToTable()

'Display total row with value in last column
ActiveSheet.ListObjects("myTable").ShowTotals = True

'Change the total for the "Total Column" to an average
ActiveSheet.ListObjects("myTable").ListColumns("TotalColumn").TotalsCalculation = _

'Totals can be added by position, rather than name
ActiveSheet.ListObjects("myTable").ListColumns(2).TotalsCalculation = _
End Sub

Types of totals calculation


Table header visability

Table headers can be turned on or off.   The following will hide the headers.

Sub ChangeTableHeader()

ActiveSheet.ListObjects("myTable").ShowHeaders = False

End Sub

Remove auto filter

The auto filter can be hidden.  Please note, the table header must be visible for this code to work.

Sub RemoveAutoFilter()

ActiveSheet.ListObjects("myTable").ShowAutoFilterDropDown = False

End Sub

I have a separate post about controlling auto filter settings – check it out here.  Most of that post applies to tables too.

Other range techniques

Other existing VBA techniques for managing ranges can also be applied to tables.

Using the union operator

To select multiple ranges, we can use VBA’s union operator. Here is an example, it will select rows 4, 1, and 3.

Sub SelectMultipleRangesUnionOperator()

Union(ActiveSheet.ListObjects("myTable").ListRows(4).Range, _
    ActiveSheet.ListObjects("myTable").ListRows(1).Range, _

End Sub

Assign values from a variant array to a table row

To assign values to an entire row from a variant array, use code similar to the following:

Sub AssignValueToTableFromArray()

'Assing values to array (for illustration)
Dim myArray As Variant
myArray = Range("A2:D2")

'Assign values in array to the table
ActiveSheet.ListObjects("myTable").ListRows(2).Range.Value = myArray

End Sub

Reference parts of a table using the range object

Within VBA, a table can be referenced as if it were a standard range object.

Sub SelectTablePartsAsRange()


End Sub

Counting rows and columns

Often, it is useful to count the number of rows or columns.  This is a good method to reference rows or columns which have been added.

Counting rows

To count the number of rows within the table, use the following macro.

Sub CountNumberOfRows()

Msgbox ActiveSheet.ListObjects("myTable").ListRows.Count

End Sub

Counting columns

The following macro will count the number of columns within the table.

Sub CountNumberOfColumns()

Msgbox ActiveSheet.ListObjects("myTable").ListColumns.Count

End Sub

Useful table techniques

The following are some other useful VBA codes for controlling tables.

Show the table data entry form

If a table starts at cell A1, there is a simple data entry form that can be displayed.

Sub ShowDataEntryForm()

'Only works if Table starts at Cell A1

End Sub

The following screenshot shows the data form for the example table.

Tables data input screen

Check if a table exists

The following macro checks if a table already exists within a workbook.  Change the tblName variable to adapt this to your requirements.

Sub CheckIfTableExists()

'Create variables to hold the worksheet and the table
Dim ws As Worksheet
Dim tbl As ListObject
Dim tblName As String
Dim tblExists As Boolean

tblName = "myTable"

'Loop through eac worksheet
For Each ws In ActiveWorkbook.Worksheets

    'Loop through each table in worksheet
    For Each tbl In ws.ListObjects

        If tbl.Name = tblName Then

            tblExists = True

        End If

    Next tbl

Next ws

If tblExists = True Then

    MsgBox "Table " & tblName & " exists."


    MsgBox "Table " & tblName & " does not exists."

End If

End Sub

Find out if a table has been selected, if so which

The following macros find the name of the selected table.

Method 1

As you will see in the comments Jon Peltier had an easy approach to this, which has now become my preferred approach.

Sub SimulateActiveTable()

Dim ActiveTable As ListObject

On Error Resume Next
Set ActiveTable = ActiveCell.ListObject
On Error GoTo 0

'Confirm if a cell is in a Table
If ActiveTable Is Nothing Then
    MsgBox "Select table and try again"
    MsgBox "The active cell is in a Table called: " & ActiveTable.Name
End If

End Sub

Method 2

This option, which was my original method, loops through each table on the worksheet and checks if they intersect with the active cell.

Sub SimulateActiveTable_Method2()

Dim ActiveTable As ListObject
Dim tbl As ListObject

'Loop through each table, check if table intersects with active cell
For Each tbl In ActiveSheet.ListObjects

    If Not Intersect(ActiveCell, tbl.Range) Is Nothing Then

        Set ActiveTable = tbl
        MsgBox "The active cell is in a Table called: " & ActiveTable.Name
    End If

Next tbl

'If no intersection then no tabl selected
If ActiveTable Is Nothing Then

    MsgBox "Select an Excel table and try again"

End If

End Sub


Wow!  That was a lot of code examples. 

There are over 30 VBA macros above, and even this does not cover everything, but hopefully covers 99% of your requirements. For your remaining requirements, you could try Microsoft’s VBA object reference library (https://docs.microsoft.com/en-us/office/vba/api/Excel.ListObject)

Don’t forget:

If you’ve found this post useful, or if you have a better approach, then please leave a comment below.

Do you need help adapting this to your needs?

I’m guessing the examples in this post didn’t exactly meet your situation.  We all use Excel differently, so it’s impossible to write a post that will meet everybody’s needs.  By taking the time to understand the techniques and principles in this post (and elsewhere on this site) you should be able to adapt it to your needs.

But, if you’re still struggling you should:

  1. Read other blogs, or watch YouTube videos on the same topic.  You will benefit much more by discovering your own solutions.
  2. Ask the ‘Excel Ninja’ in your office.  It’s amazing what things other people know.
  3. Ask a question in a forum like Mr Excel, or the Microsoft Answers Community.  Remember, the people on these forums are generally giving their time for free.  So take care to craft your question, make sure it’s clear and concise.  List all the things you’ve tried, and provide screenshots, code segments and example workbooks.
  4. Use Excel Rescue, who are my consultancy partner.   They help by providing solutions to smaller Excel problems.

What next?
Don’t go yet, there is plenty more to learn on Excel Off The Grid.  Check out the latest posts:

26 thoughts on “VBA Tables and ListObjects

  1. Vijay says:

    It really really a good one. It gave me solution to all my doubts. Thanks a lot. Explanation is so nice and simple, one can easily understand. Once again thanks

    • Excel Off The Grid says:

      Personally, yes I would. There seems little point adding a new row, then populating it later.

  2. Ludo Soete says:


    Lets say you have a table starting at row 10, 4 columns wide.
    on cell A7 i would like to place the MAX value from the table column1
    and on cell A8 the MIN value from the same column.
    And this for each column.

    How can this be done?
    following code generates an error 9
    myMax = Application.WorksheetFunction.Max(ActiveSheet.ListObjects(“Tabel1[kolom1]”).Range)

    • Excel Off The Grid says:

      To reference a column in a Table, you would need to use the following syntax:

      myMax = Application.WorksheetFunction.Max(ActiveSheet.ListObjects(“Tabel1”).ListColumns(“kolom1”).Range)

  3. Luisa says:

    Awesome post!!! Thank you so much.
    I have a question for you:

    Do you know a way of applying a customized table style using vBA?
    I have only been able to use the predefined ones.
    This works:
    objSheet.ListObjects(sFile).TableStyle = “TableStyleMedium2
    But this does not:
    objSheet.ListObjects(sFile).TableStyle = “My_Style_Table”


    • Excel Off The Grid says:

      I’ve tested this, and I can select a custom style.

      The custom styles only exist in the workbook they are created in (they are not available in other workbooks), so it may be that the style you’ve created doesn’t exist in the workbooks you’re trying to apply them in.

  4. Rick says:

    Great learning points here. My question is I’m using the a Table as source data for a Pivot Table but the range I’m getting does not include the Header Row which is required by the Pivot Table.

    This is my code line that gets the source data:
    SrcData = shQB.Name & “!” & Range(“tbQB”).Address(ReferenceStyle:=xlR1C1

    As I said this does not include the Header Row in the return

    If I apply your line in article above such as
    SrcData = ActiveSheet.ListObjects(“tbQB”).Range then I get an error code 13
    Any idea on how I can get this to work greatly appreciated

    • Excel Off The Grid says:

      Using structured references for Tables, the range should be defined as follows:


      That should include the header row, but not the totals row.

  5. tony says:

    For some of the ‘check’ type subroutines, I’d suggest changing the sub to a function.
    sub CheckTableExists()
    Function CheckTableExists(tblname as string) as boolean
    Instead of the MsgBox responses inside the sub, you’d set the return value to TRUE or FALSE as appropriate

  6. Adilson Lima says:

    I look for a way to DISABLE the “Autofill ” for table columns.
    I’ve tryed the following actions but did not work:
    1. “Application.AutoCorrect.AutoExpandListRange = False”
    2. “Application.AutoCorrect.AutoFillFormulasInLists = False”

    How can I get this working out?

  7. Julius Cezar F. Rivera says:

    This post explained Excel tables as implemented in VBA. I watched long boring Excel tutorials. I’ve learned a lot from them but didn’t get the information about manipulating tables in VBA.
    The codes presented here are straight forward.
    Thanks a lot!

  8. Stephanie Lachaud says:

    Instead of selecting a specific row, how would you select a row based on an active cell? Using (ActiveCell.Row) in lieu of the row number?

    Sub SelectRowOfData()


    End Sub

    • Excel Off The Grid says:

      You could just use a simple calculation based on row numbers:

      Dim SelectRow As Integer
      SelectRow = ActiveCell.Row - ActiveSheet.ListObjects("myTable").ListRows(1).Range.Row + 1
  9. Gav says:

    What an excellent post. Thank you.
    I came looking for one thing and found a wealth of information on other things I can use. Having said that, what I was looking for isn’t here and I hope you can help.

    I have a workbook with many many tables and recently some corruptions in the file began to occur. The excel error report “Repaired Records: External data range from /xl/queryTables/queryTable17.bin part (External data range)”.
    The xml report didn’t have any additional info.
    Some of the tables are refreshed by powerquery from csv files in the same folder.

    I had already renamed all of my tables to Tbl_AMoreDescriptiveTableName and have no idea which one is Table17.
    Because I’ve renamed the tables, the original table numbers are nowhere to be found (not that I can see in name manager etc.)

    I was given the VB code (excerpt below) but it only lists the table name and sheet it’s located but not the original table number that the MS error message refers to.
    Is there a way to determine the same table number as referenced by Excel’s error handling ?

    variable tbl As ListObject

    .Cells(lRow, “A”) = tbl.Name
    .Cells(lRow, “B”) = tbl.Parent.Name (for the sheet it’s located in)

    • Excel Off The Grid says:

      It’s difficult to advise, as your file is corrupted. So it’s not a standard scenario.

      If your file is corrupted, then it may be that Table17 doesn’t exist, but within the XML file structure Excel believes it does. You could try (a) rebuilding the file (b) finding some software that repairs Excel files.

  10. Jeff Sararas says:

    I’m attempting to use these table references to copy table contents to a blank sheet in another file, and I get a subscript out of range.

    Sheets(“SourceSheetWithTable”).ListObjects(“ExcelTableName”).Range.Copy Workbooks(“Destination.xlsx”).Worksheets(“Sheet1”).Range(“A1”)

    Does it not like going from a table to a regular range? Does the range have to match the size of the table?

    • Excel Off The Grid says:

      The “Subscript out of range” error is because one of the items you’ve referenced doesn’t exist. When I’ve tested your code it works fine, so it’s definitely something to do with the names in your environment.
      (a) You should double-check the names of everything, as there is probably a typo in there.
      (b) Are you running the code with the workbook containing the “SourceSheetWithTable” sheet as the active workbook

      • Jeff Sararas says:

        Thanks- here’s what I cobbled together from some other sources. Instead of using the Copy method, it’s assigning .value to .value. It doesn’t seem as elegant but it works! I added my questions as comments. Could you tell me if I’m understanding correctly?

        ‘load a Range variable with the listobject.Range
        With wsCopy.ListObjects(1)
        Set src = .Range ‘Union(.HeaderRowRange, .DataBodyRange)
        End With

        ‘Does this resize the destination range to match the size of the src.Range and then assign it the src.Range.Value? It looks like .Rows.Count and .Columns.Count inside the Resize method refer to src variable due to being inside the With?
        With src
        wsDest.Range(“A1”).Resize(.Rows.Count, .Columns.Count).Value = .Value
        End With

Leave a Reply

Your email address will not be published. Required fields are marked *