Do you have to deal with Base64 format? Then this site is perfect for you! Use our super handy online tool to encode or decode your data.

Base64 Encoding in Visual Basic: A Complete Guide

Here is a problem that Visual Basic developers have met for twenty-five years: you have a JPEG, a binary blob, a license file, or a perfectly ordinary sentence, and the channel in front of you only accepts text. A JSON field, an environment variable, an email attachment, a URL, a config file, a database column typed as text, and all the other doors in the building have one rule in common: printable characters only. Base64 is the bouncer that lets binary in. It rewrites your bytes as a stream of letters, digits, plus, slash, and equals, so anything that travels as text can carry them. And the good news: every piece of the encoder you could ever need is already inside the .NET runtime. No packages, no components, no ceremony.

In one breath, because the home page of this site goes deep on the format itself: Base64 takes three input bytes and writes them as four characters from a 64-symbol alphabet, adding one or two trailing = characters when the byte count does not divide evenly. That four-for-three trade is why encoded data runs about a third larger than the original, the famous size tax you pay once per encoding. Everything else in this article is about making the encoding you produce do exactly what the next system expects: the right alphabet, the right padding, the right line breaks, and the right character set.

The Encoder Toolbox: Everything Is Built In

The encoding side of the runtime grew across the same four waves as the decoding side, so the toolbox has a long tail of still-supported options. Here is the full family and the job each one is built for:

API Available since What it is for
System.Convert.ToBase64String .NET Framework 1.1 (2003) The classic. One array in, one string out, with overloads for array subsets, spans, and optional MIME-style line breaks.
System.Convert.ToBase64CharArray .NET Framework 1.1 (2003) Writes the encoded characters into a character buffer you already allocated, and returns how many characters it used.
System.Convert.TryToBase64Chars .NET Core 2.1 (2018) Span-based, allocation-light encoding into a character span you provide, with a Boolean answer instead of an exception.
System.Buffers.Text.Base64 .NET Core 2.1 (2018) Low-level, span-based encoding: write into your own UTF-8 buffer, inflate in place, and size buffers with GetMaxEncodedToUtf8Length.
System.Buffers.Text.Base64Url .NET 9 (2024) The URL-safe alphabet (- and _ instead of + and /) without padding. On older runtimes it rides in the Microsoft.Bcl.Memory NuGet package.
ToBase64Transform + CryptoStream .NET Framework 1.1 (2003) Streaming encoding: read a file in chunks, write encoded text out, keep memory flat on giant inputs.

For the version landscape: .NET 10 is the current long-term-support release (November 2025, supported until November 2028), .NET 8 and .NET 9 are supported until November 2026, and .NET 11 is in preview with a fresh batch of Base64 convenience methods on the way. Everything in the table above is stable across all of those. The single version gate is Base64Url: built in from .NET 9 onward, available on .NET Framework 4.6.2 and newer through the Microsoft.Bcl.Memory package, and that is the only package this article ever asks you to install. To get a scratch project going, the .NET SDK ships Visual Basic in the box:

dotnet new console -lang VB -o Packer
cd Packer
dotnet run

Your First Encode: Bytes In, Text Out

Ninety percent of encoding life in Visual Basic is two calls, and the order matters: the encoder takes bytes, not text, so if you start with a string you first choose an encoding to turn it into bytes, and only then do the Base64 step. Here is the whole dance:

Imports System
Imports System.Text
Module Encoder
    Sub Main()
        Dim text As String = "Man"
        Dim bytes() As Byte = Encoding.UTF8.GetBytes(text)
        Dim packed As String = System.Convert.ToBase64String(bytes)
        Console.WriteLine(packed)
        ' TWFu
    End Sub
End Module

That three-line middle section is the entire craft, and the string "TWFu" is the perfect smoke test for any encoder you write. The overloads give you control once you need it. The subset forms encode a slice of an array without copying it out first, which is handy when the real payload sits inside a larger buffer:

Imports System
Module SlicePacker
    Sub Main()
        Dim data() As Byte = {1, 2, 3, 4, 5, 6, 7, 8}
        Dim packed As String = System.Convert.ToBase64String(data, 2, 4)
        Console.WriteLine(packed)
        ' AwQFBg==  (only the bytes 3 through 6 were encoded)
    End Sub
