Vinted Browser Extension

Install this Chrome extension to import any Vinted listing into your item wizard with one click.

How It Works

1

Navigate to any Vinted item listing in Chrome

2

Click the Maison Archive extension icon and press "Import"

3

The item wizard opens pre-filled — review, adjust price, and publish

Installation Steps

1

Create extension folder

Create a folder on your computer called maison-vinted-ext

2

Save the files below

Copy each file into that folder with the exact filename shown

3

Open Chrome Extensions

Go to chrome://extensions, enable "Developer mode" (top right toggle)

4

Load the extension

Click "Load unpacked" and select your maison-vinted-ext folder

5

Sign in first

Make sure you're signed in to Maison Archive in Chrome, then open a Vinted listing and click the extension icon

Extension Files — Copy & Save

manifest.json
{
  "manifest_version": 3,
  "name": "Maison Archive — Vinted Importer",
  "version": "1.0",
  "description": "Import Vinted listings directly into Maison Archive",
  "permissions": ["activeTab", "storage", "scripting"],
  "host_permissions": ["https://www.vinted.com/*", "https://www.vinted.fr/*", "https://www.vinted.co.uk/*", "https://www.vinted.es/*", "https://www.vinted.de/*"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "content_scripts": [
    {
      "matches": ["https://www.vinted.com/items/*", "https://www.vinted.fr/items/*", "https://www.vinted.co.uk/items/*", "https://www.vinted.es/items/*", "https://www.vinted.de/items/*"],
      "js": ["content.js"]
    }
  ]
}
content.js
// Maison Archive — Vinted content script
function extractVintedData() {
  const data = {};

  // URL
  data.url = window.location.href;

  // Try JSON-LD first (most reliable)
  const jsonLd = document.querySelector('script[type="application/ld+json"]');
  if (jsonLd) {
    try {
      const parsed = JSON.parse(jsonLd.textContent);
      data.title = parsed.name || "";
      data.description = parsed.description || "";
      data.price = parsed.offers?.price || "";
      const img = parsed.image;
      data.images = Array.isArray(img) ? img : (img ? [img] : []);
    } catch(e) {}
  }

  // Fallback: scrape DOM
  if (!data.title) {
    data.title = document.querySelector('[data-testid="item-title"] h1, h1[itemprop="name"]')?.textContent?.trim() || 
                 document.querySelector('h1')?.textContent?.trim() || "";
  }
  if (!data.price) {
    data.price = document.querySelector('[data-testid="item-price"], [itemprop="price"]')?.getAttribute("content") ||
                 document.querySelector('[data-testid="item-price"]')?.textContent?.replace(/[^0-9.,]/g, "") || "";
  }

  // Brand
  const brandEl = document.querySelector('[data-testid="item-details-brand"] span, [itemprop="brand"]');
  data.brand = brandEl?.textContent?.trim() || "";

  // Size
  const sizeEl = document.querySelector('[data-testid="item-details-size"] span');
  data.size = sizeEl?.textContent?.trim() || "";

  // Color
  const colorEl = document.querySelector('[data-testid="item-details-color"] span');
  data.color = colorEl?.textContent?.trim() || "";

  // Condition
  const condEl = document.querySelector('[data-testid="item-details-condition"] span, [data-testid="status"]');
  data.condition = condEl?.getAttribute("data-testid") || condEl?.textContent?.trim() || "";

  // Images fallback
  if (!data.images || data.images.length === 0) {
    const imgs = Array.from(document.querySelectorAll('[data-testid="item-photo"] img, .item-photos img'));
    data.images = imgs.map(i => i.src || i.getAttribute("src")).filter(Boolean).slice(0, 8);
  }

  return data;
}

// Listen for message from popup
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.type === "EXTRACT") {
    sendResponse(extractVintedData());
  }
});
popup.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { width: 320px; font-family: 'Inter', system-ui, sans-serif; background: #FEFCF9; color: #0B0D10; }
    .header { background: #0B0D10; padding: 16px 20px; }
    .header h1 { font-size: 13px; font-weight: 500; letter-spacing: 0.1em; color: #C7B08B; text-transform: uppercase; }
    .header p { font-size: 11px; color: rgba(255,255,255,0.4); margin-top: 2px; }
    .body { padding: 20px; }
    #status { font-size: 13px; color: #3A3D42; margin-bottom: 16px; line-height: 1.5; }
    button { width: 100%; padding: 12px; background: #0B0D10; color: white; border: none; border-radius: 10px; font-size: 13px; cursor: pointer; letter-spacing: 0.05em; }
    button:hover { background: #23262B; }
    button:disabled { opacity: 0.5; cursor: not-allowed; }
    .success { background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 10px; padding: 12px; font-size: 12px; color: #166534; margin-bottom: 12px; }
    .error { background: #fef2f2; border: 1px solid #fecaca; border-radius: 10px; padding: 12px; font-size: 12px; color: #991b1b; margin-bottom: 12px; }
  </style>
</head>
<body>
  <div class="header">
    <h1>Maison Archive</h1>
    <p>Vinted Importer</p>
  </div>
  <div class="body">
    <p id="status">Navigate to a Vinted item listing, then click below to import it.</p>
    <div id="message"></div>
    <button id="importBtn">Import This Listing</button>
  </div>
  <script src="popup.js"></script>
</body>
</html>
popup.js
const APP_URL = "https://maison.ahurawellness.com"; // e.g. https://myapp.base44.app

document.getElementById("importBtn").addEventListener("click", async () => {
  const btn = document.getElementById("importBtn");
  const msg = document.getElementById("message");
  const status = document.getElementById("status");
  
  btn.disabled = true;
  btn.textContent = "Extracting...";
  msg.innerHTML = "";

  try {
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    
    if (!tab.url?.includes("vinted")) {
      msg.innerHTML = '<div class="error">Please navigate to a Vinted item listing first.</div>';
      btn.disabled = false;
      btn.textContent = "Import This Listing";
      return;
    }

    // Extract data from page
    const data = await chrome.tabs.sendMessage(tab.id, { type: "EXTRACT" });

    if (!data || !data.title) {
      msg.innerHTML = '<div class="error">Could not extract data. Make sure you\'re on a product page.</div>';
      btn.disabled = false;
      btn.textContent = "Import This Listing";
      return;
    }

    // Get stored auth token
    const stored = await chrome.storage.local.get("maisonToken");
    const token = stored.maisonToken;

    if (!token) {
      // Open app login to get token
      chrome.tabs.create({ url: APP_URL + "/login?ext=1" });
      msg.innerHTML = '<div class="error">Please sign in to Maison Archive first. A tab has been opened.</div>';
      btn.disabled = false;
      btn.textContent = "Import This Listing";
      return;
    }

    btn.textContent = "Sending to Maison Archive...";

    const res = await fetch(APP_URL + "/api/functions/vintedImport", {
      method: "POST",
      headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
      body: JSON.stringify(data),
    });

    const result = await res.json();

    if (result.success) {
      // Store imported data and open the admin item wizard
      // We inject a content script to set localStorage, then navigate
      chrome.scripting.executeScript({
        target: { tabId: tab.id },
        func: (data, url) => {
          localStorage.setItem("vintedImport", JSON.stringify(data));
          window.location.href = url + "/AdminItems?import=1";
        },
        args: [result.data, APP_URL],
      }).catch(() => {
        // Fallback: open new tab with data encoded in URL (basic fields only)
        chrome.tabs.create({ url: APP_URL + "/AdminItems?import=1" });
      });
      msg.innerHTML = '<div class="success">✓ Imported! The item wizard has been opened.</div>';
      status.textContent = "Item data sent to Maison Archive.";
    } else {
      throw new Error(result.error || "Unknown error");
    }
  } catch (e) {
    msg.innerHTML = '<div class="error">' + e.message + '</div>';
  }

  btn.disabled = false;
  btn.textContent = "Import This Listing";
});
Note: For icon.png, save any 48×48px PNG image as icon.png in the same folder. You can use your logo.

Auto-fill support in Admin Items

When you import from the extension, the Admin Items page will automatically detect the import data and open the wizard pre-filled. Just go to Admin → Items after clicking import in the extension.