<?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: 王磊</title>
    <description>The latest articles on DEV Community by 王磊 (@_172c9b2760d6c5bc1e8966).</description>
    <link>https://dev.to/_172c9b2760d6c5bc1e8966</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%2F3964002%2Fd67ecd17-1334-49df-abff-4386876a8a76.png</url>
      <title>DEV Community: 王磊</title>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_172c9b2760d6c5bc1e8966"/>
    <language>en</language>
    <item>
      <title>Parsing multi-gigabyte Outlook PST files in the browser</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Fri, 14 Aug 2026 01:30:36 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/parsing-multi-gigabyte-outlook-pst-files-in-the-browser-16oe</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/parsing-multi-gigabyte-outlook-pst-files-in-the-browser-16oe</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure up front: I build &lt;a href="https://pst.aivismonitor.com" rel="noopener noreferrer"&gt;Mailward&lt;/a&gt;, a browser-based PST/OST/OLM/MBOX/EML viewer. These are the implementation notes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A PST file is not a folder of emails. It is a single-file B-tree database — Microsoft documents the layout as [MS-PST] — and the mail inside exists only as sets of typed properties. To show one message you walk the B-trees, resolve the property set, and reconstruct an RFC 5322 message from scratch.&lt;/p&gt;

&lt;p&gt;Now do that in a browser tab, for a 4 GB file, without uploading it anywhere and without loading it into memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that shapes everything
&lt;/h2&gt;

&lt;p&gt;The whole point of a browser-side viewer is privacy: the file never leaves the machine. That rules out "stream it to a server". And &lt;code&gt;FileReader.readAsArrayBuffer&lt;/code&gt; on the whole file rules itself out the moment archives pass a gigabyte — mobile Safari will simply kill the tab.&lt;/p&gt;

&lt;p&gt;So the parser must do &lt;strong&gt;random access&lt;/strong&gt; over a &lt;code&gt;File&lt;/code&gt; it never fully reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  One method to intercept
&lt;/h2&gt;

&lt;p&gt;We build on &lt;a href="https://github.com/epfromer/pst-extractor" rel="noopener noreferrer"&gt;pst-extractor&lt;/a&gt;, a faithful JS port of java-libpst. It has one architectural gift: every byte it reads funnels through a single method, &lt;code&gt;PSTFile.readSync(buffer, length, position)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means you can subclass &lt;code&gt;PSTFile&lt;/code&gt; and reroute all I/O without touching parser internals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;PSTFile&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;pst-extractor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pendingReader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RandomAccessReader&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReaderBackedPSTFile&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;PSTFile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;archiveReader&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RandomAccessReader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RandomAccessReader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pendingReader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// super() reads the 514-byte header via readSync&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alloc&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;pendingReader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveReader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ReaderBackedPSTFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;readSync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archiveReader&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;pendingReader&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;position&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;Two quirks worth explaining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The override lives &lt;strong&gt;on the prototype, outside the class body&lt;/strong&gt;, because &lt;code&gt;PSTFile&lt;/code&gt;'s constructor immediately reads the 514-byte header — so the replacement must already be installed while &lt;code&gt;super()&lt;/code&gt; runs.&lt;/li&gt;
&lt;li&gt;Instance fields don't exist yet at that point, hence the module-scoped &lt;code&gt;pendingReader&lt;/code&gt; handoff. Workers are single-threaded, so this is safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The reader: 64 KB pages, LRU-capped
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;RandomAccessReader&lt;/code&gt; is a paged view of the file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;64 KB pages&lt;/strong&gt;, loaded on demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LRU cache capped at 1024 pages&lt;/strong&gt; — 64 MB worst case, regardless of file size.&lt;/li&gt;
&lt;li&gt;Reads that span page boundaries are assembled from multiple pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside a Web Worker the page loader is almost embarrassingly small, because &lt;code&gt;FileReaderSync&lt;/code&gt; gives you synchronous random access to a user-selected &lt;code&gt;File&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileSlicePageLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;reader&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;FileReaderSync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;loadPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&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;file.slice()&lt;/code&gt; doesn't copy — it's a view. We measured a 4 KB slice read at &lt;strong&gt;~200 µs in Chrome&lt;/strong&gt;, which makes B-tree walks feel instant. The same &lt;code&gt;RandomAccessReader&lt;/code&gt; interface is backed by a plain &lt;code&gt;Buffer&lt;/code&gt; in Node, so the entire parser is testable outside the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're in Node, you don't need any of this
&lt;/h2&gt;

&lt;p&gt;pst-extractor's constructor accepts &lt;strong&gt;either a Buffer or a filename&lt;/strong&gt;. The 2 GB failures people report almost always come from &lt;code&gt;fs.readFileSync&lt;/code&gt; → Buffer. Pass the path instead and the library reads through a file descriptor with no Buffer ceiling:&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;pst&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;PSTFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/path/to/large.pst&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// descriptor-backed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this doesn't solve
&lt;/h2&gt;

&lt;p&gt;Honesty section: none of this repairs a corrupted PST — if &lt;code&gt;scanpst&lt;/code&gt; can't open it, a reader can't either. And ANSI-era PSTs (Outlook 97–2002, the 2 GB-capped variant) parse through the same pipeline in theory, but genuine ANSI files written by 20-year-old Outlook are nearly impossible to test against, so we claim "likely works, fails loudly" rather than "supported".&lt;/p&gt;

&lt;p&gt;More format field notes (OST's account lock, OLM's timezone-less UTC timestamps, MBOX From-munging) live in this repo: &lt;a href="https://github.com/xsxs89757/email-archive-formats" rel="noopener noreferrer"&gt;email-archive-formats&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>email</category>
      <category>performance</category>
    </item>
    <item>
      <title>Shopify Markets CSV Price Updates: A Safer Way to Verify Catalog Imports</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Wed, 05 Aug 2026 01:40:32 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-markets-csv-price-updates-a-safer-way-to-verify-catalog-imports-5fkj</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-markets-csv-price-updates-a-safer-way-to-verify-catalog-imports-5fkj</guid>
      <description>&lt;p&gt;A Shopify CSV import can finish without an obvious error and still leave a market catalog with the wrong price or fewer variants than the base catalog.&lt;/p&gt;

