<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ville Teikko</title>
    <description>The latest articles on DEV Community by Ville Teikko (@teikko).</description>
    <link>https://dev.to/teikko</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4052894%2F238c68c4-da06-44d6-8b0e-a6678d599562.jpg</url>
      <title>DEV Community: Ville Teikko</title>
      <link>https://dev.to/teikko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/teikko"/>
    <language>en</language>
    <item>
      <title>How to read your 23andMe raw data privately — in the browser, nothing uploaded</title>
      <dc:creator>Ville Teikko</dc:creator>
      <pubDate>Wed, 29 Jul 2026 09:59:09 +0000</pubDate>
      <link>https://dev.to/teikko/how-to-read-your-23andme-raw-data-privately-in-the-browser-nothing-uploaded-nb7</link>
      <guid>https://dev.to/teikko/how-to-read-your-23andme-raw-data-privately-in-the-browser-nothing-uploaded-nb7</guid>
      <description>&lt;p&gt;If you've ever done a 23andMe, AncestryDNA or MyHeritage test, you're sitting on a plain-text file with hundreds of thousands of your own genetic markers. Most people never open it — and the tools that read it usually want you to &lt;strong&gt;upload&lt;/strong&gt; your genome to their servers. After the 23andMe bankruptcy and asset sale, a lot of us are (rightly) more careful about that.&lt;/p&gt;

&lt;p&gt;Here's the thing: you don't need to upload anything. A DNA raw-data file is just text, and modern browsers can read it &lt;strong&gt;entirely on your own device&lt;/strong&gt;. Let me show you how that works — and how to verify nothing leaves your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually in the file
&lt;/h2&gt;

&lt;p&gt;Unzip your export and you'll find a tab-separated text file that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# rsid        chromosome  position   genotype
rs4988235     2           136608646  AA
rs1801133     1           11856378   GG
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row is a SNP: an &lt;code&gt;rsid&lt;/code&gt; (the marker's ID), where it sits on the genome, and your genotype (the two alleles you carry). That's it — no magic, just a big lookup table of ~600k–700k markers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading it in the browser (no server involved)
&lt;/h2&gt;

&lt;p&gt;The browser's &lt;code&gt;FileReader&lt;/code&gt; / &lt;code&gt;Blob.text()&lt;/code&gt; API reads a local file into memory without any network request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[type=file]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// read locally — no upload&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;rsid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;genotype&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;snps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rsid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;genotype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// now look up any marker you care about — e.g. lactase persistence:&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rs4988235:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;snps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rs4988235&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;input.files[0].text()&lt;/code&gt; never touches the network. You can parse the whole file, look up any marker, and render results — all client-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to verify nothing is uploaded
&lt;/h2&gt;

&lt;p&gt;Don't take anyone's word for it (including mine). Open &lt;strong&gt;DevTools → Network tab&lt;/strong&gt;, filter by &lt;code&gt;Fetch/XHR&lt;/code&gt;, then load your file into the tool. If it's processing locally, you'll see &lt;strong&gt;zero&lt;/strong&gt; requests carrying your data. That's the entire trust model — verifiable in about ten seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can actually look up
&lt;/h2&gt;

&lt;p&gt;Once you have the &lt;code&gt;rsid → genotype&lt;/code&gt; map, you can check well-studied variants, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;rs4988235&lt;/strong&gt; (near &lt;em&gt;LCT&lt;/em&gt;) — lactase persistence: can you digest milk as an adult?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rs1801133&lt;/strong&gt; (&lt;em&gt;MTHFR&lt;/em&gt; C677T) — folate metabolism&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rs671&lt;/strong&gt; (&lt;em&gt;ALDH2&lt;/em&gt;) — the alcohol-flush variant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rs1815739&lt;/strong&gt; (&lt;em&gt;ACTN3&lt;/em&gt;) — the so-called "sprint gene"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One caution: consumer genotyping isn't clinical sequencing, and a single SNP is rarely destiny. Treat this as educational, not medical advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  A ready-made version
&lt;/h2&gt;

&lt;p&gt;If you'd rather not write the parser, I've been using &lt;a href="https://www.quanome.com/tools/dna-explorer/" rel="noopener noreferrer"&gt;Quanome's free DNA Explorer&lt;/a&gt; — it does exactly this (23andMe / AncestryDNA / MyHeritage, in-browser, nothing uploaded) and explains common trait, health and pharmacogenomic variants in plain language. No signup. Open the Network tab and you'll see it never phones home.&lt;/p&gt;

&lt;p&gt;Either way — write it yourself or use a tool — the point stands: &lt;strong&gt;your genome is a local text file, and reading it shouldn't mean handing it to another company.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>healthtech</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