End Module

And the array-to-char form, ToBase64CharArray, writes into a character buffer you allocate and tells you how many characters it filled, which is the right tool when the destination is part of a larger text structure you are building by hand. Note the house rule for Visual Basic syntax: a byte array is written Byte() with the empty parentheses. Drop them and you have a single byte, and Option Strict On (on in the Visual Studio templates, worth enabling in SDK-style projects) will catch the mix-up at compile time.

Shaping the Output: Padding, Line Breaks, and Exact Sizes

Encoding the same bytes twice can legitimately produce two different strings, and the differences all come down to output shaping. First, padding: when the input length is not a multiple of three, the encoder tops up the final group with one or two = characters. The RFC says to include them unless the spec you are following says otherwise, and ToBase64String includes them by default. Second, line breaks: the second parameter of the formatting overloads, Base64FormattingOptions.InsertLineBreaks, makes the encoder emit 76-character lines separated by CRLF, which is exactly the MIME rule. MIME itself inherited the 76-character limit from the old 64-character lines of PEM, and both limits trace back to restrictions inside SMTP. If your consumer is an email pipeline, turn line breaks on; if it is a URL, a JSON field, or a database column, leave them off, because an invisible CRLF inside your data will find a way to surprise you later:

Imports System
Module MimePacker
    Sub Main()
        Dim data(113) As Byte
        For i As Integer = 0 To 113
            data(i) = CByte(i)
        Next
        Dim wrapped As String = System.Convert.ToBase64String(data, Base64FormattingOptions.InsertLineBreaks)
        Console.WriteLine(wrapped.Length)
        ' 154: two 76-character lines plus one CRLF between them
    End Sub
End Module

Third, exact sizes, because you will want to pre-allocate buffers and column widths. The rule is four characters for every three input bytes, rounded up: 1000 bytes becomes 1336 characters. Rather than doing the arithmetic by hand, the runtime has a helper that returns the maximum encoded length for a given input size, which is what you pass to buffer allocation:

Imports System.Buffers.Text
Module Sizing
    Sub Main()
        Dim dataLength As Integer = 1000
        Dim textNeeded As Integer = Base64.GetMaxEncodedToUtf8Length(dataLength)
        Console.WriteLine(textNeeded)
        ' 1336
    End Sub
End Module

That same four-for-three ratio is the size tax in its purest form: every encoded value is roughly 33 percent larger than the bytes it carries, so plan your storage and transfer sizes with that margin in mind. And one behavior worth knowing before it bites you: if you decode a string and then re-encode the result, the new string is not guaranteed to match the original one, because whitespace disappears and padding gets normalized. Compare decoded bytes when you need to compare values, not the encoded text.

Choosing the Character Set Before You Encode

Because the first step of text encoding is "string to bytes", the character set you pick decides what the receiver sees when it decodes. Visual Basic strings are UTF-16 inside the runtime, but the bytes you emit should match what the other side expects to read, and the menu of choices is short:

  • Encoding.UTF8: the right default for the web, APIs, and anything modern. It round-trips every Unicode character the language can hold.
  • Encoding.Unicode: UTF-16 little-endian. A reasonable choice when both ends of the pipe are .NET programs that explicitly agreed on UTF-16, and nothing more.
  • Encoding.ASCII: 7-bit only, and it will silently replace anything else with a question mark. Encoding "Café" as ASCII gives you the bytes for "Caf?", which will decode back to exactly that, question mark and all.
  • Encoding.Default: the ANSI code page of the local machine. On a Japanese-locale system it is not the same as on a German one, so the same string encodes differently on different computers. A classic source of "works on my machine" mysteries.
Imports System
Imports System.Text
Module CharsetPacker
    Sub Main()
        Dim text As String = "Café"
        Dim utf8() As Byte = Encoding.UTF8.GetBytes(text)
        Dim ascii() As Byte = Encoding.ASCII.GetBytes(text)
        Console.WriteLine(System.Convert.ToBase64String(utf8))
        ' Q2Fmw6k=
        Console.WriteLine(System.Convert.ToBase64String(ascii))
        ' Q2FmPw==  (the accent became a question mark)
    End Sub
End Module

Two different Base64 strings, one word, and only one of them survives the trip. The practical rule: unless the protocol you are following names another scheme, encode text as UTF-8 and say so.

