How To Export List of Purchase Orders to CSV in Shopify
Shopify doesn't let you export purchase orders. No button, no built-in option. If you've got more than a handful of POs, copying them manually into a spreadsheet is painful.
Here's a free script you can run directly in your browser to export all your purchase orders — with full details — into a clean CSV file. No app install, no third-party access to your store.
Video Tutorial
You can view the code mentioned in the video here.
What It Exports
Every purchase order in your store, including:
- PO number, status (Draft, Ordered, etc.), supplier name
- Destination, payment terms, estimated arrival
- Shipping carrier and tracking number
- All line items with quantities, costs, tax, and totals
- Reference number, notes, tags, and cost summary
How to Run It
Step 1: Log into your Shopify admin and go to Products → Purchase orders.
Step 2: Open your browser's developer console. On most browsers:
- Mac:
Cmd + Option + J(Chrome) orCmd + Option + K(Firefox) - Windows:
Ctrl + Shift + J(Chrome) orCtrl + Shift + K(Firefox)
Step 3: Type allow pasting before you paste the code and hit enter. This is a browser security feature — to allow you to run code in console.
Step 4: Paste the script below and hit Enter.
The script will automatically visit each purchase order, grab the details, navigate back, and repeat until it's gone through all of them.
You'll see progress in the console. When it's done, a CSV file will download automatically.
Important: Keep the browser tab in focus while it runs. Don't switch away — some browsers slow down background tabs.
(async () => {
const DELAY = 2500;
const sleep = ms => new Promise(r => setTimeout(r, ms));
function waitFor(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) return resolve(el);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) { observer.disconnect(); resolve(el); }
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => { observer.disconnect(); reject('Timeout: ' + selector); }, timeout);
});
}
async function collectPOLinks() {
const links = [];
function grab() {
document.querySelectorAll('a[href*="/purchase_orders/"][data-primary-link]').forEach(a => {
const href = a.getAttribute('href');
if (href && !links.includes(href)) links.push(href);
});
}
grab();
console.log(`Page 1: ${links.length} POs`);
let page = 1;
while (page < 100) {
const nextBtn = document.querySelector('button[aria-label="Next"]');
if (!nextBtn || nextBtn.disabled || nextBtn.getAttribute('aria-disabled') === 'true') break;
nextBtn.click();
await sleep(2500);
page++;
grab();
console.log(`Page ${page}: ${links.length} POs total`);
}
return links;
}
function scrapePOPage() {
function getText(selector) {
return document.querySelector(selector)?.textContent?.trim() || '';
}
function getFieldByLabel(labelText) {
const subduedEls = document.querySelectorAll('s-internal-text[color="subdued"]');
for (const el of subduedEls) {
if (el.textContent.trim().toLowerCase() === labelText.toLowerCase()) {
const container = el.closest('div');
const p = container?.querySelector('p[aria-hidden="false"], p');
if (p) return p.textContent.trim();
const link = container?.querySelector('s-internal-link');
if (link) return link.textContent.trim();
}
}
const labels = document.querySelectorAll('label');
for (const lbl of labels) {
if (lbl.textContent.trim().toLowerCase().includes(labelText.toLowerCase())) {
const inputId = lbl.getAttribute('for');
if (inputId) {
const input = document.getElementById(inputId);
if (input && input.value) return input.value.trim();
}
const wrapper = lbl.closest('.Polaris-Connected, .Polaris-Labelled__LabelWrapper')?.parentElement;
if (wrapper) {
const input = wrapper.querySelector('input, textarea');
if (input && input.value) return input.value.trim();
}
}
}
const labelSpans = document.querySelectorAll('.Polaris-Label__Text span');
for (const span of labelSpans) {
if (span.textContent.trim().toLowerCase() === labelText.toLowerCase()) {
const labelEl = span.closest('label');
if (labelEl) {
const inputId = labelEl.getAttribute('for');
if (inputId) {
const input = document.getElementById(inputId);
if (input && input.value) return input.value.trim();
}
}
}
}
return '';
}
// PO number
const poNum = getText('.Polaris-Breadcrumbs__PageTitle h1');
// Status
const badgeEl = document.querySelector('.Polaris-Header-Title__TitleMetadata s-internal-badge');
let status = '';
if (badgeEl) {
badgeEl.childNodes.forEach(node => {
if (node.nodeType === Node.TEXT_NODE) status += node.textContent.trim();
});
if (!status) status = badgeEl.innerText?.trim() || '';
}
// Number of products
const footerText = document.querySelector('[class*="Footer"] p')?.textContent.trim() || '';
const numProducts = footerText.match(/(\d+)\s*variant/)?.[1] || '';
// Supplier & Destination
const halfBoxes = document.querySelectorAll('[style*="width: 50%"]');
const supplierBox = halfBoxes[0];
const destBox = halfBoxes[1];
const supplier =
supplierBox?.querySelector('button[title]')?.getAttribute('title') ||
supplierBox?.querySelector('.Polaris-Text--headingLg')?.textContent.trim() || '';
const destName =
destBox?.querySelector('button[title]')?.getAttribute('title') ||
destBox?.querySelector('.Polaris-Text--headingLg')?.textContent.trim() || '';
const destAddr =
destBox?.querySelector('s-internal-paragraph[size="small"]')?.textContent.trim() || '';
// Payment terms
let paymentTerms = getFieldByLabel('Payment terms');
if (!paymentTerms) {
const selectEl = document.querySelector('s-internal-select[name="paymentTerms"]');
if (selectEl) {
const selectedOption = selectEl.querySelector('s-option[selected]');
if (selectedOption) {
paymentTerms = selectedOption.textContent.trim();
} else {
paymentTerms = selectEl.value || selectEl.getAttribute('value') || '';
}
}
}
// Shipment details
const estArrival = getFieldByLabel('Estimated arrival');
const carrier = getFieldByLabel('Shipping carrier');
const trackingNum = getFieldByLabel('Tracking number');
// Reference number
let refNumber = getFieldByLabel('Reference number');
if (!refNumber) {
const refField = document.querySelector('s-internal-text-field[name="referenceNumber"]');
if (refField) {
refNumber = refField.value || refField.getAttribute('value') || '';
}
}
// Note to supplier
let noteToSupplier = getFieldByLabel('Note to supplier');
if (!noteToSupplier) {
const noteArea = document.querySelector('textarea[name="supplierNote"]');
if (noteArea) noteToSupplier = noteArea.value.trim();
}
// Total received (Ordered mode only)
const recvBtn = document.querySelector('button[aria-label*="Total received"]');
const totalReceived = recvBtn?.textContent.replace('Total received:', '').trim() || '';
// Tags
let tags = '';
document.querySelectorAll('s-internal-heading').forEach(h => {
if (h.textContent.trim() === 'Tags') {
tags = h.closest('div')?.querySelector('s-internal-paragraph')?.textContent.trim() || '';
}
});
if (!tags || tags === 'None') {
const tagsInput = document.querySelector('input[name="tags"]');
if (tagsInput && tagsInput.value) tags = tagsInput.value.trim();
}
// Line items
const lineItems = [];
document.querySelectorAll('tr[class*="PurchaseOrderLineItem"]').forEach(row => {
const product =
row.querySelector('td[class*="ItemDetails"] s-internal-text[fontweight="semibold"]')?.textContent.trim() || '';
const skuCell = row.querySelector('td[class*="SupplierSku"]');
let sku = skuCell?.textContent.trim() || '';
if (!sku) {
const skuInput = skuCell?.querySelector('input');
if (skuInput) sku = skuInput.value.trim();
}
const recvCell = row.querySelector('td[class*="Received"]');
let received = recvCell?.querySelector('[class*="GridSummary"]')?.textContent.trim() || '';
if (!received) {
const qtyInput = recvCell?.querySelector('input[type="number"]');
if (qtyInput) received = qtyInput.value;
}
const costCell = row.querySelector('td[class*="Cost_"]');
let cost = '';
const costInput = costCell?.querySelector('input[inputmode="decimal"]');
if (costInput) {
const prefix = costCell?.querySelector('[class*="Prefix"]')?.textContent.trim() || '';
cost = prefix + costInput.value;
} else {
cost = costCell?.innerText.replace('Cost', '').trim() || '';
}
const taxCell = row.querySelector('td[class*="Tax_"]');
let tax = '';
const taxInput = taxCell?.querySelector('input[inputmode="numeric"]');
if (taxInput) {
tax = taxInput.value + '%';
} else {
tax = taxCell?.innerText.replace('Tax', '').trim() || '';
}
const totalCell = row.querySelector('td[class*="Total_"]');
const total = totalCell?.innerText.replace('Total', '').trim() || '';
lineItems.push({ product, sku, received, cost, tax, total });
});
// Cost summary
let taxes = '', subtotal = '', shipping = '', grandTotal = '';
document.querySelectorAll('[class*="SummaryLine"]').forEach(line => {
const label = line.innerText.split('\n')[0]?.trim().toLowerCase() || '';
const valSpan = line.querySelector('[class*="DisplayValue"] span');
const val = valSpan?.textContent.trim() || line.querySelector('[class*="DisplayValue"]')?.innerText?.trim() || '';
if (label.startsWith('tax')) taxes = val;
else if (label === 'subtotal') subtotal = val;
else if (label === 'shipping') shipping = val;
else if (label === 'total') grandTotal = val;
});
return {
po_number: poNum, status, num_products: numProducts,
supplier,
destination: destName + (destAddr ? ` | ${destAddr}` : ''),
payment_terms: paymentTerms, total_received: totalReceived,
estimated_arrival: estArrival, shipping_carrier: carrier,
tracking_number: trackingNum, reference_number: refNumber,
note_to_supplier: noteToSupplier, tags,
line_items: lineItems.map(li =>
`${li.product} [SKU:${li.sku}] Qty:${li.received} Cost:${li.cost} Tax:${li.tax} Total:${li.total}`
).join(' || '),
taxes, subtotal, shipping, total: grandTotal
};
}
// ---- MAIN ----
console.log('Collecting PO links from list...');
const poLinks = await collectPOLinks();
console.log(`Found ${poLinks.length} POs. Will now visit each one.\n`);
const listUrl = window.location.href;
const results = [];
for (let i = 0; i < poLinks.length; i++) {
console.log(`[${i + 1}/${poLinks.length}] Navigating to ${poLinks[i]}`);
const link = document.querySelector(`a[href="${poLinks[i]}"]`);
if (link) {
link.click();
} else {
window.location.href = poLinks[i];
}
try {
await waitFor('.Polaris-Breadcrumbs__PageTitle h1', 10000);
await sleep(1000);
} catch (e) {
console.warn('Detail page did not load, skipping');
results.push({ po_number: poLinks[i], error: 'page did not load' });
window.history.back();
await sleep(2000);
continue;
}
try {
const data = scrapePOPage();
results.push(data);
console.log(` ✓ ${data.po_number} | ${data.status} | ${data.supplier} | ${data.num_products} products`);
} catch (err) {
console.warn(` ✗ Error:`, err);
results.push({ po_number: poLinks[i], error: err.message });
}
const backLink = document.querySelector('a[aria-label="Purchase orders"]');
if (backLink) {
backLink.click();
} else {
window.history.back();
}
try {
await waitFor('a[data-primary-link][href*="/purchase_orders/"]', 10000);
await sleep(1000);
} catch (e) {
console.warn('List page did not reload, trying direct nav');
window.location.href = listUrl;
await sleep(3000);
}
}
if (!results.length) { console.log('No data collected!'); return; }
const headers = Object.keys(results[0]);
const csv = [
headers.join(','),
...results.map(r => headers.map(h => `"${String(r[h] || '').replace(/"/g, '""')}"`).join(','))
].join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `purchase_orders_${new Date().toISOString().slice(0, 10)}.csv`;
a.click();
console.log(`\nDone! Exported ${results.length} POs.`);
})();
Is This Safe?
This script runs entirely in your browser. It doesn't send your data anywhere, doesn't install anything, and doesn't modify any of your purchase orders. It only reads what's already on your screen.
If you want to verify that yourself, paste the code into Claude or ChatGPT and ask: "What does this code do? Does it send data anywhere?" They'll walk you through it line by line.
Limitations
- The script navigates through each PO one at a time, so it takes a few seconds per order. If you have 100+ POs, give it a few minutes.
- If your connection is slow, you may need to increase the
DELAYvalue at the top of the script (change2500to4000or higher). - The script handles both Draft and Ordered purchase orders automatically.
That's it. No app to pay for, no CSV import/export feature request to wait on. Just your data, in a spreadsheet, in under a minute.