How to Create CSV Files with Headers in PowerShell

To create a CSV file with headers in PowerShell, pipe objects to Export-Csv. The header row comes from the property names, so you never type it yourself:

[pscustomobject]@{ Name = 'Maria Lopez'; City = 'Austin' } |
    Export-Csv -Path .\people.csv -NoTypeInformation

Below I cover custom header names, header-only files, appending, empty values and the spots where PowerShell 7 and Windows PowerShell 5.1 write different files.

Create a CSV file with headers using Export-Csv

Build each row as a PowerShell custom object. Export-Csv reads the property names of the first object and writes them as the header line:

$employees = @(
    [pscustomobject]@{ Name = 'Maria Lopez'; Title = 'Analyst'; City = 'Austin' }
    [pscustomobject]@{ Name = 'Tom Baker';   Title = 'Manager'; City = 'Denver' }
    [pscustomobject]@{ Name = 'Dana Reyes';  Title = 'Engineer'; City = 'Seattle' }
)

$employees | Export-Csv -Path .\employees.csv -NoTypeInformation
Get-Content -Path .\employees.csv

Output:

"Name","Title","City"
"Maria Lopez","Analyst","Austin"
"Tom Baker","Manager","Denver"
"Dana Reyes","Engineer","Seattle"
PowerShell Export-Csv writing a CSV file with Name, Title and City headers
Export-Csv writes the header row from the property names (PowerShell 7)

Name, Title and City became the columns, in the order I defined them. Both shells produced this exact file when I added -NoTypeInformation.

Microsoft’s Export-Csv notes add one rule: every later object is matched to the first object’s properties. A missing property becomes an empty field, and an extra one is silently dropped. For the full parameter list, see my Export-Csv guide.

Need tabs instead of commas for another tool? Here’s how to convert a CSV file to tab-delimited with PowerShell.

How do you set custom column headers in a CSV file?

Export-Csv has no -Header parameter, whatever older posts say. To rename columns, rename the properties first with calculated properties in Select-Object:

$employees = @(
    [pscustomobject]@{ Name = 'Maria Lopez'; Title = 'Analyst'; City = 'Austin' }
    [pscustomobject]@{ Name = 'Tom Baker';   Title = 'Manager'; City = 'Denver' }
    [pscustomobject]@{ Name = 'Dana Reyes';  Title = 'Engineer'; City = 'Seattle' }
)

$employees | Select-Object @{ Name = 'Employee Name'; Expression = { $_.Name } },
    @{ Name = 'Job Title'; Expression = { $_.Title } },
    @{ Name = 'Office'; Expression = { $_.City } } |
    Export-Csv -Path .\employees-custom.csv -NoTypeInformation

Get-Content -Path .\employees-custom.csv

Output:

"Employee Name","Job Title","Office"
"Maria Lopez","Analyst","Austin"
"Tom Baker","Manager","Denver"
"Dana Reyes","Engineer","Seattle"

Each hashtable sets the header text in Name and the value in Expression. Spaces in a header are fine, since Export-Csv quotes them. The Select-Object cmdlet also picks and orders the columns you keep.

Don’t use Format-Table for this. Microsoft warns that formatted objects export as format data, not your columns.

Create an empty CSV file with only headers

Export-Csv writes nothing useful without objects. I piped an empty array to it, and both shells created a 0-byte file with no header. For a template file, write the header line yourself:

$headers = 'Name', 'Title', 'City', 'StartDate'
$headers -join ',' | Set-Content -Path .\new-hires.csv

Get-Content -Path .\new-hires.csv
(Import-Csv -Path .\new-hires.csv).Count

Output:

Name,Title,City,StartDate
0

Import-Csv reads it as a valid CSV with zero rows. Check for the file first, as in creating a file only if it doesn’t exist, so a rerun doesn’t wipe filled-in data.

Append rows to a CSV file that already has headers

Add -Append and Export-Csv keeps the existing header. I deliberately listed the properties in a different order than the file:

$hire = [pscustomobject]@{
    StartDate = '2026-11-02'; City = 'Chicago'; Title = 'Designer'; Name = 'Kevin Moore'
}
$hire | Export-Csv -Path .\new-hires.csv -Append -NoTypeInformation
Get-Content -Path .\new-hires.csv

try {
    [pscustomobject]@{ Name = 'Lisa Grant'; Dept = 'IT' } |
        Export-Csv -Path .\new-hires.csv -Append -NoTypeInformation -ErrorAction Stop
}
catch { 'Failed. ' + $_.Exception.Message }

Output:

Name,Title,City,StartDate
"Kevin Moore","Designer","Chicago","2026-11-02"
Failed. Cannot append CSV content to the following file: .\new-hires.csv. The appended object does not have a property that corresponds to the following column: Title. To continue with mismatched properties, add the -Force parameter, and then retry the command.

Kevin’s values landed under the right headers, because Export-Csv matches columns by name, not by position. An object without a Title property is rejected; -Force would write it with only the matching columns.

If the file doesn’t exist yet, -Append simply creates it with a header. Avoid Add-Content for data rows, because it won’t quote commas inside values.

Why does Windows PowerShell 5.1 add a #TYPE line above the headers?

Here’s the same export without -NoTypeInformation. PowerShell 7 wrote the file exactly as before. Windows PowerShell 5.1 added a type line on top:

$employees = @(
    [pscustomobject]@{ Name = 'Maria Lopez'; Title = 'Analyst'; City = 'Austin' }
    [pscustomobject]@{ Name = 'Tom Baker';   Title = 'Manager'; City = 'Denver' }
    [pscustomobject]@{ Name = 'Dana Reyes';  Title = 'Engineer'; City = 'Seattle' }
)

$employees | Export-Csv -Path .\employees.csv
Get-Content -Path .\employees.csv

Output in Windows PowerShell 5.1:

"Name","Title","City"
"Maria Lopez","Analyst","Austin"
"Tom Baker","Manager","Denver"
"Dana Reyes","Engineer","Seattle"
#TYPE System.Management.Automation.PSCustomObject
"Name","Title","City"
"Maria Lopez","A