Base64Url: The Alphabet That Survives URLs

The standard alphabet contains + and /, and both characters have jobs of their own inside URLs, so Base64 built on that alphabet breaks the moment it lands in a query string or a path segment. The fix, standardized in section 5 of RFC 4648, swaps the two offenders for - and _, which are URL-safe, and usually drops the trailing padding because the data length already tells the decoder where the data ends. This variant, known as Base64Url, is the alphabet of JWTs, API tokens, and a growing number of APIs. .NET 9 added a dedicated class for it, and it is built with one opinion worth knowing: it omits padding by design:

Imports System.Buffers.Text
Module UrlSafePacker
    Sub Main()
        Dim data() As Byte = {219, 255, 0, 63, 16}
        Dim packed As String = Base64Url.EncodeToString(data)
        Console.WriteLine(packed)
        ' 2_8APxA  (dash and underscore, no trailing padding)
    End Sub
End Module

The example above is a good one: the chosen bytes make both of the special characters appear, so you can see the swap happen. When you are on an older runtime, the same alphabet is two character replacements plus a trim, and you get a drop-in compatible result:

Imports System
Module CompatPacker
    Function ToUrlSafe(ByVal packed As String) As String
        Return packed.Replace("+"c, "-"c).Replace("/"c, "_"c).TrimEnd("="c)
    End Function
End Module

On .NET Framework 4.6.2 or newer, the Microsoft.Bcl.Memory package gives you the real Base64Url class instead. Either way, mind the padding line in the sand: .NET's Base64Url output has no padding, while some libraries in other ecosystems add it (and a few strict decoders insist on it). JWT, for example, requires the unpadded form, so the .NET default is exactly right there. When you cross an ecosystem boundary, check the other side's expectation before you ship the string.

Packing Files

Files are the original use case: turn a binary file into a text file that email, FTP, and configuration systems will all happily carry. In Visual Basic the whole job is three calls, one of which reads and one of which writes:

Imports System.IO
Module FilePacker
    Sub Main()
        Dim bytes() As Byte = File.ReadAllBytes("photo.png")
        Dim packed As String = System.Convert.ToBase64String(bytes)
        File.WriteAllText("photo.b64", packed)
    End Sub
End Module

Keep the size ratio in your pocket: a 10 megabyte photo becomes a text file of about 13.4 megabytes. The operation is fast on modern hardware (more on that below), so the cost is almost always storage and bandwidth rather than CPU, which is the usual bill for a 33 percent tax. When the file is only going to live next to the text that references it, this pattern is perfectly fine; when the file is large and long-lived, ask whether the channel really needs the text form at all.

Images: Building Data URIs by Hand

The data URI scheme (RFC 2397) embeds file contents directly in a URL: data:, the media type, the literal marker ;base64, a comma, and the encoded bytes. Browsers use them to inline small images and fonts, and WPF can build an image straight from one. Building the URI in Visual Basic is one string concatenation, and consuming it is a single constructor:

Imports System.IO
Imports System.Windows.Media.Imaging
Module DataUriPacker
    Sub Main()
        Dim bytes() As Byte = File.ReadAllBytes("logo.png")
        Dim dataUri As String = "data:image/png;base64," & System.Convert.ToBase64String(bytes)
        Dim image As New BitmapImage(New Uri(dataUri, UriKind.Absolute))
        ' image can now be assigned to an Image control
    End Sub
End Module

The RFC itself warns that data URIs are useful only for short values, and HTML documents impose their own attribute length limits, so the sensible scope is icons, avatars, thumbnails, and tiny background patterns. The media type has to match the bytes you actually encoded, because nothing downstream will re-derive it from the content.

HTTP: Auth Headers and JSON Payloads

On the wire, the two places you will encode by hand are the HTTP Basic auth header and JSON fields that carry binary or pre-encoded data. Basic auth is the most visible: the header is the word Basic, a space, and the Base64 of username:password joined with a colon. Building it is one encoding call:

Imports System
Imports System.Text
Module AuthPacker
    Function MakeBasicHeader(ByVal user As String, ByVal password As String) As String
        Dim raw() As Byte = Encoding.UTF8.GetBytes(user & ":" & password)
        Return "Basic " & System.Convert.ToBase64String(raw)
    End Function