&lt;p&gt;This is especially easy to miss after the change from Markets to Catalogs. A product import CSV and a catalog-specific price CSV may look similar, but they are not interchangeable workflows. The safest approach is to treat a market-price update as a small data-reconciliation job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a successful import is not proof
&lt;/h2&gt;

&lt;p&gt;There are three separate questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Did Shopify parse the CSV?&lt;/li&gt;
&lt;li&gt;Did the intended Handle/SKU rows match the right variants?&lt;/li&gt;
&lt;li&gt;Did the target catalog apply the expected price and inclusion state?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first answer can be "yes" while the second or third is "no." A catalog can fall back to a base price, omit a variant, or keep an old inclusion value without producing the kind of row-level error merchants expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safer workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Save a baseline
&lt;/h3&gt;

&lt;p&gt;Export the current product state and record the target market/catalog, export date, currency, and number of products and variants. Keep this as a rollback reference. Do not start by editing the only copy of the export.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use the target catalog's own template
&lt;/h3&gt;

&lt;p&gt;Open the specific catalog you intend to update and download its sample or export format when Shopify provides one. Do not assume that columns such as &lt;code&gt;Price / US&lt;/code&gt;, &lt;code&gt;Price / UK&lt;/code&gt;, or &lt;code&gt;Included / US&lt;/code&gt; belong in the ordinary product import flow.&lt;/p&gt;

&lt;p&gt;For a catalog-specific file, keep the smallest set of columns that the template documents. In many workflows that means an identity pair plus the catalog fields, for example:&lt;/p&gt;

&lt;p&gt;Example columns: Handle, SKU, Price, Included.&lt;/p&gt;

&lt;p&gt;Example row: blue-shirt, BLUE-S, 29.90, true.&lt;/p&gt;

&lt;p&gt;Use your actual exported headers and identifiers; the example is only a shape, not a universal Shopify template.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Normalize numbers before checking catalog behavior
&lt;/h3&gt;

&lt;p&gt;Prices should be numeric and consistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a decimal point, not a locale comma, unless the target template explicitly says otherwise.&lt;/li&gt;
&lt;li&gt;Remove currency symbols and thousands separators.&lt;/li&gt;
&lt;li&gt;Keep one currency per file and confirm the catalog currency before importing.&lt;/li&gt;
&lt;li&gt;Check for formulas that exported as blank strings or text.&lt;/li&gt;
&lt;li&gt;Make sure the same SKU is not repeated for two different variant rows unless that is intentional.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A browser-side &lt;a href="https://shopify-csv.aivismonitor.com/shopify-csv-price-format-fixer" rel="noopener noreferrer"&gt;numeric price-format check&lt;/a&gt; can catch malformed price cells before they create noise in the catalog test. It cannot prove that Shopify mapped a row to the correct market catalog, so treat it as the formatting step only.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Compare identities and counts
&lt;/h3&gt;

&lt;p&gt;Before importing, compare the Handles and SKUs in the proposed file with the baseline export. Record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;total rows in the proposed file;&lt;/li&gt;
&lt;li&gt;unique Handle + SKU pairs;&lt;/li&gt;
&lt;li&gt;missing identifiers;&lt;/li&gt;
&lt;li&gt;duplicate identity pairs;&lt;/li&gt;
&lt;li&gt;products with fewer or more variant rows than expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This catches a common false positive: the file is valid, but the catalog receives only part of the intended variant set.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Test a small, multi-variant sample
&lt;/h3&gt;

&lt;p&gt;Choose a few products that cover the risky cases: one single-variant product, one product with several options, one sale-price change, and one product that should be excluded from the target catalog.&lt;/p&gt;

&lt;p&gt;After the import, check the catalog itself. Do not rely only on the import toast or the product page's base price.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Verify the result in the target catalog
&lt;/h3&gt;

&lt;p&gt;For each sample, confirm the market price, currency display, included/excluded state, and variant count. Then compare the post-import export with the baseline. If one market shows fewer variants or the base price appears in place of the market price, stop the full rollout and isolate that catalog's template and identity rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Keep a rollback path
&lt;/h3&gt;

&lt;p&gt;Store the original export, the exact file you imported, a short change summary, and the post-import verification result. For a large catalog, keep an exception sheet rather than repeatedly re-importing the whole file while guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical boundary
&lt;/h2&gt;

&lt;p&gt;A general CSV validator can help with headers, encoding, row identity, file size, and numeric price formatting. It cannot read Shopify's private catalog state or guarantee that an import changed every market correctly.&lt;/p&gt;

&lt;p&gt;That boundary matters: "CSV accepted" is an input check; "catalog prices and variant coverage match the plan" is a post-import reconciliation check. Keeping those two claims separate makes international price updates much safer.&lt;/p&gt;

</description>
      <category>shopify</category>
    </item>
    <item>
      <title>Bulk Update Shopify Product Descriptions and Images Without Blank Overwrites</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Mon, 03 Aug 2026 01:39:47 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/bulk-update-shopify-product-descriptions-and-images-without-blank-overwrites-mfc</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/bulk-update-shopify-product-descriptions-and-images-without-blank-overwrites-mfc</guid>
      <description>&lt;p&gt;Bulk catalog edits are easy to start and surprisingly easy to make dangerous.&lt;/p&gt;

&lt;p&gt;If you need to update hundreds of Shopify product descriptions and replace images, the safest workflow is usually a small, intentional CSV update—not a full export with every column left in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with a fresh Shopify export
&lt;/h2&gt;

&lt;p&gt;Export the products immediately before preparing the change. Keep that original file untouched as your rollback reference. Do not build a new file from memory: the export contains the exact Handles, option rows, and variant structure Shopify currently knows.&lt;/p&gt;

