<?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: Peter</title>
    <description>The latest articles on DEV Community by Peter (@redpoint9).</description>
    <link>https://dev.to/redpoint9</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%2F4087005%2Fd8cee0b5-1d66-4650-859c-0ee63f12e0ed.png</url>
      <title>DEV Community: Peter</title>
      <link>https://dev.to/redpoint9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/redpoint9"/>
    <language>en</language>
    <item>
      <title>The barcode prefix does not tell you where a product was made</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Thu, 20 Aug 2026 16:52:54 +0000</pubDate>
      <link>https://dev.to/redpoint9/the-barcode-prefix-does-not-tell-you-where-a-product-was-made-3ing</link>
      <guid>https://dev.to/redpoint9/the-barcode-prefix-does-not-tell-you-where-a-product-was-made-3ing</guid>
      <description>&lt;p&gt;There is a claim about barcodes that circulates every few years, usually attached to a call to boycott something: the first digits tell you which country a product was made in. Scan a jar, read the prefix, learn the origin.&lt;/p&gt;

&lt;p&gt;It is wrong, and the organisation that issues the numbers says so directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since GS1 user companies can manufacture products anywhere in the world, GS1&lt;br&gt;
Prefixes do not identify the country of origin for a given product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What the prefix actually identifies is &lt;strong&gt;the GS1 member organisation that allocated the number to the brand owner&lt;/strong&gt;. A company registers with one organisation, usually the one where it has its seat, and keeps those number ranges wherever it later manufactures. A Swedish company with a &lt;code&gt;73x&lt;/code&gt; prefix can have every unit made in Vietnam and the barcode will not change.&lt;/p&gt;

&lt;p&gt;I went looking for a dataset that got this right and did not find one, so I built it. It sits behind &lt;a href="https://smartatest.se/guider/ean-landsprefix" rel="noopener noreferrer"&gt;a Swedish-language lookup tool&lt;/a&gt; where you can paste a barcode and see the same answer without installing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What annoyed me enough to build something
&lt;/h2&gt;

&lt;p&gt;Most lookup services label the field &lt;code&gt;country of origin&lt;/code&gt;. Not &lt;code&gt;issuing organisation&lt;/code&gt;, not &lt;code&gt;country of the issuing organisation&lt;/code&gt;. The label itself teaches the myth to everyone who uses the tool.&lt;/p&gt;

&lt;p&gt;The npm package with the most obvious name is &lt;code&gt;gs1-prefix-code-to-country-code&lt;/code&gt;. Last published in 2023, no TypeScript types, no keywords, and the entire API is named after the wrong idea. Install it and the mistake propagates into your codebase as a function name.&lt;/p&gt;

&lt;p&gt;That felt like a small thing worth fixing properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two sources, and what happens when they disagree
&lt;/h2&gt;

&lt;p&gt;The table is reconciled against GS1's own prefix list and the English Wikipedia article &lt;code&gt;List of GS1 country codes&lt;/code&gt;. Most entries agree. Two do not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;612&lt;/code&gt; appears in Wikipedia as Somalia, but is absent from GS1's own list&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;894&lt;/code&gt; is listed as Bangladesh by Wikipedia, while GS1 says the range is
managed centrally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The obvious move is to pick one and move on. I kept both disagreements in the record instead, as a boolean field:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GS1_RANGES&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gs1-prefix&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;GS1_RANGES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceDisagreement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// the two ranges where the sources conflict&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part I actually care about. A dataset that silently resolves conflicts looks cleaner and tells you less. If you are building something where an unallocated prefix matters, you want to know that one of your 144 ranges rests on a single source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;gs1-prefix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gs1-prefix&lt;/span&gt;&lt;span class="dl"&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7310865004703&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                        &lt;span class="c1"&gt;// "731"&lt;/span&gt;
&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issuingOrganisation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// "GS1 Sweden"&lt;/span&gt;
&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organisationCountry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// "Sweden"&lt;/span&gt;
&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organisationCountryCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "SE"&lt;/span&gt;
&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkDigitValid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python, same data, same names in snake_case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;gs1-prefix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;gs1_prefix&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;7310865004703&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issuing_organisation&lt;/span&gt;   &lt;span class="c1"&gt;# "GS1 Sweden"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or skip the dependency and take the CSV, JSON or DCAT-AP file from &lt;a href="https://gs1-prefix-ranges.vercel.app" rel="noopener noreferrer"&gt;gs1-prefix-ranges.vercel.app&lt;/a&gt;. CORS is open, so you can fetch it straight from a page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that trips people up
&lt;/h2&gt;

&lt;p&gt;A 12-digit UPC-A is a GTIN-13 with an implied leading zero. Read the twelve digits as they are and you land on the wrong prefix, and the answer still looks plausible, which is the worst kind of bug.&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="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;012345678905&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;paddedFromUpc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;012345678905&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// "001", not "012"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flag is there so you can tell the two cases apart rather than wondering.&lt;/p&gt;

&lt;p&gt;The check digit follows the same principle. It returns &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, or &lt;code&gt;null&lt;/code&gt; when the length carries no check digit, so "there is no check digit here" and "the check digit is wrong" are different answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ranges that are not countries at all
&lt;/h2&gt;

&lt;p&gt;Worth knowing before you write &lt;code&gt;range.organisationCountry&lt;/code&gt; into a UI:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;020&lt;/code&gt;-&lt;code&gt;029&lt;/code&gt;, &lt;code&gt;200&lt;/code&gt;-&lt;code&gt;299&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Restricted distribution, defined locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;040&lt;/code&gt;-&lt;code&gt;049&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Restricted to within one company&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;977&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Periodicals, ISSN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;978&lt;/code&gt;-&lt;code&gt;979&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Books, ISBN. &lt;code&gt;979-0&lt;/code&gt; is sheet music, ISMN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;980&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Refund receipts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;981&lt;/code&gt;-&lt;code&gt;983&lt;/code&gt;, &lt;code&gt;990&lt;/code&gt;-&lt;code&gt;999&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Coupons&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those carry &lt;code&gt;issuingOrganisation: null&lt;/code&gt; and a &lt;code&gt;note&lt;/code&gt; explaining what the range is for. Around 281 of the 1000 possible prefixes are unallocated, and the lookup returns a null range rather than guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  One caution if you maintain your own copy
&lt;/h2&gt;

&lt;p&gt;Several GS1 member organisations publish stale versions of the list. GS1 Slovakia's still names Serbia and Montenegro, a country that stopped existing in&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A stale list looks exactly like a current one, so check the date on
whatever you copy from.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That applies to this dataset too. It is generated from a table I maintain, and the generation date is in the JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Licence and where it lives
&lt;/h2&gt;

&lt;p&gt;MIT for the code, CC BY 4.0 for the data. Attribution goes to &lt;a href="https://smartatest.se/guider/ean-landsprefix" rel="noopener noreferrer"&gt;smartatest.se&lt;/a&gt;, where the table is maintained.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Site and raw files: &lt;a href="https://gs1-prefix-ranges.vercel.app" rel="noopener noreferrer"&gt;gs1-prefix-ranges.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/xoocode/gs1-prefix-ranges" rel="noopener noreferrer"&gt;github.com/xoocode/gs1-prefix-ranges&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm and PyPI: &lt;code&gt;gs1-prefix&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Corrections to the data are welcome as an issue. If you find a range where GS1 and Wikipedia disagree and I have not flagged it, that is the most useful thing you could tell me.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
  </channel>
</rss>
