Add to Procurement

v0.6.0

Turns the cart or product page you're on into rows you paste straight into the procurement sheet (columns A–I). Works on McMaster-Carr, Amazon, and any Shopify store (WCP, AndyMark, ThriftyBot…).

Install (desktop Chrome / Edge)

  1. Show your bookmarks bar: Ctrl/Cmd + Shift + B.
  2. Drag this button onto the bookmarks bar:

➕ Add to Procurement

Use it

  1. On a cart or product page, click the Add to Procurement bookmark.
  2. Enter your name when prompted, then click Copy rows.
  3. In the procurement sheet, click the first empty cell in column A and paste.

Desktop only for now. Phones can't run bookmarklets reliably (and stores like McMaster block hosted scripts via CSP), so the mobile path is parked. Do procurement entry from a computer.

Manual install / raw bookmarklet link
javascript:(()=>{var et=Object.defineProperty;var C=(t,e)=>{for(var n in e)et(t,n,{get:e[n],enumerable:!0})};var k={};C(k,{key:()=>ot,scrapeCart:()=>it,scrapeProduct:()=>at});function l(t,e=0){if(typeof t=="number")return Number.isFinite(t)?t:e;if(t==null)return e;let n=parseFloat(String(t).replace(/[^0-9.\-]/g,""));return Number.isFinite(n)?n:e}function h(t){return Math.round((Number(t)+Number.EPSILON)*100)/100}function _(t=new Date){return`${t.getMonth()+1}/${t.getDate()}/${t.getFullYear()}`}var nt=/^(utm_|ref$|ref_|_ga$|gclid|fbclid|mc_[ce]id|psc$|th$)/i;function T(t,e){if(!t)return"";try{let n=new URL(t,e);for(let o of[...n.searchParams.keys()])nt.test(o)&&n.searchParams.delete(o);return n.toString()}catch{return String(t).trim()}}function S(t){return String(t||"").replace(/^www\./,"").toLowerCase()}var ot="shopify",rt=/\(([A-Z]{2,5}-\d{3,5})\)/,R=/\$\s?([\d,]+\.\d{2})/;async function it(t){return((await t.fetch("/cart.js").then(n=>n.json())).items||[]).map(n=>({partNumber:n.sku||"",description:U(n.product_title||n.title,n.variant_title),quantity:n.quantity,unitCost:I(n.final_price??n.price),weblink:lt(t,n.url)}))}async function at(t){let e=ct(t);if(e.length)return e;let n=st(t.location.pathname);if(!n)throw new Error("Not a Shopify product page.");let{product:o}=await t.fetch(`/products/${n}.json`).then(i=>i.json()),r=ut(o,t.location.search);return[{partNumber:r.sku||"",description:U(o.title,r.title),quantity:1,unitCost:I(r.price),weblink:pt(t,n,r.id)}]}function ct(t){let e=[],n=new Set;for(let o of t.document.querySelectorAll("input.product-custom-option")){if(!o.checked)continue;let r=o.closest(".admin__field-option");if(!r||r.classList.contains("ihidden"))continue;let i=(r.textContent||"").replace(/\s+/g," ").trim(),a=rt.exec(i);if(!a||n.has(a[1]))continue;n.add(a[1]);let c=R.exec(i.slice(a.index+a[0].length))||R.exec(i),p=r.querySelector('input.option-qty, input[name^="options_qty"]');e.push({partNumber:a[1],description:i.slice(0,a.index).trim(),quantity:p?l(p.value,1):1,unitCost:c?l(c[1]):0,weblink:t.location.href})}return e}function I(t){if(typeof t=="number")return t/100;let e=parseFloat(t);return Number.isFinite(e)?e:0}function U(t,e){let n=(e||"").trim();return n&&n.toLowerCase()!=="default title"?`${t} (${n})`:t||""}function st(t){let e=/\/products\/([^/?%23]+)/.exec(t||"");return e?e[1]:null}function ut(t,e){let n=t.variants||[],o=new URLSearchParams(e||"").get("variant");if(o){let r=n.find(i=>String(i.id)===String(o));if(r)return r}return n[0]||{}}function pt(t,e,n){let o=`${t.location.origin}/products/${e}`;return n?`${o}?variant=${n}`:o}function lt(t,e){try{return new URL(e,t.location.origin).toString()}catch{return e||""}}var $={};C($,{key:()=>dt,scrapeCart:()=>ft,scrapeProduct:()=>gt});var dt="amazon",mt=/\/(?:dp|gp\/product|gp\/aw\/d)\/([A-Z0-9]{10})/i;function ft(t){let e=t.document,n=e.querySelector('%23sc-active-cart, [data-name="Active Items"]')||e,o=[...n.querySelectorAll(".sc-list-item[data-asin], [data-asin][data-itemtotal]")];o.length||(o=[...n.querySelectorAll("[data-asin]")]);let r=new Set,i=[];for(let a of o){let c=a.getAttribute("data-asin");!c||r.has(c)||(r.add(c),i.push({partNumber:c,description:b(a,[".sc-product-title",".a-truncate-full",".sc-product-link","a .a-link-normal"]),quantity:yt(a),unitCost:l(b(a,[".sc-product-price",".sc-badge-price-to-pay .a-offscreen",".a-price .a-offscreen",".sc-price"])),weblink:H(t,c)}))}return i}function gt(t){let e=t.document,n=xt(t)||ht(e,["%23ASIN","input%23ASIN","[data-asin]"],"value","data-asin");return[{partNumber:n||"",description:b(e,["%23productTitle","%23title"]),quantity:1,unitCost:l(b(e,["%23corePriceDisplay_desktop_feature_div .a-offscreen","%23corePrice_feature_div .a-offscreen",".a-price .a-offscreen","%23priceblock_ourprice","%23price"])),weblink:n?H(t,n):t.location.href}]}function yt(t){let e=t.querySelector('input.sc-quantity-textfield, input[name="quantity"]');if(e&&e.value)return l(e.value,1);let n=t.getAttribute("data-quantity");if(n)return l(n,1);let o=t.querySelector("select option[selected], .a-dropdown-prompt");return o?l(o.textContent,1):1}function xt(t){let e=mt.exec(t.location.pathname||t.location.href||"");return e?e[1]:null}function H(t,e){return`${t.location.origin}/dp/${e}`}function b(t,e){for(let n of e){let o=t.querySelector(n),r=o&&o.textContent?o.textContent.trim():"";if(r)return r}return""}function ht(t,e,...n){for(let o of e){let r=t.querySelector(o);if(r)for(let i of n){let a=i==="value"?r.value:r.getAttribute(i);if(a)return a}}return""}var A={};C(A,{key:()=>bt,scrapeCart:()=>qt,scrapeProduct:()=>Ct});var bt="mcmaster",wt=/\b\d{3,6}[A-Z]\d{2,6}[A-Z0-9]*\b/,D=/^\/(\d{3,6}[A-Z]\d{2,6}[A-Z0-9]*)\/?$/i,vt=/\$\s?([\d,]+\.\d{2})/;async function qt(t){await O(t);let e=t.document,n=[...e.querySelectorAll("input.line-part-number-input")].map(p=>(p.value||"").trim().toUpperCase()),o=[...e.querySelectorAll("input.line-quantity-input")].map(p=>l(p.value,1)),r=[...e.querySelectorAll(".line-total-price")].map(p=>B(p.textContent)),i=St(e),a=Math.min(n.length,o.length,r.length),c=[];for(let p=0;p<a;p++){let m=n[p];if(!m)continue;let f=o[p];c.push({partNumber:m,description:i[m]||"",quantity:f,unitCost:f>0?r[p]/f:0,weblink:j(t,m)})}return c}async function Ct(t){await O(t);let e=t.document,n=kt(t)||$t(e);return[{partNumber:n||"",description:At(e),quantity:1,unitCost:B((e.querySelector('[class*="Price" i], [class*="price" i]')||e.body||{}).textContent),weblink:n?j(t,n):t.location.href}]}function St(t){let e={};for(let n of t.querySelectorAll("a[href]")){let o=D.exec(n.getAttribute("href")||"");if(!o)continue;let r=o[1].toUpperCase(),i=(n.textContent||"").trim();i&&i.toUpperCase()!==r&&!e[r]&&(e[r]=i)}return e}function B(t){let e=vt.exec(t||"");return e?l(e[1]):0}function kt(t){let e=D.exec(t.location.pathname||"");return e?e[1].toUpperCase():null}function $t(t){let e=wt.exec(t.title||"");return e?e[0].toUpperCase():null}function At(t){let e=t.querySelector("h1");return(e&&e.textContent.trim()||t.title||"").replace(/\s*\|\s*McMaster-?Carr\s*$/i,"").trim()}function j(t,e){return`${t.location.origin}/${e}/`}function O(t){let e=t.window;return!e||typeof e.requestAnimationFrame!="function"?Promise.resolve():new Promise(n=>{let o=0,r=()=>{t.document.querySelector("input.line-part-number-input, a[href]")||o++>10?n():e.requestAnimationFrame(r)};r()})}var Nt={"wcproducts.com":"WCP","andymark.com":"Andy Mark","thethriftybot.com":"Thrifty Bot"};function V(t){let e=S(t);return/(^|\.)amazon\./.test(e)?"amazon":/(^|\.)mcmaster\.com$/.test(e)?"mcmaster":"shopify"}function Z(t){let e=S(t);return Nt[e]||e.split(".")[0]}var K={amazon:"Amazon",mcmaster:"McMaster Carr"};var Ft={shopify:k,amazon:$,mcmaster:A};function Y(t){let e=V(t.location.hostname),n=e==="shopify"?Z(t.location.hostname):K[e];return{adapter:Ft[e],key:e,mode:Lt(e,t),storeName:n}}function Lt(t,e){let n=e.location.pathname||"";return t==="amazon"?/\/(?:dp|gp\/product|gp\/aw\/d)\//i.test(n)?"product":"cart":t==="mcmaster"?/^\/\d{3,6}[A-Z]\d{2,6}[A-Z0-9]*\/?$/i.test(n)?"product":"cart":/\/products\//.test(n)?"product":"cart"}function G(t,e={}){let n=e.requestDate||_(),o=e.requestor||"";return(t||[]).map(r=>{let i=l(r.quantity,1),a=h(l(r.unitCost,0));return{requestDate:n,requestor:o,partNumber:w(r.partNumber),description:w(r.description),quantity:i,unitCost:a,extendedCost:h(i*a),storeName:w(r.storeName)||w(e.storeName),weblink:T(r.weblink)}})}function N(t){return t.quantity=l(t.quantity,1),t.extendedCost=h(t.quantity*l(t.unitCost,0)),t}function w(t){return(t==null?"":String(t)).replace(/\s+/g," ").trim()}var W=["requestDate","requestor","partNumber","description","quantity","unitCost","extendedCost","storeName","weblink"];function x(t){return(t||[]).map(e=>W.map(n=>J(e[n])).join("	")).join(`
`)}function F(t){let e=n=>`<tr>${W.map(o=>`<td>${zt(J(n[o]))}</td>`).join("")}</tr>`;return`<table>${(t||[]).map(e).join("")}</table>`}function J(t){return t==null?"":String(t).replace(/[\t\r\n]+/g," ").trim()}function zt(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Mt(){return{document,window,location:window.location,fetch:(...t)=>window.fetch(...t)}}async function Q(t={}){let e=t.ctx||Mt(),{adapter:n,key:o,mode:r,storeName:i}=Y(e),a=r==="product"?await n.scrapeProduct(e):await n.scrapeCart(e),c=G(a,{storeName:i,requestor:t.requestor,requestDate:t.requestDate});return{rows:c,tsv:x(c),mode:r,storeName:i,store:o,count:c.length}}var X=`<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1080 1080" xml:space="preserve">
<style type="text/css">
	.st0{fill:currentColor;}
</style>
<g>
	<path class="st0" d="M863.23,340.9l45.78-142.14l-56.61-67.94h-77.76l-63.7,62.43l-171.34,0.65l-166.64-0.63l-0.14-0.1l-0.07,0.1
		l-4.5-0.02l-63.7-62.43H226.8l-56.61,67.94l45.78,142.14L96.55,596.21l167.7,318.59l4.34,0.47
		c105.84,11.52,192.42,19.1,271.01,18.69c2.09,0.01,4.16,0.02,6.26,0.02c76.91,0,161.72-7.49,264.75-18.71l4.34-0.47l167.7-318.59
		L863.23,340.9z M962.52,591.88l-126.34,55.91l23.8-275.14L962.52,591.88z M781.34,147.22h63.38l45.91,55.11l-39.89,123.85
		L725.96,201.49L781.34,147.22z M713.61,212.34l132.32,132.22L818.86,657.6l-78.9,93.52V543L547.93,440.03V324.62L713.61,212.34z
		 M539.57,210.3l148.66-0.57L539.81,310.31L396.47,209.75L539.57,210.3z M281.83,467.05l56.93,83.2v200.3l-78.42-92.95
		l-16.48-190.55H281.83z M282.02,450.65h-39.59l-9.18-106.09l13.78-13.77l80.02,32.09L282.02,450.65z M531.53,324.53v102.65
		L259.66,318.18l108.4-108.32L531.53,324.53z M188.57,202.32l45.91-55.11h63.38l55.38,54.27L228.45,326.17L188.57,202.32z
		 M219.22,372.65l23.8,275.14l-126.34-55.91L219.22,372.65z M274.7,899.43l-150.76-286.4l123.4,54.61l208.99,247.71
		C401.75,912.41,342.18,906.75,274.7,899.43z M539.64,917.55h-0.04h-0.04c-19.7,0.1-39.94-0.31-60.87-1.16L355.16,770V556.63
		l187.12-101.01l181.28,97.2v217.75L600.51,916.4C579.57,917.25,559.34,917.66,539.64,917.55z M804.5,899.43
		c-67.48,7.31-127.05,12.98-181.64,15.91l209-247.71l123.4-54.61L804.5,899.43z"/>
	<polygon class="st0" points="432.03,588.89 399.18,643.26 428.03,643.26 477.4,708.06 479.09,620.62 603.42,620.62 605.12,708.06
		653.72,643.04 681.68,643.04 640.62,588.89 	"/>
	<polygon class="st0" points="600.68,813.02 561.33,813.02 519.31,813.02 479.96,813.02 450.61,766.53 437.3,788.72 476.7,865.62
		519.31,865.62 561.33,865.62 611.85,865.62 643.34,788.72 630.03,766.53 	"/>
	<path class="st0" d="M488.88,525.54l46.67,27.03c3.47,2.14,7.85,2.14,11.32,0l46.67-27.59c6.71-4.13,6.05-14.1-1.14-17.31
		l-51.18-27.36l-51.18,27.92C482.83,511.45,482.17,521.41,488.88,525.54z"/>
	<polygon class="st0" points="679.38,441.69 641.81,424.82 608.63,452.08 648.32,473.94 	"/>
</g>
</svg>
`;var z="procurement-scraper-overlay",tt="procReq.requestor",Pt="Manrope,-apple-system,system-ui,Segoe UI,Roboto,sans-serif",v={gold:"%23F0B842",goldHover:"%23E0A832",green:"%237CD49B",red:"%23E85D5D"},u={card:`box-sizing:border-box;width:min(420px,92vw);max-height:80vh;overflow:auto;background:%2315203F;color:%23F5F1E8;border:1px solid rgba(255,255,255,.14);border-radius:10px;box-shadow:0 30px 80px rgba(0,0,0,.5);padding:14px;font:13px/1.45 ${Pt};`,head:"display:flex;align-items:center;gap:8px;margin-bottom:10px;",mark:"color:%23F5F1E8;display:flex;flex:0 0 auto;",count:"font-size:15px;font-weight:700;",sub:"color:rgba(245,241,232,.62);text-transform:capitalize;",close:"margin-left:auto;border:0;background:none;font-size:20px;line-height:1;cursor:pointer;color:rgba(245,241,232,.38);",reqLabel:"display:block;color:rgba(245,241,232,.62);margin-bottom:10px;",reqInput:"display:block;width:100%25;margin-top:3px;padding:6px 8px;background:%23070F22;color:%23F5F1E8;border:1px solid rgba(255,255,255,.14);border-radius:6px;box-sizing:border-box;font:inherit;",row:"display:flex;align-items:center;gap:8px;padding:7px 0;border-top:1px solid rgba(255,255,255,.07);",main:"flex:1;min-width:0;",desc:"white-space:nowrap;overflow:hidden;text-overflow:ellipsis;",meta:"color:rgba(245,241,232,.38);font-size:11px;",qty:"width:52px;padding:4px;background:%23070F22;color:%23F5F1E8;border:1px solid rgba(255,255,255,.14);border-radius:6px;font:inherit;text-align:center;box-sizing:border-box;",ext:"width:72px;text-align:right;font-variant-numeric:tabular-nums;",actions:"display:flex;align-items:center;gap:10px;margin-top:12px;",copy:"background:%23F0B842;color:%230A1633;border:0;border-radius:7px;padding:8px 14px;font:inherit;font-weight:700;cursor:pointer;",status:"font-size:12px;",msg:"padding:6px 2px;"};_t().catch(t=>alert("Add to Procurement failed: "+(t&&t.message?t.message:t)));async function _t(){let t=document.getElementById(z);if(t){t.remove();return}let e=It();L(e,"Reading this page\u2026");try{let n=localStorage.getItem(tt)||"",o=await Q({requestor:n});if(!o.count){L(e,`No items found on this ${o.mode==="product"?"product page":"cart"} (${o.storeName}).`);return}Tt(e,o,n)}catch(n){L(e,`Couldn't scrape this page: ${n&&n.message?n.message:n}`)}}function Tt(t,e,n){t.textContent="";let o=e.rows,r=s("button",u.close,"\xD7");r.onclick=()=>document.getElementById(z)?.remove();let i=s("div",u.head);i.append(Ut(),s("span",u.count,`${e.count} item${e.count===1?"":"s"}`),s("span",u.sub,`${e.storeName} \xB7 ${e.mode}`),r);let a=s("label",u.reqLabel,"Requestor"),c=s("input",u.reqInput);c.type="text",c.placeholder="Your name",c.value=n,a.append(c);let p=s("div","");o.forEach(d=>{let g=s("div",u.main);g.append(s("div",u.desc,d.description||d.partNumber||"(no description)"),s("div",u.meta,`${d.partNumber||"\u2014"} \xB7 $${d.unitCost.toFixed(2)} ea`));let y=s("input",u.qty);y.type="number",y.min="0",y.step="1",y.value=String(d.quantity);let E=s("div",u.ext,`$${d.extendedCost.toFixed(2)}`);y.addEventListener("input",()=>{d.quantity=y.value,N(d),E.textContent=`$${d.extendedCost.toFixed(2)}`});let P=s("div",u.row);P.append(g,y,E),p.append(P)});let m=s("button",u.copy,"Copy rows");m.onmouseenter=()=>{m.style.background=v.goldHover},m.onmouseleave=()=>{m.style.background=v.gold};let f=s("span",u.status,""),M=s("div",u.actions);M.append(m,f);let q=d=>o.forEach(g=>g.requestor=d);q(n),c.addEventListener("input",()=>q(c.value.trim())),m.onclick=async()=>{let d=c.value.trim();d&&localStorage.setItem(tt,d),q(d);let g=await Rt(o);f.textContent=g?`Copied ${o.length} row${o.length===1?"":"s"} \u2014 paste into the sheet`:"Copy failed \u2014 try again",f.style.color=g?v.green:v.red},t.append(i,a,p,M)}async function Rt(t){let e=x(t);try{if(window.ClipboardItem&&navigator.clipboard&&navigator.clipboard.write)return await navigator.clipboard.write([new ClipboardItem({"text/plain":new Blob([e],{type:"text/plain"}),"text/html":new Blob([F(t)],{type:"text/html"})})]),!0}catch{}try{return await navigator.clipboard.writeText(e),!0}catch{}try{let n=document.createElement("textarea");n.value=e,n.style.cssText="position:fixed;left:-9999px;top:0",document.body.appendChild(n),n.focus(),n.select();let o=document.execCommand("copy");return n.remove(),o}catch{return!1}}function It(){let t=s("div","position:fixed;inset:auto 16px 16px auto;z-index:2147483647;margin:0;padding:0;border:0;background:transparent;max-width:92vw;");t.id=z;let e=t.attachShadow({mode:"open"}),n=s("div",u.card);if(e.append(n),(document.body||document.documentElement).append(t),typeof t.showPopover=="function"){t.setAttribute("popover","manual");try{t.showPopover()}catch{}}return n}function L(t,e){t.textContent="",t.append(s("div",u.msg,e))}function s(t,e,n){let o=document.createElement(t);return e&&(o.style.cssText=e),n!=null&&(o.textContent=n),o}function Ut(){let t=s("span",u.mark);t.innerHTML=X;let e=t.querySelector("svg");return e&&(e.setAttribute("width","16"),e.setAttribute("height","16"),e.style.display="block"),t}})();