End Module

The JSON case is equally routine. If an API wants an image or a certificate inside a request body, you encode the bytes and drop the string into the payload, and System.Text.Json (in the box since .NET Core 3.0) handles the serialization around it:

Imports System.Text.Json
Module ApiPacker
    Function WidgetPayload(ByVal name As String, ByVal imageBytes() As Byte) As String
        Dim payload = New With {
            .name = name,
            .image = System.Convert.ToBase64String(imageBytes)
        }
        Return JsonSerializer.Serialize(payload)
    End Function
End Module

Two house rules: send credentials only over HTTPS, because over plain HTTP the Base64 is a costume, not a lock, and never log the raw auth header or the credentials it decodes to.

Email Attachments and MIME Wrapping

Email is where Base64 earned its living. SMTP was built for 7-bit ASCII, so a binary attachment has to become text before it can fly, and the MIME standard (RFC 2045) made the choice: Base64, wrapped at 76 characters per line, declared with a Content-Transfer-Encoding: base64 header. If you work with the System.Net.Mail classes, the whole ritual is two lines of setup, because the mail library does the wrapping for you at send time:

Imports System.IO
Imports System.Net.Mail
Imports System.Net.Mime
Module MailPacker
    Sub Main()
        Using message As New MailMessage("me@example.com", "you@example.com")
            message.Subject = "Quarterly report"
            message.Body = "Please find the report attached."
            Using stream As New FileStream("report.bin", FileMode.Open, FileAccess.Read)
                Dim attachment As New Attachment(stream, "report.bin")
                attachment.ContentEncoding = TransferEncoding.Base64
                message.Attachments.Add(attachment)
            End Using
        End Using
    End Sub
End Module

You only need to produce the wrapped form yourself, with Base64FormattingOptions.InsertLineBreaks, when you are writing raw MIME text by hand: a mailer test fixture, a legacy gateway, or a tool that spits out .eml files. The 76-character rule is not a style preference; some receiving systems truncate longer lines, which is why the limit has survived in the standard for decades.

Storing Encoded Values: Databases, Config Files, and Env Vars

Text-only storage keeps asking for Base64: a database column typed as text, an XML configuration value, an environment variable. You encode the bytes, store the string, and decode it on the way out. The encoding side is always the same one-liner, but the storage side has limits that make the size tax concrete. A regular NVARCHAR column in SQL Server stops at 8,000 characters, which is room for about 6,000 bytes of binary before the 33 percent overhead pushes you over, past which you reach for the MAX types or, more honestly, for a real binary column. On Windows, the entire environment block for a process is capped around 32 kilobytes, so "keep the whole license blob in an env var" has a hard ceiling, and a handful of large variables can use up the budget for the whole process:

Imports System
Module EnvPacker
    Sub Main()
        Dim blob() As Byte = {1, 2, 3, 4, 5}
        Dim packed As String = System.Convert.ToBase64String(blob)
        Environment.SetEnvironmentVariable("APP_BLOB", packed)
        Console.WriteLine(packed)
        ' AQIDBAU=
    End Sub
End Module

Configuration files follow the same shape, with the value living in XML or JSON text and the decode happening in your startup code. One legacy note for the enterprise corner: WCF and XML data contracts serialize a byte array as the base64Binary XML schema type, so a large body of older .NET services stores binary exactly this way, and the value you find in that XML is plain ToBase64String output.

JWTs: Building the Compact Form

A JSON Web Token in compact form is three dot-separated pieces of Base64Url: the header, the payload, and a signature. The first two are plain JSON, and the third is a cryptographic proof that a holder of the right key built this token. Building the unsigned shape by hand is two encodings and a string join, but a real JWT needs the signature step, and a small HMAC-SHA256 example makes the whole thing concrete:

Imports System
Imports System.Buffers.Text
Imports System.Security.Cryptography
Imports System.Text
Module JwtPacker
    Function BuildHs256Jwt(ByVal headerJson As String, ByVal payloadJson As String, ByVal secret() As Byte) As String
        Dim header As String = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(headerJson))
        Dim body As String = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(payloadJson))
        Dim signingInput As String = header & "." & body
        Using hmac As New HMACSHA256(secret)
            Dim signature() As Byte = hmac.ComputeHash(Encoding.UTF8.GetBytes(signingInput))
            Return signingInput & "." & Base64Url.EncodeToString(signature)
        End Using
    End Function