&lt;p&gt;For a description-only change, identify the columns you actually need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Handle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Title&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Body (HTML)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are changing prices, SKUs, inventory, or variant options at the same time, treat that as a separate change set. Mixing unrelated edits makes it much harder to explain a partial import or recover from an overwrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Generate the repeated description in a spreadsheet
&lt;/h2&gt;

&lt;p&gt;A spreadsheet formula is enough when the template is stable. For example, if the product title is in column B, a Google Sheets formula can build a simple HTML description:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;="&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;"&amp;amp;B2&amp;amp;"&amp;lt;/strong&amp;gt; is handcrafted and ships free in 2 days. 30-day returns on every "&amp;amp;B2&amp;amp;".&amp;lt;/p&amp;gt;"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Adjust the wording for your store, fill the formula down, then copy the result and paste it back as values into &lt;code&gt;Body (HTML)&lt;/code&gt;. Values-only paste prevents a later formula change from silently changing the import file.&lt;/p&gt;

&lt;p&gt;Keep the original &lt;code&gt;Title&lt;/code&gt; column while you work. It is useful for reviewing the generated text, even though the description is the field you intend to update.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Avoid the blank-column trap
&lt;/h2&gt;

&lt;p&gt;For an existing-product update, an omitted column and an included blank column do not mean the same thing. A blank column can clear existing data, while an omitted column leaves that field alone.&lt;/p&gt;

&lt;p&gt;Before importing, remove every column you are not deliberately updating. Preserve all variant rows for each Handle if your file still contains variant-level fields. Never sort rows in a way that breaks the product's Handle grouping, and never remove option columns from a variant update without first checking how Shopify will match the variants.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Treat images as a separate decision
&lt;/h2&gt;

&lt;p&gt;A Shopify product CSV carries image references, not files sitting on your laptop. If new images are already hosted, use direct public &lt;code&gt;https://&lt;/code&gt; URLs in &lt;code&gt;Image Src&lt;/code&gt; and keep the Handle and image position mapping consistent. If the files are local, upload them to a location that produces stable public URLs before importing.&lt;/p&gt;

&lt;p&gt;Do not mix a large image replacement with a description update unless you have a clear reason. A description-only pilot is easier to verify, and an image-only pilot makes URL or row-mapping failures much easier to isolate.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Pilot before the full catalog
&lt;/h2&gt;

&lt;p&gt;Import one or two representative products first. Confirm the description, variant count, image order, and unchanged fields in Shopify admin. Keep the pre-import export and the exact CSV you uploaded.&lt;/p&gt;

&lt;p&gt;After the pilot, export the affected products again and compare the before/after values. Only then expand to the rest of the catalog. If the catalog is large, split the work into batches that each have a clear product range and a simple rollback point.&lt;/p&gt;

&lt;p&gt;For a faster preflight of identity matching, changed columns, blank overwrites, and update-only risk, I use this browser-side &lt;a href="https://shopify-csv.aivismonitor.com/shopify-csv-existing-product-update-audit" rel="noopener noreferrer"&gt;Shopify existing-product CSV update audit&lt;/a&gt;. It does not import into Shopify or replace the pilot; it helps make the file's intended change set visible before you upload it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Safer Way to Bulk-Update Shopify Product Descriptions by CSV</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Sat, 01 Aug 2026 01:39:09 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/a-safer-way-to-bulk-update-shopify-product-descriptions-by-csv-13mg</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/a-safer-way-to-bulk-update-shopify-product-descriptions-by-csv-13mg</guid>
      <description>&lt;p&gt;Bulk-editing 200 Shopify product descriptions sounds like a spreadsheet task. It is, but the dangerous part is usually not the formula. It is sending a full export back to Shopify with columns you did not intend to change.&lt;/p&gt;

&lt;p&gt;A safer workflow separates content generation, import scope, and post-import verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with a dated baseline
&lt;/h2&gt;

&lt;p&gt;Export the products before making edits and keep that file unchanged. Treat it as the rollback reference, not as the file you will immediately import.&lt;/p&gt;

&lt;p&gt;Record which products are in scope and keep the export date in the filename. If an app or another team member changes the catalog while you work, the baseline can become stale, so re-export before a large production update when possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Generate descriptions in a separate column
&lt;/h2&gt;

&lt;p&gt;In Excel or Google Sheets, build the new Body (HTML) value from the existing Title column and your approved template. Review a few short, long, and unusual titles manually before filling the formula down.&lt;/p&gt;

&lt;p&gt;Keep the original description in a separate working column until the new text has been reviewed. Do not paste spreadsheet formulas into Shopify; paste values into Body (HTML) after the formula is checked.&lt;/p&gt;

&lt;p&gt;This is also the point to decide whether HTML is really needed. Unescaped quotes, copied rich text, and spreadsheet formatting can create a description that looks acceptable in the sheet but behaves differently after import.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Trim the import file
&lt;/h2&gt;

&lt;p&gt;For a description-only update, the file should normally contain only the stable identifier and the fields you intentionally changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle&lt;/li&gt;
&lt;li&gt;Title, if you are using it for review&lt;/li&gt;
&lt;li&gt;Body (HTML)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An included blank cell can clear an existing value. A column that is absent is different: Shopify cannot update what you did not include. Remove unused price, inventory, option, image, and metafield columns instead of leaving them blank.&lt;/p&gt;

&lt;p&gt;If you are changing variants, keep the relevant option and identity columns together and test one multi-variant product first. A description update should not accidentally become a variant update.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Treat images as a separate job
&lt;/h2&gt;

&lt;p&gt;A product CSV carries image URLs; it does not upload image files from your computer. For new images, prepare stable public URLs first, then review Image Src, Image Position, and the repeated Handle rows together.&lt;/p&gt;

&lt;p&gt;Check that each URL is reachable and that the image order is intentional. Do not assume a reachable URL proves that Shopify will associate every Variant Image reference correctly. If the images are only local files, upload them through an appropriate Shopify workflow before preparing the CSV.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Run a preflight before production
&lt;/h2&gt;

&lt;p&gt;Use a small representative batch first, then compare the exported result with the baseline. Look specifically for changed Handles, missing variant rows, unexpected blank cells, and fields that changed outside the requested description scope.&lt;/p&gt;

