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

VBA Print # vs Write # in Excel — Write a Text File or CSV (and Why Yours Has Quotes Around Everything)

|

VBA Print # vs Write # in Excel — Write a Text File or CSV (and Why Yours Has Quotes Around Everything)

TL;DRPrint # and Write # 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 by Input #. Using Write # for a human-readable CSV is why yours is full of quotes; use Print # 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 Output truncates while For Append adds
  • 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 a MsgBox — 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#, True as #TRUE#, and Empty as #NULL#. It is a serialisation format, designed so that Input # 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:

  • ModeFor Output rewrites the file from empty every run; For Append adds to the end. A running log such as C:\Logs\run.log must be opened For Append, or each run wipes the last.
  • Decimal comma — on a German, French, or Spanish system, CStr(3.5) can produce 3,5, whose comma collides with your CSV delimiter and splits one number into two columns. Force a point with Format(x, "0.00") or Str(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, use ADODB.Stream or the FileSystemObject's CreateTextFile(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 so Input # 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