End Module

Run it with the header {"alg":"HS256","typ":"JWT"} and a payload of your choosing, and the result is a genuine compact JWT: no padding anywhere, URL-safe characters in all three parts. Notice that the signature is Base64Url too, because the entire token has to survive a URL or an HTTP header. For production systems, the System.IdentityModel.Tokens.Jwt package (the IdentityModel suite from the Microsoft Entra team) builds, signs, and verifies these tokens for you, which is the layer where key management, algorithm pinning, and expiry checks belong. Hand-rolling the encoding is fine for understanding and for small tools; for anything that guards access, let the library carry the weight.

Pitfalls: Where VB Encoders Slip

The traps here are a mix of language habits and output-shaping surprises, and most of them cost a debugging session rather than a crash:

  • Byte versus Byte(). The encoder wants an array. In Visual Basic a single byte is Byte and an array is Byte(), and the difference is one pair of parentheses. Under Option Strict On a wrong guess is a compile error; with it off, you may instead get a runtime surprise. Keep the strictness on and the parentheses visible.
  • The Encoding.Default trap, in reverse. On the encoding side the locale trap means your bytes depend on the machine that produced them. Encode a string as Default on one machine and it is not the same string when decoded with a different code page. For anything that crosses machines, name the encoding explicitly, usually UTF-8.
  • CRLF has a way in. InsertLineBreaks is wonderful for MIME and dreadful for URLs, JSON, and database text columns, where it inserts a carriage return and a line feed that nobody asked for. Use it only when the consumer expects wrapped lines, and when in doubt, use the default None.
  • Padding mismatches at the boundary. .NET's Base64Url emits no padding, while some libraries in other ecosystems add it (and a few strict decoders require it). When your encoded value crosses an ecosystem, confirm the other side's expectation before you ship the string; JWT wants the unpadded form, which is the .NET default.
  • Round trips are not identity. Decode a wrapped, padded string and re-encode it, and you get a clean single line with fresh padding, not the original text. If your logic compares encoded values for equality, compare the decoded bytes instead.
  • The size ceiling is real. The output length formula, four characters per three bytes rounded up, overflows a 32-bit count at roughly 1.5 gigabytes of input, and the encoder answers with an OutOfMemoryException rather than a partial string. For inputs anywhere near that scale, stream instead (below).
  • The span wall. The span-based encoders are callable from VB at the call site: pass your Byte() or Char() arrays straight in and the compiler converts them. But you cannot declare a variable, field, or parameter of type Span or ReadOnlySpan; the compiler refuses with "Types with embedded references are not supported in this version of your compiler". The VB idiom is to call the span APIs with plain arrays and never store a span.
  • BitConverter is not Base64. BitConverter.ToString(bytes) renders hex with dashes between the pairs, so it is a tempting wrong answer that produces 4D-61-6E where the other system expects TWFu. For Base64, the class is System.Convert, every time.

Speed and Size: Performance Notes

The "slow text codec" reputation of Base64 does not survive contact with the modern runtime. The encoder inside .NET runs hardware-vectorized code when the machine supports it, with dedicated fast paths for AVX-512, AVX2, and SSE instruction sets, and the AVX-512 path chews through 48 bytes per step. For ordinary payloads, the classic ToBase64String call is fast enough that the algorithm is rarely the bottleneck; the costs you feel are the 33 percent size tax and, for hot paths, the intermediate allocations. If you are encoding millions of small values, the span-based APIs are the refinement: TryToBase64Chars writes into a character span you control and reports success with a Boolean, and System.Buffers.Text.Base64 takes it further, encoding straight into UTF-8 buffers you allocate and even inflating data in place:

Imports System
Imports System.Buffers
Imports System.Buffers.Text
Module BufferPacker
    Sub Main()
        Dim data() As Byte = {1, 2, 3, 4, 5}
        Dim textLength As Integer = Base64.GetMaxEncodedToUtf8Length(data.Length)
        Dim buffer(textLength) As Byte
        Dim written As Integer
        Dim consumed As Integer
        Dim status As OperationStatus = Base64.EncodeToUtf8(data, buffer, consumed, written)
        Dim packed As String = Encoding.ASCII.GetString(buffer, 0, written)
        Console.WriteLine(packed)
        ' AQIDBAU=
    End Sub