&lt;p&gt;A browser-side checker such as the Shopify CSV Existing Product Update Audit can flag missing identifiers and risky blank cells, then produce a smaller update file for manual review: &lt;a href="https://shopify-csv.aivismonitor.com/shopify-csv-existing-product-update-audit" rel="noopener noreferrer"&gt;https://shopify-csv.aivismonitor.com/shopify-csv-existing-product-update-audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The checker does not write to Shopify or guarantee a rollback. That boundary is useful: the merchant still decides which rows to import and verifies the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Verify after import
&lt;/h2&gt;

&lt;p&gt;Open a few updated products and confirm the rendered description, HTML structure, image count, image order, options, and variant identities. Compare a fresh export with the pre-import baseline. If anything outside the intended fields changed, stop the next batch and investigate before continuing.&lt;/p&gt;

&lt;p&gt;The repeatable rule is simple: generate in a working sheet, import the smallest intentional file, and verify the exported result before scaling up.&lt;/p&gt;

</description>
      <category>shopify</category>
    </item>
    <item>
      <title>Shopify CSV Cost per Item: Audit It Before You Create a Purchase Order</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Thu, 30 Jul 2026 01:41:57 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-csv-cost-per-item-audit-it-before-you-create-a-purchase-order-3pf7</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-csv-cost-per-item-audit-it-before-you-create-a-purchase-order-3pf7</guid>
      <description>&lt;p&gt;A native Shopify Purchase Order can leave line-item costs blank even when a variant already has a Cost per item value. That is a workflow gap, not necessarily a bad CSV.&lt;/p&gt;

&lt;p&gt;A CSV can still help you prepare the numbers safely, but it cannot reach into a native Purchase Order and fill its lines. Treat the CSV step as a controlled cost review before you create or receive the PO.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with a fresh product export
&lt;/h2&gt;

&lt;p&gt;Keep the original export unchanged. Work from a copy that includes the fields you need to identify each variant and compare the economics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle&lt;/li&gt;
&lt;li&gt;Title&lt;/li&gt;
&lt;li&gt;Variant SKU&lt;/li&gt;
&lt;li&gt;Variant Price&lt;/li&gt;
&lt;li&gt;Cost per item&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your catalog uses barcodes or another stable identity, keep that reference too. Do not delete the baseline: it is the evidence you need if a later import changes more rows than intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Check identity before checking the numbers
&lt;/h2&gt;

&lt;p&gt;A cost value is only useful when it is attached to the right variant. Before reviewing costs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look for blank or repeated identifiers.&lt;/li&gt;
&lt;li&gt;Confirm that the SKU or other identity maps to one intended variant.&lt;/li&gt;
&lt;li&gt;Keep option values when several variants share a product Handle.&lt;/li&gt;
&lt;li&gt;Separate a supplier SKU from Shopify's own Variant SKU instead of silently treating them as interchangeable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This prevents a clean-looking cost column from being applied to the wrong size, color, or pack.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Review Cost per item beside Variant Price
&lt;/h2&gt;

&lt;p&gt;For each variant, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost per item is present and numeric.&lt;/li&gt;
&lt;li&gt;Variant Price is present and numeric.&lt;/li&gt;
&lt;li&gt;Cost is lower than price when a sale is expected to be profitable.&lt;/li&gt;
&lt;li&gt;A blank, zero, or unusually large value is reviewed instead of being accepted automatically.&lt;/li&gt;
&lt;li&gt;A supplier cost increase does not quietly turn a previously healthy margin into a loss.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://shopify-csv.aivismonitor.com/shopify-product-csv-margin-audit" rel="noopener noreferrer"&gt;Shopify product CSV margin audit&lt;/a&gt; can calculate the margin amount and percentage locally from those columns and surface missing cost, invalid price, and low-margin rows for review.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Separate standing cost from the PO cost
&lt;/h2&gt;

&lt;p&gt;Cost per item is a product-level reference. A Purchase Order is a specific supplier agreement at a specific time. If two suppliers quote different prices, keep the agreed PO cost in your purchasing worksheet and decide deliberately whether the standing product cost should change.&lt;/p&gt;

&lt;p&gt;A practical worksheet can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supplier and supplier SKU&lt;/li&gt;
&lt;li&gt;Shopify Variant SKU&lt;/li&gt;
&lt;li&gt;Ordered quantity and location&lt;/li&gt;
&lt;li&gt;Agreed unit cost and currency&lt;/li&gt;
&lt;li&gt;Freight, duty, or other adjustments&lt;/li&gt;
&lt;li&gt;The date and reason for any standing-cost change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This preserves the historical price you actually agreed to instead of rewriting it later when a supplier sends a new list.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Test the smallest safe change
&lt;/h2&gt;

&lt;p&gt;If you need to update product costs through Shopify's CSV workflow, use one representative product or a very small variant batch first. Compare the result with the original export, then expand only after the identifiers and cost values are confirmed.&lt;/p&gt;

&lt;p&gt;Do not assume that a successful product CSV import means the native Purchase Order was populated. Verify the PO lines separately in Shopify and record any manual cost entry that remains.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this workflow does not do
&lt;/h2&gt;

&lt;p&gt;A browser-side CSV review cannot access a merchant's admin, read native Purchase Orders, call the Admin API, or write costs back automatically. It prepares evidence and a safer review path; the final Shopify import and PO entry remain manual actions.&lt;/p&gt;

&lt;p&gt;That boundary is useful: it keeps supplier and store data local, makes the cost decision explicit, and avoids presenting a worksheet as an integration that Shopify does not expose.&lt;/p&gt;

</description>
      <category>inventory</category>
    </item>
    <item>
      <title>Shopify CSV Files Over 15MB: Split by Product Handle, Not Row Count</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Tue, 28 Jul 2026 01:40:08 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-csv-files-over-15mb-split-by-product-handle-not-row-count-17b3</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-csv-files-over-15mb-split-by-product-handle-not-row-count-17b3</guid>
      <description>&lt;p&gt;When a Shopify product CSV grows past 15 MB, making it smaller is only half the job. The split must keep each product together, or Shopify may receive variants and image rows without the parent context they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safer batching checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Keep the untouched Shopify export as your rollback reference. Make a working copy before removing or reordering anything.&lt;/li&gt;
