A free, privacy-first web tool that decodes Google Authenticator's otpauth-migration://offline?data=... export QR codes so you can migrate your 2FA secrets to any other authenticator.
Everything runs in your browser. Nothing is uploaded. No telemetry. No backend.
- Scan, upload, or paste the QR code that Google Authenticator produces when you choose Settings → Transfer accounts → Export accounts.
- The tool decodes the embedded protobuf payload and lists every account it contains.
- For each account you choose how to receive it:
- A re-scannable QR code (point your new authenticator's camera at it)
- The plain-text base32 secret and full
otpauth://URI (copy/paste) - Inclusion in a bulk JSON or CSV download
Multi-batch exports (large vaults split across several QRs) and HOTP entries are supported.
npm install
npm run devOpen the URL Vite prints (typically http://127.0.0.1:5173). Camera access requires a secure context, so use localhost / 127.0.0.1 or HTTPS.
https://free-tools.heimlane.com/ga-decoder/
npm run buildThe dist/ folder is a fully self-contained static bundle. Drop it on any web host (S3, Netlify, GitHub Pages, your own server). The bundle makes zero network calls at runtime.
- No analytics, no tracking, no third-party scripts.
- A strict Content-Security-Policy meta tag in
index.htmlblocks unexpected network access. - All processing (QR decoding, protobuf parsing, code generation) happens entirely in the browser.
- Open the browser DevTools Network tab while using the tool: you will see zero outbound requests after the initial page load.
The source code in this folder is licensed under the MIT License (see LICENSE).
The Heimlane name, the Heimlane logo, and any other Heimlane trade dress shipped in this repository are not covered by the MIT license, are not CC-licensed, and remain the property of Heimlane. Trademark and property rights apply independently of the source code license.
If you fork or redistribute this tool, you must replace the Heimlane name and logo with your own branding before publishing. The MIT license covers the code; it does not grant any right to use the Heimlane brand.
- Replace
public/heimlane-logo.svgwith your own logo (and updateLOGO_PATHinsrc/branding.tsif you rename the file). - Edit the non-translatable constants in
src/branding.ts:BRAND: your product name (interpolated into translated strings as{brand})LOGO_ALT: alt-text for the logo imageSOURCE_URL: the link behind the "View source" anchor in the privacy bannerMARKETING_URL: the homepage link for the logo / footer, and the fallback "Learn more" CTA target
- Edit the visible copy in
src/locales/en.json(and any other locales you ship):header.tagline: the sub-line under the titlead.headline,ad.body,ad.cta: the soft-sell card at the bottom__meta.marketingUrl(optional, per-locale): localized destination for the "Learn more" CTA
- Run
npm run buildand shipdist/.
The UI ships in English and French. Adding a language is a one-file pull request:
- Copy
src/locales/en.jsontosrc/locales/<code>.json, using a 2-letter ISO code (de,es,it, ...). - Translate every value. Leave the keys unchanged.
- Set the meta fields at the top of your file:
__meta.nativeName: how your language is named in itself ("Deutsch", "Español", "Italiano")__meta.flag: the ISO 3166-1 alpha-2 country code in lowercase (de,es,it, ...). The picker renders an inline SVG flag. If your code is not yet insrc/flags.ts, add a tiny SVG there (~500 bytes) in the same pull request; if you leave it out, the picker shows the country code as a text badge.__meta.marketingUrl(optional): the URL the "Learn more" ad button should point to for users on this locale. If your brand has language-specific pages, set the right one. Falls back to the globalMARKETING_URLinsrc/branding.tswhen omitted.
- Keep the
{placeholder}tokens inside the strings exactly as they appear (e.g.{count},{got},{total},{brand},{error}). They are replaced at runtime. - For plural-aware strings (suffixed
_one/_other), provide both variants. Languages that do not pluralize can put the same wording in both. - Run
npm run buildand open the result. Your locale will be auto-discovered (Vite'simport.meta.globfinds every JSON insrc/locales/). - Open a pull request.
No code edits are required. The picker, browser-language auto-detect, and fallback to English all work as soon as your JSON is in place.
Runtime dependencies are all permissive-licensed:
No protobuf runtime is bundled; the decoder for the tiny MigrationPayload schema is hand-rolled (see src/migration.ts).
- Alex Bakker: Parsing the Google Authenticator export QR code
- Protobuf schema:
MigrationPayload { repeated OtpParameters otp_parameters = 1; int32 version = 2; int32 batch_size = 3; int32 batch_index = 4; int32 batch_id = 5; }withOtpParameters { bytes secret = 1; string name = 2; string issuer = 3; Algorithm algorithm = 4; DigitCount digits = 5; OtpType type = 6; int64 counter = 7; }.