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"

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