End Module

The pattern to notice is that you size the buffer with the helper, encode into it, and convert only the used prefix to a string, which keeps the intermediate surface as small as it can be. And for files that are large enough to make strings uncomfortable, the streaming pair keeps memory flat: the ToBase64Transform transform wrapped in a CryptoStream reads your input in chunks and writes encoded text out, so a two gigabyte file never has to become a 2.7 gigabyte string in one piece:

Imports System.IO
Imports System.Security.Cryptography
Module StreamPacker
    Sub EncodeFile(ByVal inputPath As String, ByVal packedPath As String)
        Using inputStream As New FileStream(inputPath, FileMode.Open, FileAccess.Read)
            Using packedStream As New CryptoStream(New FileStream(packedPath, FileMode.Create), New ToBase64Transform(), CryptoStreamMode.Write)
                Dim buffer(65535) As Byte
                While True
                    Dim read As Integer = inputStream.Read(buffer, 0, buffer.Length)
                    If read = 0 Then Exit While
                    packedStream.Write(buffer, 0, read)
                End While
            End Using
        End Using
    End Sub
End Module

One forward note: the preview .NET 11 libraries add new convenience and span overloads to the existing Base64 types, so if your project can track previews, the toolbox keeps growing; if it cannot, everything above is stable on every supported release.

A Short History: From ADODB.Stream to Spans

Long before .NET, Visual Basic programs that needed Base64 borrowed it from the COM world. The classic VB6 and VBA trick (the macro language that still runs inside Excel and Office) used the ADO Stream object: write your bytes into a binary stream, flip the stream to text mode with the special base64 charset, and read the encoded text back. It was clever, it was everywhere, and it is why "base64 VBA" still lights up search engines decades later. The era ended in 2002, when the first .NET version of the language, Visual Basic 7.0, joined the new Common Language Runtime, and the .NET Framework brought System.Convert with ToBase64String out of the box. From .NET Framework 1.1 in 2003, every VB program could encode Base64 with one call and no components to register.

The modern chapters are short. In 2018, .NET Core 2.1 added the allocation-light TryToBase64Chars method and the low-level span-based System.Buffers.Text.Base64 class. In 2024, .NET 9 standardized the URL-safe alphabet as Base64Url, ending a decade of hand-rolled Replace calls. As of 2025, .NET 10 is the long-term-support release carrying all of this, and the preview .NET 11 libraries are adding a new generation of convenience methods, so the encoder from the 2003 one-liner to the span era is a story of the same class getting faster and more precise, never of starting over.

Fun Facts, VB Edition

  • InsertLineBreaks reproduces the MIME 76-character rule exactly, CRLF included, which means the line breaks your encoder writes in 2026 are byte-for-byte the same shape as the ones an email standard defined in the 1990s.
  • The IsNot operator, added with Visual Basic 2005, once made the news as the subject of a Microsoft patent application. Very few language operators can claim that distinction.
  • The first Visual Basic shipped in 1991, before the World Wide Web existed. By the time the data URI scheme appeared in 1998, Base64 had already been carrying email attachments for five years, and VB was about to grow up into a 32-bit language.
  • On hardware with AVX-512, the runtime encoder processes 48 bytes per vector step, which is the difference between a table lookup in a museum and a conveyor belt in a factory.
  • The My namespace, Visual Basic's famous sugar layer from 2005, never needed to add a Base64 helper. System.Convert was always one namespace import away, a rare case where the VB runtime added nothing to a story the framework already told.

The Flip Side

This article has covered the encoding side of Base64 in Visual Basic: the toolbox, the output-shaping decisions, the URL-safe alphabet, and the use cases from files to JWTs. The reverse direction, taking an incoming string and turning it back into the bytes it hides, has its own set of behaviors, forgiveness rules, and pitfalls, and it is covered in full detail in the companion decoding article on the sister site. The link to it sits just below this line, and the tool on the home page remains the quickest way to encode a small payload by hand.

Last updated: 2026-08-30

Related article: Base64 Decoding in Visual Basic: A Complete Guide