Read the failure before you edit the string
A decoder gives you one of three outcomes, and each points at a different repair. Knowing which one you have saves you from rewriting a string that was never broken.
The string holds characters outside the alphabet, or its length is not a multiple of four once padding is counted. Hyphens and underscores mean a URL-safe encoder produced it. Spaces sitting where plus signs belong mean a form or a URL decoder ate them. Both are mechanical fixes, applied here before the decode runs.
The bytes were fine and the character encoding was not. Names arriving as Zürich mean UTF-8 bytes read as Latin-1 somewhere upstream. A first header cell starting with  is a byte order mark the reader failed to skip.
Nothing failed. The source was never TSV. Commas point at CSV, braces at JSON, an angle bracket at XML or HTML. A column meter reading 1 on a table with forty rows is the tell.
What gets repaired without asking
Four fixes run before decoding, and the input reading panel names each one that fired, so you learn what the original string held:
- URL-safe characters. RFC 4648 section 5 swaps
+and/for-and_so a string survives a query parameter. JWT payloads and signed download links use that alphabet constantly. Both characters get translated back. - Missing padding. Base64 packs three bytes into four characters, and trailing
=signs mark a short final group. Plenty of encoders drop them, since most decoders work the length out anyway. The length gets rounded back up to a multiple of four. - Data URI prefixes. A string copied out of an
hrefarrives asdata:text/tab-separated-values;base64,VGl0bGUJ.... Everything up to and including that comma gets removed. - Whitespace and wrapping. MIME wraps at 76 columns and PEM at 64. Line breaks, stray spaces, and the quote marks around a value copied out of a JSON field are stripped before the alphabet check runs.
One thing stays unfixable: a truncated string. Base64 carries no length field and no checksum, so a copy that lost its last quarter decodes without complaint into a table that stops mid-row. Check the row count against what you expected rather than trusting a green status line.
Tabs are invisible, so the page draws them
The whole failure mode of TSV is a separator nobody sees. The Hidden characters view marks every one:
- Tab arrows show where columns actually split. Two arrows together are a deliberate empty cell, not missing data.
- Line break marks separate CRLF from LF. The meter names the style and reports mixed endings when a file was edited on two platforms.
- Edge spaces appear as middle dots at the start or end of a cell. Spreadsheet exports collect these routinely, and a key of
Northnever matchesNorthin a join. - Control and zero-width characters show as filled circles. Non-breaking spaces, zero-width joiners, and stray vertical tabs travel through Base64 untouched and break comparisons that look right on screen.
The ragged row meter counts rows whose column count differs from the header. Importers align by position, so a single row missing one tab shifts every later value one field to the left.
Getting the table into a spreadsheet
Copy the raw text, open a blank sheet, paste. Excel and Google Sheets both split pasted text on tabs, which is the one thing TSV does better than CSV. Sheets asks nothing at all. Excel occasionally opens the Text Import Wizard, where you pick Delimited and tick Tab.
Saving to a file needs one extra thought. Excel reads a .tsv file as the system code page unless the bytes open with a UTF-8 byte order mark, so accented names and CJK text turn into mojibake. The download button writes that marker for you. Pasting from the clipboard carries text rather than bytes, so the question never comes up there.
Numbers stored as text are the other trap. A leading zero in a product code survives the decode perfectly, then disappears when Excel reads the column as a number. Format the column as text before you paste.
Doing the same decode in code
Once this turns into a repeat job, move it into a script. Each version below matches what the page does, minus the automatic repairs:
$b64 = strtr($b64, '-_', '+/');$tsv = base64_decode($b64, true);if ($tsv === false) { throw new RuntimeException('bad base64'); }
$rows = array_map(fn($l) => explode("\t", $l), explode("\n", trim($tsv)));import base64, csv, io
raw = base64.urlsafe_b64decode(b64 + '=' * (-len(b64) % 4))rows = list(csv.reader(io.StringIO(raw.decode('utf-8')), delimiter='\t'))const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0));const tsv = new TextDecoder('utf-8', {fatal: true}).decode(bytes);echo "$B64" | base64 --decode > export.tsv
column -t -s $'\t' export.tsv | headThe JavaScript line is where people slip. atob returns a binary string whose characters are single bytes, so treating the result as text mangles anything outside ASCII. The TextDecoder step turns those bytes into real characters, and fatal: true throws on invalid UTF-8 instead of filling your table with replacement diamonds.
Base64 is an encoding, not a lock. A string sitting in a log file, a URL, or a support ticket is readable by anyone who pastes it into a decoder, this page included. Treat an encoded customer export with the same care as the plain file. If a string someone sent you turns out to hold personal data, that data was never protected in transit.
Where this page stops
Decoding, the shape check, and file reading all happen in this tab. Nothing is uploaded and nothing is stored, so closing the page discards the lot.
File input is capped at 2 MB of Base64, roughly 1.5 MB of decoded table. Bigger exports belong on the command line, where base64 --decode finishes in a moment and no textarea has to hold the result.
Only text comes out. A string holding a zip, an image, or a spreadsheet binary decodes to bytes that are not valid UTF-8, and the status says so rather than showing you garbage. Base64 to file handles those.
No delimiter guessing happens. Semicolon and pipe separated data decodes fine and reports one column, because a tab is the only separator the table view reads.
The table view renders every decoded row, which turns slow in the browser past a few thousand of them. The raw text view stays responsive at any size the file cap allows.
Nearby pages
Going the other direction, TSV to Base64 encodes a table and checks its shape first. When the decoded text turns out to be comma separated, Base64 to CSV fits better. Base64 validator checks a string before you decode it at all, and TSV to JSON turns the decoded rows into records once you have them.