&lt;li&gt;Group rows by the exact &lt;code&gt;Handle&lt;/code&gt;. Do not split after an arbitrary line number: one product can occupy several rows.&lt;/li&gt;
&lt;li&gt;Keep every variant row for a Handle in the same batch. Check Option values, Variant SKU, price, inventory, and barcode together.&lt;/li&gt;
&lt;li&gt;Keep the product's image rows with the same Handle. Moving an Image Src row into another file can attach media to the wrong product or make the import harder to audit.&lt;/li&gt;
&lt;li&gt;Check each batch independently for headers, UTF-8 text, prices, and duplicate Handles before importing.&lt;/li&gt;
&lt;li&gt;Import a small representative batch first. Confirm product count, variant count, prices, inventory, and images before continuing with the remaining files.&lt;/li&gt;
&lt;li&gt;Keep a simple batch log: file name, Handle range, row count, import time, and any Shopify warning. This makes a partial retry much safer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 15 MB limit is a file-size boundary, not a promise that a large catalog will import cleanly. Long descriptions and image URLs can push a file over the limit, while a row-count split can quietly break variant groups. A handle-aware split plus a post-import spot check gives you a better audit trail.&lt;/p&gt;

&lt;p&gt;For a browser-side check and handle-aware batching guide, see &lt;a href="https://shopify-csv.aivismonitor.com/split-shopify-csv-under-15mb" rel="noopener noreferrer"&gt;the Shopify CSV size checker&lt;/a&gt;. It prepares review guidance locally; it does not upload files to Shopify or change products in your admin.&lt;/p&gt;

</description>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Shopify CSV Prices That Look Right but Still Fail: A Pre-Import Checklist</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Sat, 25 Jul 2026 01:43:09 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-csv-prices-that-look-right-but-still-fail-a-pre-import-checklist-249f</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-csv-prices-that-look-right-but-still-fail-a-pre-import-checklist-249f</guid>
      <description>&lt;p&gt;A Shopify CSV can look perfectly normal in a spreadsheet and still fail, skip values, or change the wrong variants after import. Price columns are a common reason: spreadsheet locale settings, currency symbols, thousands separators, and variant-row matching can all change what Shopify receives.&lt;/p&gt;

&lt;p&gt;Here is a practical pre-import checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start from a Shopify export
&lt;/h2&gt;

&lt;p&gt;Use a recent product export as your baseline. Keep the original file untouched, then make a working copy for edits. Confirm that the file uses Shopify's expected headers, especially &lt;code&gt;Handle&lt;/code&gt;, &lt;code&gt;Option1 Value&lt;/code&gt;, &lt;code&gt;Variant SKU&lt;/code&gt;, &lt;code&gt;Variant Price&lt;/code&gt;, and, when relevant, &lt;code&gt;Variant Compare At Price&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do not rebuild a large catalog from a blank spreadsheet unless you have a strong reason. The export preserves the row structure that Shopify uses to group products and variants.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Make the price format unambiguous
&lt;/h2&gt;

&lt;p&gt;Before saving the CSV, check that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;prices use a decimal point, not a locale-specific decimal comma;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;currency symbols are not embedded in the cell value;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;thousands separators are not left in values such as &lt;code&gt;1,299.00&lt;/code&gt; unless your cleaning step removes them safely;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;empty cells are intentional, especially on continuation rows;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;compare-at prices are reviewed together with the selling price.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the spreadsheet displays a value as currency, inspect the raw cell value before exporting. A display format can hide characters that end up in the CSV.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Verify variant identity before changing prices
&lt;/h2&gt;

&lt;p&gt;A correct price on the wrong variant is still a bad import. For each product, check that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;every variant row keeps the same &lt;code&gt;Handle&lt;/code&gt;;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;option names and values stay paired;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SKU or another stable identifier matches the baseline export;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the update file does not accidentally include blank columns that could overwrite existing data;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the number of variant rows is unchanged unless you intentionally add or remove variants.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a large catalog, test one representative product with multiple options before preparing the full file.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Scan the working copy
&lt;/h2&gt;

&lt;p&gt;Run the working CSV through a local checker and review critical findings before exporting a cleaned copy. Pay special attention to malformed prices, unexpected headers, encoding warnings, duplicate handles, and file-size warnings. Treat the report as a preflight review; it cannot know whether a particular sale price is commercially correct.&lt;/p&gt;

&lt;p&gt;For a quick browser-side check, use the &lt;a href="https://shopify-csv.aivismonitor.com/shopify-csv-price-format-fixer" rel="noopener noreferrer"&gt;Shopify CSV price format fixer&lt;/a&gt; to review price cells and export a cleaned copy when the correction is unambiguous.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Import a tiny batch and compare
&lt;/h2&gt;

&lt;p&gt;Import a small set of products first. After Shopify finishes, compare the result against the baseline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;selling price and compare-at price;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;variant SKU and option values;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;inventory policy and availability;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product count and handle count.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the result differs from the plan, stop before uploading the full catalog. Keep the original export and the tested CSV so you can explain exactly what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final caution
&lt;/h2&gt;

&lt;p&gt;A CSV preflight does not replace Shopify's importer, change market-specific pricing, or guarantee that a theme or app will display prices as expected. It helps narrow down file-level risks so the next import is easier to verify.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From Supplier PDF to Shopify CSV: A Safer Staging Checklist</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Thu, 23 Jul 2026 01:39:55 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/from-supplier-pdf-to-shopify-csv-a-safer-staging-checklist-17ha</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/from-supplier-pdf-to-shopify-csv-a-safer-staging-checklist-17ha</guid>
      <description>&lt;p&gt;Supplier catalogues often arrive as PDFs, while price lists arrive as spreadsheets. The tempting workflow is “give the files to an AI and upload the result.” The safer workflow is to treat this as a data-matching and import-preflight project.&lt;/p&gt;

