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
| Variable | Alphabet | Padding | <<???>> encodes to |
|---|---|---|---|
StdEncoding | + / | yes | PDw/Pz8+Pg== |
URLEncoding | - _ | yes | PDw_Pz8-Pg== |
RawStdEncoding | + / | no | PDw/Pz8+Pg |
RawURLEncoding | - _ | no | PDw_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:
| Input | Error |
|---|---|
PDw_Pz8-Pg== | illegal base64 data at input byte 3 (the _) |
aGk | illegal base64 data at input byte 0 |
aGk=!! | illegal base64 data at input byte 4 |
aGk== | illegal base64 data at input byte 4 |
aGVs bG8K | illegal 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. UseRawURLEncoding, or inspect the token with the JWT decoder first. - Ignoring the partial result.
"aGk=!!"returns"hi"and an error. Do not use the bytes whenerr != nil. - Forgetting
Close()on the encoder. The last block is lost. - Spaces from copy and paste. The decoder skips only
\rand\n. Remove other whitespace first withstrings.Join(strings.Fields(s), "").
Version notes
RawStdEncodingandRawURLEncoding: Go 1.5.Strict(): Go 1.8.AppendEncode,AppendDecode: Go 1.22.- The package has no MIME line-wrapping encoder; the
mimeandnet/mailpackages handle email, or insert\r\nevery 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.