Upload a file
  1. base64hub
  2. Base64 in Code
  3. Go (Golang)
Code

Base64 in Go

Go’s encoding/base64 package has four ready-made encodings and streaming wrappers. Picking the right one of the four is most of the work. Outputs below come from Go 1.25.

Encode and decode a string

Go strings are already UTF-8, so converting to []byte is all you need:

package main

import (
	"encoding/base64"
	"fmt"
)

func main() {
	enc := base64.StdEncoding.EncodeToString([]byte("héllo wörld"))
	fmt.Println(enc) // aMOpbGxvIHfDtnJsZA==

	dec, err := base64.StdEncoding.DecodeString(enc)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(dec)) // héllo wörld
}

Always check err. On failure, DecodeString can return the bytes decoded so far together with the error, so using the result without the check gives you silently truncated data.

The four encodings: Std, URL, Raw

VariableAlphabetPadding<<???>> encodes to
StdEncoding+ /yesPDw/Pz8+Pg==
URLEncoding- _yesPDw_Pz8-Pg==
RawStdEncoding+ /noPDw/Pz8+Pg
RawURLEncoding- _noPDw_Pz8-Pg
d := []byte("<<???>>")
base64.URLEncoding.EncodeToString(d)    // PDw_Pz8-Pg==
base64.RawURLEncoding.EncodeToString(d) // PDw_Pz8-Pg

b, err := base64.RawURLEncoding.DecodeString("PDw_Pz8-Pg") // "<<???>>", nil

Decoding is strict about padding in both directions. The padded encodings reject unpadded input, and the raw ones reject =:

base64.StdEncoding.DecodeString("aGk")
// illegal base64 data at input byte 0
base64.RawURLEncoding.DecodeString("PDw_Pz8-Pg==")
// illegal base64 data at input byte 10

If you accept input from several sources, strip trailing = with strings.TrimRight(s, "=") and decode with a Raw encoding. You can also build custom encodings with base64.NewEncoding(alphabet) and .WithPadding(base64.NoPadding). The Base64 vs Base64URL page explains where each variant shows up.

Encode and decode a file

data, err := os.ReadFile("hello.txt")
if err != nil {
	log.Fatal(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(data)) // aGVsbG8K

raw, err := base64.StdEncoding.DecodeString("aGVsbG8K")
if err != nil {
	log.Fatal(err)
}
os.WriteFile("copy.txt", raw, 0o644)

Streaming with NewEncoder and NewDecoder

For large files, wrap an io.Writer or io.Reader. Memory use stays constant:

in, _ := os.Open("big.bin")
defer in.Close()
out, _ := os.Create("big.b64")
defer out.Close()

w := base64.NewEncoder(base64.StdEncoding, out)
if _, err := io.Copy(w, in); err != nil {
	log.Fatal(err)
}
if err := w.Close(); err != nil { // flushes the last partial block
	log.Fatal(err)
}

w.Close() does not close out; it writes the final one or two bytes and padding. Leaving it out is the classic Go Base64 bug: the output looks fine but is short by up to four characters. Decoding is symmetric:

r := base64.NewDecoder(base64.StdEncoding, f)
io.Copy(dst, r)

Streaming and in-memory results were identical for a 300 KB test file. The decoder skips \r and \n, so it reads MIME-wrapped input directly. To precompute buffer sizes, EncodedLen(5) returns 8 and RawStdEncoding.EncodedLen(5) returns 7; the size overhead guide has the formula behind it.

Go 1.22 added AppendEncode and AppendDecode, which append to an existing slice. Handy for headers:

dst := base64.StdEncoding.AppendEncode([]byte("Basic "), []byte("user:pass"))
fmt.Println(string(dst)) // Basic dXNlcjpwYXNz

The Basic Auth header generator builds the same value in the browser.

Validation and CorruptInputError

Every decoding error is a base64.CorruptInputError, an int64 offset of the first bad byte:

_, err := base64.StdEncoding.DecodeString("PDw_Pz8-Pg==")
var ce base64.CorruptInputError
if errors.As(err, &ce) {
	fmt.Println(err) // illegal base64 data at input byte 3
}

Results we got with StdEncoding:

InputError
PDw_Pz8-Pg==illegal base64 data at input byte 3 (the _)
aGkillegal base64 data at input byte 0
aGk=!!illegal base64 data at input byte 4
aGk==illegal base64 data at input byte 4
aGVs bG8Killegal base64 data at input byte 4 (space)
aGl=no error, returns hi
"aGVs\nbG8K"no error, newline skipped

aGl= has non-zero padding bits. To reject non-canonical input like that, use .Strict():

_, err = base64.StdEncoding.Strict().DecodeString("aGl=")
// illegal base64 data at input byte 3

The Base64 validator highlights the same positions; common Base64 errors has the usual causes.

Pitfalls

  • Decoding JWT parts with StdEncoding. Tokens are unpadded Base64URL. Use RawURLEncoding, or inspect the token with the JWT decoder first.
  • Ignoring the partial result. "aGk=!!" returns "hi" and an error. Do not use the bytes when err != nil.
  • Forgetting Close() on the encoder. The last block is lost.
  • Spaces from copy and paste. The decoder skips only \r and \n. Remove other whitespace first with strings.Join(strings.Fields(s), "").

Version notes

  • RawStdEncoding and RawURLEncoding: Go 1.5.
  • Strict(): Go 1.8.
  • AppendEncode, AppendDecode: Go 1.22.
  • The package has no MIME line-wrapping encoder; the mime and net/mail packages handle email, or insert \r\n every 76 characters yourself.

FAQ

What is the difference between URLEncoding and RawURLEncoding?

Both use the URL-safe alphabet with - and _. URLEncoding writes = padding and requires it when decoding; RawURLEncoding omits padding and rejects it. JWTs use RawURLEncoding.

What does 'illegal base64 data at input byte 0' mean?

It is a base64.CorruptInputError, and the number is the offset where decoding failed. At byte 0 with a short string it usually means the length is wrong, often missing padding; use the Raw encoding for unpadded input.

Does Go's Base64 decoder ignore newlines?

Yes. \r and \n are skipped by all four encodings, so MIME-wrapped input decodes. Spaces and other characters are errors.

Keep reading