&lt;p&gt;A PDF is a presentation format, not a Shopify product data model. Before any upload, create a reviewable intermediate table and keep a clear line between extracted facts, assumptions, and fields that still need a human decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Choose one stable identity
&lt;/h2&gt;

&lt;p&gt;Pick the supplier SKU or product code as the primary matching key. Keep it as text so spreadsheet software does not turn long codes into scientific notation or remove leading zeroes.&lt;/p&gt;

&lt;p&gt;For each row, record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;supplier SKU or product code&lt;/li&gt;
&lt;li&gt;product title and a proposed Handle&lt;/li&gt;
&lt;li&gt;variant option names and values&lt;/li&gt;
&lt;li&gt;price and currency&lt;/li&gt;
&lt;li&gt;vendor&lt;/li&gt;
&lt;li&gt;image source reference&lt;/li&gt;
&lt;li&gt;a short source-page or source-file note&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not match a price list to a catalogue by title alone. If a code is missing, duplicated, or spelled differently in the two sources, put that row in an exception list instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Build variants deliberately
&lt;/h2&gt;

&lt;p&gt;Shopify product CSVs represent one product across multiple rows. Give all variants of the same product the same Handle, keep the option names consistent, and give each distinct variant its own SKU when one exists.&lt;/p&gt;

&lt;p&gt;Check for the common failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one variant accidentally gets a new Handle&lt;/li&gt;
&lt;li&gt;a colour or size value changes spelling halfway through the product&lt;/li&gt;
&lt;li&gt;a blank or reused SKU makes two variants look identical&lt;/li&gt;
&lt;li&gt;an update file contains only one variant from a multi-variant product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep a last-known-good product export nearby so you can compare identity before changing existing products.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Treat images as a separate mapping
&lt;/h2&gt;

&lt;p&gt;An image printed in a PDF is not automatically a usable Shopify image URL. If you have public image URLs, test that each URL opens directly without a login, redirect chain, or expiring session. Then map the image to the correct product and variant before importing.&lt;/p&gt;

&lt;p&gt;Do not silently replace a missing image with a guessed supplier URL. Mark it for review.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Validate the structured CSV
&lt;/h2&gt;

&lt;p&gt;Once the table has become a Shopify-shaped CSV, run a local preflight for headers, encoding, prices, handles, variant grouping, image fields, and file size. The browser-only &lt;a href="https://shopify-csv.aivismonitor.com/shopify-csv-import-fixer" rel="noopener noreferrer"&gt;Shopify CSV Import Fixer&lt;/a&gt; is useful at this stage because it can flag structural problems before you upload a small test batch.&lt;/p&gt;

&lt;p&gt;The checker does not parse supplier PDFs, crawl supplier websites, create products, or write to Shopify. It is a safety net for the structured CSV you have already prepared; ambiguous catalogue decisions still need review.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Import in a small batch
&lt;/h2&gt;

&lt;p&gt;Start with a few complete products: include every variant, one or two images, and the fields you actually intend to update. Confirm the preview, import result, handles, prices, option values, and image assignments before expanding the batch.&lt;/p&gt;

&lt;p&gt;Keep the original source files, the intermediate mapping sheet, the final CSV, and a short exception report. That trail is much easier to debug than trying to reconstruct why a large catalogue import changed an existing product.&lt;/p&gt;

&lt;p&gt;The goal is not “one-click PDF to store.” It is a staged conversion where every uncertain match is visible before Shopify receives the file.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>csv</category>
      <category>ecommerce</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Shopify CSV Variant Updates: Why a Tiny Batch Protects Existing Options</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Tue, 21 Jul 2026 01:39:40 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-csv-variant-updates-why-a-tiny-batch-protects-existing-options-2kci</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/shopify-csv-variant-updates-why-a-tiny-batch-protects-existing-options-2kci</guid>
      <description>&lt;p&gt;A Shopify product CSV can look valid in a spreadsheet and still change more variants than you intended. This is especially easy to miss when you are updating prices, inventory, SKUs, or option values on an existing catalog.&lt;/p&gt;

&lt;p&gt;The safest approach is to treat a CSV update as a comparison problem, not just a formatting problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Keep a last-known-good export
&lt;/h2&gt;

&lt;p&gt;Before editing, save a fresh Shopify product export as the baseline. Keep it unchanged. Your working file should be a separate copy.&lt;/p&gt;

&lt;p&gt;At minimum, preserve these identity fields while you work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Handle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Option1 Name&lt;/code&gt;, &lt;code&gt;Option1 Value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Option2 Name&lt;/code&gt;, &lt;code&gt;Option2 Value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Option3 Name&lt;/code&gt;, &lt;code&gt;Option3 Value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Variant SKU&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Variant Barcode&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a row is meant to update an existing variant, its identity should remain recognizable in both files.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Check each Handle as a group
&lt;/h2&gt;

&lt;p&gt;Shopify product CSVs represent a product and its variants across multiple rows. Group the file by &lt;code&gt;Handle&lt;/code&gt; and compare the variant count with the baseline.&lt;/p&gt;

&lt;p&gt;For every product group, look for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Handle that was accidentally renamed or blanked.&lt;/li&gt;
&lt;li&gt;A populated option value without the matching option name.&lt;/li&gt;
&lt;li&gt;A duplicate combination of option values.&lt;/li&gt;
&lt;li&gt;A variant that exists in the baseline but disappeared from the working file.&lt;/li&gt;
&lt;li&gt;A new row that has no SKU, barcode, or other deliberate identity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not delete a row merely because it looks repetitive. Repeated Handles are normal when one product has multiple variants; the risk is an unintended change to the group structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Separate formatting fixes from catalog decisions
&lt;/h2&gt;

&lt;p&gt;Normalize obvious formatting issues, but review catalog decisions manually. Prices should use a dot as the decimal separator and should not include currency symbols. Be careful with blank cells in an update file: a blank may be interpreted differently from “leave this field unchanged,” depending on the import workflow.&lt;/p&gt;

