The snapshot CSV: every column and how to restore from it
What each column in the SafeSale snapshot CSV means, why Applied At or Restored At can be empty, and how to put original prices back from the file alone.
By Beacon Wave Studio · · 7 min read
Every sale page in SafeSale has a quiet line above the product list that reads something like Snapshots: 412 original prices saved · 412 written · 0 restored, with a Download snapshot CSV button next to it. Most merchants never click it, and that is fine: the app restores prices on its own when the sale ends. The button exists for the day something is not fine. This post is the manual for the file it gives you: what each column holds, what an empty cell is telling you, and the three ways to put a price back using nothing but the file.
What a snapshot is
Before SafeSale writes a single sale price, it records the price and compare-at price every affected variant has at that moment. That record is the snapshot. One row per variant, frozen at the start, never updated while the sale runs. When the sale ends, the restore reads those rows and writes them back. The CSV is a plain export of that table, sorted by product and then by variant, so rows for the same product sit together.
The eight columns
| Column | What it holds |
|---|---|
| Product ID | The numeric id of the product, the same number you see at the end of the product URL in the admin. |
| Variant ID | The numeric id of the variant. Together with Product ID this is the key of the row. |
| Variant Price | The price the variant had before the sale touched it. This is the value a restore writes back. |
| Variant Compare At Price | The compare-at price before the sale. Empty means the variant had no compare-at price, and a restore clears the field rather than leaving a sale value behind. |
| Product GID | The same product as a GraphQL id, gid://shopify/Product/..., for scripts and import tools that want that form. |
| Variant GID | The variant as a GraphQL id, gid://shopify/ProductVariant/... |
| Applied At | The moment the sale price was written to this variant, in UTC. Empty if the sale price was never written. |
| Restored At | The moment the original price was written back. Empty if that has not happened yet. |
The two price columns use the exact header names from Shopify's product CSV format on purpose. If you paste them into a product export the importer recognises them without renaming. Cells that contain a comma, a quote or a line break are quoted, which only ever matters for the timestamps and not for ids or prices.
Reading the two timestamps
The ids and prices are static. The two timestamps are the part of the file that changes with time, and reading them together tells you where each variant is.
- Both empty. The snapshot was taken but the sale price has not been written. You see this on a scheduled sale that has not started, and on a variant the sale skipped at apply time, for example because it was blocked by a guardrail. The store still shows the original price, so there is nothing to restore.
- Applied At set, Restored At empty. The variant is at its sale price right now. Normal for a live sale. Suspicious for a sale that is marked as ended.
- Both set. The variant went on sale and came back. If the store still shows the sale price for such a variant, somebody or something edited the price after the restore, which is what the audit log is for.
So the question "which prices are still wrong" has a spreadsheet answer: filter to rows where Applied At is filled and Restored At is empty. On a healthy ended sale that filter is empty. On a sale that stopped halfway through its restore it is the exact list of variants to fix, and nothing else.
Three ways to restore from the file
A handful of variants: type them in
Open the variant in the admin, either by searching the product or by going straight to the URL /products/<Product ID>/variants/<Variant ID> under your store's admin address, and type the Variant Price and Variant Compare At Price from the row. Leave compare-at empty if the cell is empty. For fewer than twenty rows this is faster than any import and leaves no room for a mapping mistake.
Dozens to hundreds: the bulk editor
Select the affected products in the product list, open the bulk editor with the Price and Compare at price columns, and work down the sheet with the CSV open beside it. Sorting both by product keeps them in step. The bulk editor validates as you type and saves in one go, so a typo cannot reach the store half-finished.
Thousands: an import
Shopify's product importer updates existing products by matching on the handle, and the snapshot has no handle column, so the route is: export your products from the admin, look up each row's Variant Price and Variant Compare At Price from the snapshot by variant, paste them into the export, and import it back with the option to overwrite existing products. A tool that matches on variant id, such as Matrixify, skips the join entirely. If you have a developer on hand, the GID columns feed a productVariantsBulkUpdate mutation directly.
Why the file exists at all
An app that rewrites prices has a duty to leave you a way out that does not depend on the app. Shopify revokes an app's access the moment it is uninstalled, so if you remove SafeSale with a sale still live, the app cannot restore anything. SafeSale keeps the snapshots for 90 days after an uninstall and a reinstall picks them up, which is the route we describe in stuck sale prices after uninstall. The CSV is the layer under that one. It needs no reinstall, no access, no support ticket. It is a text file of your own prices, in your own hands, and if you ever do write to support, attaching it is the fastest way to get help, because it tells us exactly which variants are where.
Our advice is boring and takes ten seconds: when a big sale goes live, download the file and drop it in the folder where you keep the rest of the campaign. You will almost certainly never open it. SafeSale would still rather you had it.
Frequently asked questions
When should I download the snapshot CSV?
Right after the sale goes live is the best moment, because by then every variant the sale touched has a row with an Applied At value, and you have the whole picture in one file. Downloading it before the start is still useful: the original prices are already recorded, only the Applied At column will be empty. Keep the file somewhere outside the app, which is the whole point of having it.
Why is Restored At empty for some rows and filled for others?
Restored At is written per variant at the moment its original price goes back. During a restore that is still running you will see a mix: rows finished so far have a timestamp, the rest are still empty. If the sale has ended and a row is still empty, that variant was not restored, and its row is exactly what you need to fix the price by hand.
Can I import the snapshot CSV straight back into Shopify?
Not with Shopify's own importer on its own, because that importer matches rows by product handle and the snapshot is keyed by variant id. The price columns already carry Shopify's names, so joining the file to a product export in a spreadsheet takes a minute. Import tools that match on variant id, Matrixify being the common one, take the file more or less as it is.
Does the file include prices from a sale run as an automatic discount?
It includes whatever snapshots the sale created. A sale that only uses a checkout discount never rewrites variant prices, so it has no snapshots and the file is just the header row. A price rewrite sale, or a mixed one, has one row per variant whose price it planned to change.