Just another native implementation of Base64 for React Native, done in C++ with a NEON fast path and reached from JS through JSI.
Drop-in replacement for btoa/atob and for JS Base64 libraries such as base64-js.
npm install react-native-fast-base64
cd ios && pod installimport {
encode,
decode,
btoa,
atob,
fromByteArray,
toByteArray,
} from 'react-native-fast-base64';
encode('Hello, world!'); // 'SGVsbG8sIHdvcmxkIQ=='
decode('SGVsbG8sIHdvcmxkIQ=='); // 'Hello, world!'
encode('foob', true); // 'Zm9vYg' (URL-safe, unpadded)
fromByteArray(new Uint8Array([1, 2, 3])); // 'AQID'
toByteArray('AQID'); // Uint8Array(3) [1, 2, 3]To shim the globals, import once at the top of your entry file:
import 'react-native-fast-base64/shim';| Function | Takes | Returns |
|---|---|---|
encode(text, urlSafe?) |
text, encoded as its UTF-8 bytes | Base64 string |
decode(base64) |
Base64 string | decoded bytes, read as UTF-8 text |
btoa(binary) |
binary string, one character per byte | Base64 string |
atob(base64) |
Base64 string | binary string, one character per byte |
fromByteArray(bytes, urlSafe?) |
Uint8Array |
Base64 string |
toByteArray(base64) |
Base64 string | Uint8Array |
isLoaded() |
whether the JSI bindings installed |
Passing urlSafe swaps +/ for -_ and drops the padding. Decoding accepts
either alphabet, padded or not, so you never have to say which you have, and
ignores ASCII whitespace, so MIME- and PEM-wrapped input decodes as it stands
instead of needing a replace first.
encode/decode are text helpers: they convert through UTF-8. btoa/atob
are binary-string helpers, one character per byte over the full 0x00-0xFF
range, exactly as the DOM defines them. Use btoa/atob (or the byte-array
functions) for anything that is not text — routing binary data through the
UTF-8 pair corrupts every byte above 0x7F.
Invalid Base64 throws instead of returning an empty string.
Run it yourself with yarn example ios or yarn example android and tap
Run benchmark. Median of three runs, debug builds, Hermes.
iPhone 17 Pro simulator:
| Payload | fast-base64 | quick-base64 | base64-js | Buffer | |
|---|---|---|---|---|---|
| 1 KiB | encode | 1788 MB/s | 614 MB/s | 11 MB/s | 11 MB/s |
| decode | 1338 MB/s | 257 MB/s | 10 MB/s | 3 MB/s | |
| 64 KiB | encode | 2541 MB/s | 845 MB/s | 10 MB/s | 10 MB/s |
| decode | 1627 MB/s | 320 MB/s | 10 MB/s | 3 MB/s | |
| 1 MiB | encode | 1465 MB/s | 745 MB/s | 11 MB/s | 11 MB/s |
| decode | 2939 MB/s | 327 MB/s | 10 MB/s | 3 MB/s |
At 16 bytes every option collapses to roughly 30 MB/s, because the per-call cost across the JS boundary dominates the codec itself.
Where the remaining speed comes from:
- NEON on arm64 -- 48 bytes per encode iteration, 64 characters per decode iteration. Other ABIs use the scalar path, which writes through a raw pointer rather than pushing character by character.
- One decode table for both alphabets, storing
value + 1so invalid input is a zero test rather than a branch per character. toByteArrayhands its buffer to JS without a second copy, andfromByteArraypasses a view's offset and length across instead of slicing a copy in JS first.
React Native 0.71 and newer, old architecture and new, bridgeless included. There is one code path: a small native module installs the JSI bindings, and every function is a host function on the JS runtime, so no call crosses the bridge on any architecture.
yarn test # C++ codec self-check, then the JS tests
yarn test:cpp # codec only, runs the SIMD and scalar pathsSee the contributing guide.
MIT