&lt;p&gt;For a price-only change, keep the option and variant identity columns intact. Avoid rebuilding the whole product row when a smaller update file is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Test a small, representative batch
&lt;/h2&gt;

&lt;p&gt;Choose a few products that cover the risky cases: one single-variant product, one product with two options, and one product with several variants. Preview that batch first, then compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;number of products shown,&lt;/li&gt;
&lt;li&gt;number of variants per product,&lt;/li&gt;
&lt;li&gt;option names and values,&lt;/li&gt;
&lt;li&gt;SKUs and barcodes,&lt;/li&gt;
&lt;li&gt;intended price changes only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the preview count is surprising, stop before importing the full catalog.&lt;/p&gt;

&lt;p&gt;For a browser-side first pass over a Shopify-shaped CSV, the &lt;a href="https://shopify-csv.aivismonitor.com/shopify-csv-variant-error-checker" rel="noopener noreferrer"&gt;Shopify CSV Variant Error Checker&lt;/a&gt; can flag structural issues before you prepare the smaller test batch. It does not write to Shopify, and the final import still requires your review in Shopify admin.&lt;/p&gt;

&lt;p&gt;The key checkpoint is simple: export, compare by Handle and variant identity, test a small batch, and only then import the full update.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Before Stocky Goes Read-Only: A Safer CSV Export Checklist</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Mon, 20 Jul 2026 01:36:45 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/before-stocky-goes-read-only-a-safer-csv-export-checklist-idl</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/before-stocky-goes-read-only-a-safer-csv-export-checklist-idl</guid>
      <description>&lt;p&gt;Stocky’s shutdown creates a data problem that is easy to underestimate. A clean purchase-order CSV is useful, but it may not preserve the context behind the numbers: supplier notes, receipt dates, partial deliveries, historical costs, lead times, and reorder decisions.&lt;/p&gt;

&lt;p&gt;Before August 31, treat the export as an archive project, not just a one-time migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Export every source while access still works
&lt;/h2&gt;

&lt;p&gt;Save the raw files before transforming anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purchase orders, including draft, confirmed, archived, and unarchived records&lt;/li&gt;
&lt;li&gt;Supplier and vendor data&lt;/li&gt;
&lt;li&gt;Stock counts, inventory adjustments, and transfer reports&lt;/li&gt;
&lt;li&gt;Current costs and any report that contains historical or manually adjusted costs&lt;/li&gt;
&lt;li&gt;Receipt details, especially received dates and partial-receipt status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the original JSON or CSV untouched and date-stamped. A flattened worksheet is a working copy, not a replacement for the source export.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Check whether the export is complete
&lt;/h2&gt;

&lt;p&gt;If you use an API export, page through the result until no records remain. A single response can look valid while containing only the first page. Keep a simple count of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purchase orders exported&lt;/li&gt;
&lt;li&gt;Line items exported&lt;/li&gt;
&lt;li&gt;Orders by status&lt;/li&gt;
&lt;li&gt;Orders with a supplier, SKU, cost, location, and receipt state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Round-number batches are worth investigating. So are missing statuses, missing locations, and a sudden drop in line-item counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Preserve the fields that CSVs often flatten
&lt;/h2&gt;

&lt;p&gt;For each purchase-order line, keep the PO number, supplier, SKU, title, ordered quantity, received quantity or receipt state, unit cost, location, and status. Store notes and dates in separate columns instead of combining them into one description field.&lt;/p&gt;

&lt;p&gt;If an export has no explicit received quantity, do not invent one. Use the available receipt date and item status as evidence, then mark partial or unconfirmed lines for manual review.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Build a small truth set
&lt;/h2&gt;

&lt;p&gt;Pick a few known purchase orders:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One fully received order&lt;/li&gt;
&lt;li&gt;One partially received order&lt;/li&gt;
&lt;li&gt;One with a cost adjustment&lt;/li&gt;
&lt;li&gt;One with supplier or PO notes&lt;/li&gt;
&lt;li&gt;One with multiple locations, if applicable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Compare those records against the exported worksheet. This catches missing context before you trust the full archive.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Keep the handoff reviewable
&lt;/h2&gt;

&lt;p&gt;Before recreating anything in another system, filter for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing SKU, supplier, cost, or location&lt;/li&gt;
&lt;li&gt;Ordered quantity greater than received quantity&lt;/li&gt;
&lt;li&gt;Missing receipt dates&lt;/li&gt;
&lt;li&gt;Duplicate PO numbers or duplicate line identities&lt;/li&gt;
&lt;li&gt;Supplier notes or historical-cost fields that did not survive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A local browser-side option is the &lt;a href="https://shopify-csv.aivismonitor.com/stocky-purchase-order-api-csv-exporter" rel="noopener noreferrer"&gt;Stocky purchase-order JSON to CSV exporter&lt;/a&gt;. It accepts a JSON export you provide, flattens purchase orders and line items into a review worksheet, and keeps likely pagination, ambiguous receipts, and missing fields visible. It does not call Stocky, ask for credentials, upload the file, or write back to Shopify.&lt;/p&gt;

&lt;p&gt;The practical goal is simple: keep the raw export, prove the archive is complete, and only then decide what deserves a new purchase order, transfer, or migration step.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Safer Shopify Inventory Transfer Worksheet Before Receiving</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Sat, 18 Jul 2026 01:42:32 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/a-safer-shopify-inventory-transfer-worksheet-before-receiving-3fh5</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/a-safer-shopify-inventory-transfer-worksheet-before-receiving-3fh5</guid>
      <description>&lt;p&gt;Large Shopify inventory transfers are easiest to troubleshoot before anyone clicks “Receive.”&lt;/p&gt;

&lt;p&gt;When a transfer arrives in several deliveries, the receiving screen is only one part of the problem. The harder question is whether the spreadsheet still identifies the right variant, location, and quantity after the shipment was split. A clean receiving checklist prevents a quick workaround from creating a barcode or inventory problem later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with stable identity
&lt;/h2&gt;

