Upload a file
  1. base64hub
  2. Base64 in Code
  3. Dart
Code

Base64 in Dart

Dart’s dart:convert library includes Base64 codecs for both alphabets, plus a normalize method that fixes padding. Outputs below come from Dart 3.13.

Encode and decode a string

Dart strings are UTF-16 internally, so convert to UTF-8 bytes with utf8.encode before encoding:

import 'dart:convert';

void main() {
  final enc = base64Encode(utf8.encode('héllo wörld'));
  print(enc);                            // aMOpbGxvIHfDtnJsZA==
  print(utf8.decode(base64Decode(enc))); // héllo wörld
}

base64Encode(bytes) is shorthand for base64.encode(bytes), and base64Decode(s) for base64.decode(s). If the decoded bytes are not UTF-8, utf8.decode throws FormatException: Invalid UTF-8 byte; pass allowMalformed: true to get replacement characters instead.

The same two calls build an HTTP Basic Auth header, for example for package:http:

final headers = {'Authorization': 'Basic ${base64Encode(utf8.encode('user:pass'))}'};
// Authorization: Basic dXNlcjpwYXNz

The Basic Auth header generator shows the same value for any credentials.

URL-safe Base64 and normalize()

base64Url is a second codec with the -_ alphabet. Both encoders always write padding, so strip it if your format wants none:

final d = utf8.encode('<<???>>');
print(base64Url.encode(d));                     // PDw_Pz8-Pg==
print(base64Url.encode(d).replaceAll('=', '')); // PDw_Pz8-Pg

Decoding is where Dart is friendlier than most languages. Both decoders accept both alphabets, but they require correct padding. normalize fixes that and also converts the alphabet:

base64Url.normalize('PDw_Pz8-Pg');   // PDw_Pz8-Pg==
base64.normalize('PDw_Pz8-Pg');      // PDw/Pz8+Pg==

utf8.decode(base64Url.decode(base64Url.normalize('PDw_Pz8-Pg')));  // <<???>>
utf8.decode(base64Decode('PDw_Pz8-Pg=='));                         // <<???>>

normalize also throws FormatException if the input is not valid Base64, so it doubles as a validator. JWT segments are unpadded Base64URL; see Base64 vs Base64URL and padding explained, or paste one into the JWT decoder.

Reading the payload of a JWT takes one line once you have normalize:

const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig';
final payload = utf8.decode(base64Url.decode(base64Url.normalize(jwt.split('.')[1])));
print(payload);                        // {"sub":"1234"}
print(jsonDecode(payload)['sub']);     // 1234

This only reads the claims; it does not verify the signature, so never trust them for authorization on the client.

Encode and decode a file

import 'dart:io';

final bytes = await File('hello.txt').readAsBytes();
print(base64Encode(bytes));                              // aGVsbG8K
await File('copy.txt').writeAsBytes(base64Decode('aGVsbG8K'));

Streaming large files

base64.encoder and base64.decoder are stream transformers, so a file can be converted chunk by chunk:

// encode: bytes -> Base64 text -> ASCII bytes on disk
await File('big.bin')
    .openRead()
    .transform(base64.encoder)
    .transform(ascii.encoder)
    .pipe(File('big.b64').openWrite());

// decode: ASCII bytes -> String chunks -> bytes
await File('big.b64')
    .openRead()
    .transform(ascii.decoder)
    .transform(base64.decoder)
    .pipe(File('big.out').openWrite());

The transformers keep leftover bytes between chunks, so chunk sizes do not matter. In both directions, a 300 KB test file came out byte for byte the same as with the in-memory functions.

Flutter: images and data URIs

To show a Base64 image:

Image.memory(base64Decode(b64))

To produce Base64 from an asset or a picked file, read the bytes and call base64Encode. For data URIs, UriData handles the prefix:

UriData.parse('data:text/plain;base64,aGk=').contentAsBytes();  // [104, 105]
UriData.fromBytes(pngBytes, mimeType: 'image/png').toString();   // data:image/png;base64,...

Passing a data URI straight to base64Decode fails with Invalid character (at character 5), the : after data. To check what an unknown string contains, Base64 to image previews it in the browser.

Validation and FormatException messages

All decoding errors are FormatException. toString() includes the message, the input and a caret under the problem; e.message and e.offset give the parts separately. With base64Decode:

InputMessage
aGkInvalid length, must be multiple of four (at character 4)
aInvalid length, must be multiple of four (at character 2)
aGk=!!Invalid padding character (at character 5)
aGk==Invalid padding character (at character 5)
aGl=Invalid encoding before padding (at character 4)
"aGVs\nbG8K"Invalid character (at character 5)

Dart is strict on two points where many languages are lenient: it rejects newlines (strip them for MIME or PEM input) and rejects non-zero padding bits (aGl=). A validity check:

bool isBase64(String s) {
  try {
    base64Decode(s);
    return true;
  } on FormatException {
    return false;
  }
}

If a string fails and the message does not tell you enough, the Base64 validator marks the problem; common Base64 errors covers the causes.

Pitfalls

  • Encoding codeUnits. base64Encode('é'.codeUnits) returns 6Q== (Latin-1, not UTF-8), and '✓'.codeUnits throws ArgumentError: Invalid argument (Not a byte value at index 0: 0x2713). Always go through utf8.encode.
  • Line breaks from other systems. MIME output from email or base64 on Linux contains \n; remove whitespace with s.replaceAll(RegExp(r'\s'), '') first.
  • Assuming URL-safe output from base64Encode. It writes + and /. Use base64Url.encode for anything that goes into a URL.
  • No MIME wrapping. The codecs never insert line breaks. Split into 76-character lines yourself if needed.

FAQ

How do I show a Base64 image in Flutter?

Decode to bytes and use Image.memory(base64Decode(b64)). If the string is a data URI, strip everything up to the comma first, or use UriData.parse(uri).contentAsBytes().

Why does base64Decode say 'Invalid length, must be multiple of four'?

The input has no padding, which is typical for JWTs and URL-safe tokens. Call base64.normalize(s) or base64Url.normalize(s) first; it adds the missing =.

Does Dart's base64Decode accept URL-safe input?

Yes. The decoder accepts both +/ and -_, so base64Decode('PDw_Pz8-Pg==') works. Only the encoders differ: base64 writes +/, base64Url writes -_.

Keep reading