TL;DR —
Print #andWrite #write to a file in opposite styles.Print #writes text exactly as it looks — no quotes, no commas, you insert the delimiters.Write #writes machine format — every string in quotes, commas between values, dates in#...#— made to be read back byInput #. UsingWrite #for a human-readable CSV is why yours is full of quotes; usePrint #and build the line yourself.
' Write # — machine format, quotes around every string:
Write #n, "Ann", "Rome", 42 ' file gets: "Ann","Rome",42
' Print # — you control the layout, no quotes:
Print #n, "Ann" & "," & "Rome" & "," & 42 ' file gets: Ann,Rome,42
Once a macro can open a channel, the next decision is how the bytes should look on
disk — and VBA gives you two statements that answer that question in completely opposite ways. Print #
writes what you see; Write # writes what a program can parse back. Pick the wrong one and the file is
technically valid but useless to whoever opens it next: a CSV drowning in quotation marks, or a "CSV"
whose columns are all in one cell. Knowing which statement produces which bytes is the entire craft.
What you'll learn
- The mental model —
Print #writes what you see,Write #writes what a program reads back - Why
Write #puts quotes around every string (and when that is exactly right) - Why a comma in a
Print #list inserts print zones, not CSV commas - How to build a clean CSV line with
Print #and your own delimiters - Why
For Outputtruncates whileFor Appendadds - The decimal-comma locale trap, and why built-in writing is ANSI not UTF-8
The mental model: display format vs machine format
The insight that settles almost every "why does my file look like that" question: Print # and
Write # serve two different readers.
Print #writes display format. It puts the text on disk exactly as it would appear in a cell or aMsgBox— no quotation marks, no automatic separators, no type markers. You decide the layout.Write #writes machine format. It wraps every string in quotation marks, separates values with commas, writes dates as#2026-01-31#,Trueas#TRUE#, andEmptyas#NULL#. It is a serialisation format, designed so thatInput #can read the exact same values back.
So the choice is never about taste — it is about who reads the file next. A program that will parse
it back with Input # wants Write #. A human, Excel, or any other tool wants Print #.
Why Write # fills your CSV with quotes
This is the number-one complaint, and it is not a bug — it is Write # doing its job:
Write #n, "Ann", "Rome", 42
' file: "Ann","Rome",42
Those quotation marks are deliberate: they let Input # tell a string containing a comma ("Rome, Italy")
apart from two separate fields. Perfect for round-tripping VBA-to-VBA; wrong for a report a person opens.
When someone asks "why is my exported CSV full of quotes," the answer is always the same — the code used
Write # when it wanted Print #.
Write # is the right tool in exactly one situation: you wrote the file with Write # and you will read
it back with Input #, entirely inside VBA. For anything a human or Excel opens, reach for Print #.
Why a comma in Print # is not a CSV comma
The obvious fix — "just use Print # with commas" — has its own trap, because the comma means something
different in a Print # list:
Print #n, "Ann", "Rome", 42 ' NOT Ann,Rome,42
A comma between items in a Print # list inserts a print zone — a tab-stop roughly every 14
characters, a leftover from console printing. So the line above comes out with wide gaps, not commas, and
your "CSV" opens as a single mangled column. The fix is to stop letting Print # insert separators and
build the whole line yourself:
Print #n, "Ann" & "," & "Rome" & "," & 42 ' Ann,Rome,42 — one string you control
Concatenate the fields with the literal delimiter you want, pass Print # a single string, and you
get exactly those bytes. A trailing semicolon (Print #n, s;) suppresses the newline when you need to
assemble a line in pieces.
Building a real CSV row with Print
Putting it together, the reliable "write a CSV" pattern joins each row into one string and lets Print #
emit it verbatim:
Sub ExportRange(ByVal rng As Range, ByVal path As String)
Dim n As Integer, r As Range, cell As Range, line As String
n = FreeFile
Open path For Output As #n ' truncates; use Append to add
For Each r In rng.Rows
line = ""
For Each cell In r.Cells
line = line & cell.Value & "," ' your delimiter, your rules
Next cell
If Len(line) > 0 Then line = Left(line, Len(line) - 1) ' drop trailing comma
Print #n, line ' one full row, exactly as built
Next r
Close #n
End Sub
This is the shape almost every production CSV export takes, because it is the only way to control quoting
and delimiters precisely. If a field can itself contain a comma, wrap that field in quotation marks
yourself — that is a decision Print # leaves to you, which is the point.
Output truncates, Append adds — and the locale and encoding traps
Two more details decide whether the file is correct:
- Mode —
For Outputrewrites the file from empty every run;For Appendadds to the end. A running log such asC:\Logs\run.logmust be openedFor Append, or each run wipes the last. - Decimal comma — on a German, French, or Spanish system,
CStr(3.5)can produce3,5, whose comma collides with your CSV delimiter and splits one number into two columns. Force a point withFormat(x, "0.00")orStr(x)(which always uses.), or wrap numeric fields in quotation marks. - Encoding — built-in
Print #writes ANSI, not UTF-8. Accented or non-Latin text (é,ü, 日本語) is written in the system code page and garbles when the file is opened as UTF-8. For Unicode output, useADODB.Streamor theFileSystemObject'sCreateTextFile(path, True, True).
The honest verdict: choose by who reads the file
Print # and Write # are not interchangeable, and the whole decision comes down to one question:
- Write # only for VBA-to-VBA round trips → its quotes, commas, and
#...#markers exist soInput #can rebuild the exact values. A human should never see them. - Print # for everyone else → build each line yourself with the delimiters you want; a comma in a
Print #list is a print zone, not a separator. - Append to accumulate, Output to rewrite → the mode decides whether history survives.
- Force
.and go UTF-8 for real CSV → guard the decimal-comma locale trap and the ANSI encoding trap before the file leaves your machine.
The clearest signal you have reached for the wrong statement is quotation marks you did not ask for. If
they appear, you wrote with Write #; switch to Print # and own the layout.
How ExcelMaster helps
Exporting clean data means knowing that Write # wraps everything in quotes for Input #, that a comma
in Print # is a print zone rather than a separator, that For Output wipes the file while For Append
grows it, and that a decimal comma or ANSI encoding can quietly corrupt a CSV — a lot of judgment for
"write a file."
ExcelMaster writes the export that
opens cleanly. Describe the job — "save this range as a CSV," or "append a line to a log each run" — and it
produces the Print # line-builder with the right delimiters, the correct mode, and locale-safe number
formatting, anchored to ThisWorkbook.Path. You describe the file a person or program
needs to read; it writes the code that produces exactly those bytes.
Frequently asked questions
What is the difference between Print # and Write # in VBA?
Print # writes text exactly as it appears — no quotation marks and no separators, so you control the
layout. Write # writes machine-readable format — every string wrapped in quotation marks, commas
between values, and dates in #...# — designed to be read back by Input #. Use Print # for anything a
human or Excel opens, and Write # only for files you will read back into VBA with Input #.
Why is my VBA CSV full of quotes?
Because the code used Write #, which deliberately wraps every string in quotation marks so Input # can
parse it back. That is correct for a VBA-to-VBA round trip but wrong for a report. Switch to Print # and
build each row yourself — Print #n, a & "," & b — to get a clean CSV with no extra quotation marks.
Why does Print # not separate my values with commas?
A comma between items in a Print # list inserts a print zone (a tab stop about every 14 characters),
not a CSV comma, so the fields come out with wide gaps in a single column. Pass Print # a single
concatenated string instead — Print #n, a & "," & b & "," & c — so the delimiters are exactly the ones
you wrote.
How do I write a text file without overwriting it in VBA?
Open it For Append rather than For Output. For Output truncates the file to empty before writing, so
each run wipes the previous contents; For Append creates the file if it is missing and otherwise adds to
the end. Take the channel number from FreeFile and close it with Close #n when
done.
How do I write UTF-8 or accented characters to a file in VBA?
Built-in Print # and Write # write ANSI in the system code page, so accented or non-Latin text
garbles when the file is read as UTF-8. For Unicode output use ADODB.Stream, or the
FileSystemObject's CreateTextFile(path, True, True) where the last
argument requests Unicode. Also force a decimal point with Format(x, "0.00") so a locale decimal comma
does not break your delimiters.
Tested in
Tested in: Excel 365 (Windows 11), VBA 7.1 — last verified 2026-08-25.
Related guides: VBA FreeFile & Open · VBA Read Text File · VBA FileSystemObject · VBA Save Workbook · VBA CurDir