&lt;p&gt;Keep at least one stable product identity on every row:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variant ID when it is available&lt;/li&gt;
&lt;li&gt;Shopify SKU&lt;/li&gt;
&lt;li&gt;Barcode only when it is a real, existing barcode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not replace a real UPC, EAN, or GTIN with a temporary value just to make a screen searchable. If the receiving workflow does not find a variant by SKU, first confirm whether it expects the barcode field, whether the SKU is duplicated, and whether the row belongs to the correct location. A temporary backfill can be useful only when the barcode field is genuinely unused and the change has been tested on a small group.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the shipment boundary explicit
&lt;/h2&gt;

&lt;p&gt;A transfer that arrives in three deliveries should not be treated as one unexplained quantity. Add a shipment or receipt reference to your working sheet, then keep these fields together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transfer or shipment reference&lt;/li&gt;
&lt;li&gt;Source and destination location&lt;/li&gt;
&lt;li&gt;Variant ID, SKU, and barcode&lt;/li&gt;
&lt;li&gt;Expected quantity&lt;/li&gt;
&lt;li&gt;Quantity in this delivery&lt;/li&gt;
&lt;li&gt;Remaining quantity&lt;/li&gt;
&lt;li&gt;Review note&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sum of the delivery quantities should equal the expected transfer quantity. If it does not, leave the row open for review instead of forcing the remaining amount into the latest shipment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate planning from receiving
&lt;/h2&gt;

&lt;p&gt;A transfer plan answers “how much should move?” Receiving answers “what actually arrived?” They should share the same identifiers, but they should not overwrite one another.&lt;/p&gt;

&lt;p&gt;For planning, compare destination stock against a minimum and target level. Cap the suggested transfer by source availability and flag any source shortfall. For receiving, record the delivered quantity and inspect variance against the plan. A planning worksheet can suggest a quantity; it cannot confirm that a carrier delivered it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review before applying changes
&lt;/h2&gt;

&lt;p&gt;Before importing or entering anything into Shopify:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check that every row has a destination location.&lt;/li&gt;
&lt;li&gt;Check that SKU and barcode values are not duplicated across unrelated variants.&lt;/li&gt;
&lt;li&gt;Confirm that quantities are numeric, non-negative, and expressed in the same unit.&lt;/li&gt;
&lt;li&gt;Keep variant rows together when you split a large file.&lt;/li&gt;
&lt;li&gt;Test one small product group or shipment before processing the full transfer.&lt;/li&gt;
&lt;li&gt;Save the reviewed before-and-after worksheet for reconciliation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a browser-side worksheet that calculates suggested quantities from source stock, destination stock, and min/max rules, see the &lt;a href="https://shopify-csv.aivismonitor.com/shopify-inventory-transfer-csv-planner" rel="noopener noreferrer"&gt;Shopify inventory transfer CSV planner&lt;/a&gt;. It creates a local review file; it does not create Shopify transfers, change barcodes, or write inventory through an API.&lt;/p&gt;

&lt;p&gt;The safest transfer workflow is deliberately boring: preserve identity, separate shipments, validate quantities, and keep the reviewed file beside the final Shopify receipt record.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Safer CSV Review for Shopify Inventory Adjustments</title>
      <dc:creator>王磊</dc:creator>
      <pubDate>Thu, 16 Jul 2026 01:38:22 +0000</pubDate>
      <link>https://dev.to/_172c9b2760d6c5bc1e8966/a-safer-csv-review-for-shopify-inventory-adjustments-3hdk</link>
      <guid>https://dev.to/_172c9b2760d6c5bc1e8966/a-safer-csv-review-for-shopify-inventory-adjustments-3hdk</guid>
      <description>&lt;p&gt;Shopify’s newer inventory adjustment flow is more explicit about why stock changes. That is useful for reporting, but it also makes a quick “type -3 and move on” workflow easy to misunderstand.&lt;/p&gt;

&lt;p&gt;A safer approach is to review the adjustment file before applying changes in the admin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check first
&lt;/h2&gt;

&lt;p&gt;Before changing a quantity, keep these fields together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SKU or Variant ID&lt;/li&gt;
&lt;li&gt;Location&lt;/li&gt;
&lt;li&gt;Previous quantity&lt;/li&gt;
&lt;li&gt;New quantity&lt;/li&gt;
&lt;li&gt;Adjustment reason&lt;/li&gt;
&lt;li&gt;Note, operator label, and timestamp when available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The previous and new quantities are more reliable than a bare positive or negative number. They let you calculate the actual delta and spot a row that would increase stock when you intended to remove it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical review sequence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Export a fresh inventory or adjustment report. Keep the original export unchanged so you have a reference copy.&lt;/li&gt;
&lt;li&gt;Confirm that every row has a SKU or Variant ID and a location. A quantity without an identifier is not safe to apply.&lt;/li&gt;
&lt;li&gt;Compare previous quantity with new quantity. For a removal, the new value should be lower; for a receipt or correction, it should be higher.&lt;/li&gt;
&lt;li&gt;Require a reason such as damaged, theft, cycle count, receiving discrepancy, or transfer. Add a short note when the reason alone is not enough context.&lt;/li&gt;
&lt;li&gt;Look for non-numeric quantities and duplicate rows. A duplicate adjustment can quietly double the intended change.&lt;/li&gt;
&lt;li&gt;Test one known SKU in Shopify first and read the preview. If the preview moves in the wrong direction, stop and fix the source row.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a browser-side worksheet that calculates the delta, flags missing reasons or quantities, and highlights possible duplicate events, see the &lt;a href="https://shopify-csv.aivismonitor.com/shopify-inventory-adjustment-reason-audit" rel="noopener noreferrer"&gt;Shopify inventory adjustment reason audit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply the reviewed change manually
&lt;/h2&gt;

&lt;p&gt;In Shopify admin, open Products → Inventory, select the Available quantity, and use the adjustment flow for the relevant location and destination. Add the reason that matches the event, then confirm the preview before saving.&lt;/p&gt;

&lt;p&gt;The worksheet is a review aid, not an API writer. Keep the exported audit trail with the original report so a later stock-take or accounting review can explain what changed and why.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
