maybe.love?
Back to Analyzer
How to Extract Your Instagram DMs

Get your spicy convos from Instagram into maybe.love for that juicy relationship tea ☕

5-minute process
Works with any Instagram DM
100% private & secure

Hey mobile scrollers! You'll need to hop on a desktop for this one.

Instagram's web version on desktop is required to run our export script. Bookmark this page and come back when you're on your computer! (Trust us, seeing your relationship analysis will be worth the device-switch drama 💅)

Why Export Your Instagram DMs?

Your DMs are a goldmine of relationship insights just waiting to be uncovered

Those late-night texts, inside jokes, and even those "we need to talk" moments? They all create patterns that reveal the true nature of your connections. At maybe.love, we analyze these patterns to help you:

  • Discover who's really putting in the effort (and who's just sending "wyd" at 2am)
  • Uncover emotional patterns you might be missing (hello, red flags!)
  • See how your communication styles match up (or hilariously clash)
  • Track how your relationship has evolved over time (from awkward to comfortable)

Instagram doesn't offer an official export option, but our method lets you grab those conversations for analysis. It's like having a relationship detective in your pocket!

Simple Export Method (No Extension Required)

Follow these steps to extract your Instagram DMs using your browser's developer console.

1Open Your Instagram DMs

  1. Go to Instagram.com (on a desktop browser)
  2. Log in to your account (we promise we can't see your password)
  3. Navigate to your Direct Messages by clicking the paper airplane icon
  4. Open the conversation you want to analyze (pick a juicy one!)

Make sure you've opened the specific conversation you want to analyze

2Open Browser Developer Console

Chrome / Edge

Press:

Ctrl+Shift+J

(Mac: Cmd+Option+J)

Firefox

Press:

Ctrl+Shift+K

(Mac: Cmd+Option+K)

Safari

First enable Developer Tools:

Safari menu > Preferences > Advanced > "Show Develop menu"

Then press:

Cmd+Option+C

Don't worry if this looks techy - you're just opening a special window where you can paste our magic script. You don't need to understand the code!

3Copy and Paste the Export Script

Important Note

Instagram's interface changes often. This script tries its best but might not perfectly capture all details like sender/time every time. Focus on getting the message text.

(() => {
  const capturedMessages = new Map(); // Use a Map to store messages, key can be a unique ID
  let messageIdCounter = 0; // To ensure unique IDs for sorting by capture order

  const updateConsoleCounter = () => {
    console.log("🟢 Captured messages: ", capturedMessages.size);
  };

  // Function to attempt parsing a Date from various timestamp formats
  const parseTimestampToDate = (timestampText) => {
    if (!timestampText || typeof timestampText !== 'string') return null;
    // Try direct parsing (e.g., "May 24 at 3:14 AM")
    let date = new Date(timestampText);
    if (!isNaN(date.getTime())) return date;

    // Add more specific regexes or parsing logic for formats like "1h", "2d", "Yesterday" if needed
    // For now, this is a basic attempt.
    return null;
  };
  
  const getMessageSender = (messageElement) => {
    // Strategy 1: Look for an image with alt text ending in "'s profile picture"
    // and traverse up to find a common ancestor, then look for a sibling with the name.
    // This is complex. Let's try simpler first.
    
    // Strategy 2: Look for a strong tag or specific span near the message text,
    // but not *within* the main text bubble itself.
    let currentElement = messageElement;
    for(let i=0; i<3; i++) { // Check current element and 2 parents
        if (!currentElement) break;
        // Look for a sibling that might be the sender name (often a div or span before the message bubble)
        const potentialSenderSibling = currentElement.previousElementSibling;
        if (potentialSenderSibling) {
            const strongSender = potentialSenderSibling.querySelector('strong');
            if (strongSender && strongSender.innerText.trim().length > 0 && strongSender.innerText.trim().length < 30) {
                return strongSender.innerText.trim();
            }
            const spanSender = potentialSenderSibling.querySelector('span[dir="auto"]');
             if (spanSender && spanSender.innerText.trim().length > 0 && spanSender.innerText.trim().length < 30 && !(/\d/.test(spanSender.innerText))) { // not a time
                return spanSender.innerText.trim();
            }
        }
        // Look within the message row for a specific sender element (less common for IG DMs)
        const senderEl = currentElement.querySelector('h3, strong'); // Example selectors
        if (senderEl && senderEl.innerText.trim().length > 0 && senderEl.innerText.trim().length < 30) {
            return senderEl.innerText.trim();
        }
        currentElement = currentElement.parentElement;
    }
    return null; // Default if not found
  };

  const getMessageTimestampText = (messageElement) => {
    // Look for a <time> element or a span that looks like a timestamp
    let currentElement = messageElement;
     for(let i=0; i<3; i++) { // Check current element and 2 parents
        if (!currentElement) break;
        const timeEl = currentElement.querySelector('time[datetime]');
        if (timeEl && timeEl.getAttribute('datetime')) {
            return new Date(timeEl.getAttribute('datetime')).toLocaleString();
        }
        // Look for spans that might contain relative time like "1h", "2d", or absolute time
        const timeSpan = Array.from(currentElement.querySelectorAll('span')).find(
            s => s.innerText.trim().length > 0 && 
                 s.innerText.trim().length < 25 && // Reasonable length for a timestamp
                 (/\d/.test(s.innerText) || /^(Seen|Sent|Delivered|Today|Yesterday)/i.test(s.innerText.trim())) &&
                 !s.closest('div[role="button"]') // Not part of a button
        );
        if (timeSpan) return timeSpan.innerText.trim();
        currentElement = currentElement.parentElement;
    }
    return null;
  };

  const getMessageText = (messageElement) => {
    // Try to find the most specific text container
    // Common pattern: text is in spans within a div with dir="auto"
    const textContainer = messageElement.querySelector('div[dir="auto"]');
    let text = '';
    if (textContainer) {
        // Concatenate all span texts within this container
        const spans = textContainer.querySelectorAll('span');
        if (spans.length > 0) {
            spans.forEach(span => { text += span.innerText + ' '; });
            text = text.trim();
        } else {
            text = textContainer.innerText.trim(); // Fallback to container's text
        }
    } else {
        // Fallback: try to get text from any span directly within the messageElement
        const directSpans = messageElement.querySelectorAll(':scope > span, :scope > div > span');
        if (directSpans.length > 0) {
             directSpans.forEach(span => { text += span.innerText + ' '; });
             text = text.trim();
        } else {
            text = messageElement.innerText.trim(); // Broadest fallback
        }
    }
    
    // Clean up common unwanted text
    text = text.replace(/\s*Edited$/i, '').trim();
    text = text.replace(/^Loading\.\.\.$/i, '').trim();
    text = text.replace(/^You sent$/i, '').trim();
    // Remove potential sender/timestamp if they got mixed in (heuristic)
    // This is tricky; for now, we rely on specific selectors above.
    
    if (text.length === 0 || text.length > 2000) return null; // Filter out empty or overly long (likely not a message)
    // Filter out very short, common UI phrases if they are the *only* text
    const uiPhrases = ['seen', 'sent', 'delivered', 'liked', 'photo', 'video', 'sticker', 'gif', 'voice message'];
    if (text.length < 20 && uiPhrases.some(p => text.toLowerCase().includes(p))) {
        // Check if it's ONLY a UI phrase
        let isOnlyUiPhrase = true;
        for(const p of uiPhrases) {
            if (text.toLowerCase() === p) break;
            if (text.toLowerCase().startsWith(p) && text.toLowerCase().length < p.length + 5) break;
        }
        if (isOnlyUiPhrase) return null;
    }
    if (/replied to you$/i.test(text) && text.length < 50) return null;


    return text;
  };


  const parseMessageElement = (el) => {
    const messageText = getMessageText(el);
    if (!messageText) return null;

    const sender = getMessageSender(el) || 'Unknown Sender';
    const timestampText = getMessageTimestampText(el) || 'Unknown Time';
    const timestampDate = parseTimestampToDate(timestampText);


    const id = `msg-${messageIdCounter++}`;
    return { id, timestampText, timestampDate, sender, text: messageText, originalOrder: capturedMessages.size };
  };

  const findAndProcessMessages = () => {
    // These selectors should target the individual message "bubble" or its immediate container
    const messageSelectors = [
        'div[role="listitem"] div[role="group"] > div > div > div[style*="max-width"]', // Often the bubble
        'div[role="row"] > div > div > div[style*="max-width"]', // Similar structure
        'div[data-testid="message-container"]',
        // Add more specific selectors if known, targeting the innermost message content wrapper
    ];
    
    let newMessagesFound = 0;
    document.querySelectorAll(messageSelectors.join(', ')).forEach(el => {
      const parsedMsg = parseMessageElement(el);
      if (parsedMsg) {
        // Use a combination of text and originalOrder for a more robust unique key
        const uniqueKey = `${parsedMsg.text.slice(0, 30)}-${parsedMsg.originalOrder}`;
        if (!capturedMessages.has(uniqueKey)) {
          capturedMessages.set(uniqueKey, parsedMsg);
          newMessagesFound++;
        }
      }
    });
    if (newMessagesFound > 0) updateConsoleCounter();
  };

  const observer = new MutationObserver(() => {
    try { findAndProcessMessages(); } 
    catch (err) { console.warn("⚠️ Error during message processing: ", err.message, err.stack); }
  });

  const findChatContainer = () => {
    const selectors = [ 'div[role="main"] div[class*="xh8yej3"]', 'main section > div:last-child', 'main'];
    for (const selector of selectors) {
      const container = document.querySelector(selector);
      if (container) return container;
    }
    return document.body;
  };

  const chatContainer = findChatContainer();
  observer.observe(chatContainer, { childList: true, subtree: true });

  console.log("👀 Script running! Scroll SLOWLY to the TOP of your conversation.");
  console.log("💡 When done, type stopCaptureAndDownload() in the console and press Enter.");
  findAndProcessMessages(); // Initial capture

  window.stopCaptureAndDownload = () => {
    console.log("🚀 Stopping capture and preparing download...");
    observer.disconnect();
    
    let messagesArray = Array.from(capturedMessages.values());
    
    // Sort by original capture order (which is reverse chronological as we scroll up)
    messagesArray.sort((a, b) => a.originalOrder - b.originalOrder);
    // Now reverse it to get chronological order (oldest first)
    const finalMessages = messagesArray.reverse();

    console.log("✅ Final message count:", finalMessages.length);
    if (finalMessages.length === 0) {
        console.warn("No messages captured. Cannot download an empty file.");
        alert("No messages were captured. Please try scrolling more or check console for errors.");
        return;
    }
    
    const header = [
      "Instagram Chat Export - maybe.love",
      "Exported on: " + new Date().toLocaleString(),
      "Total messages: " + finalMessages.length,
      "-------------------------------------------",
      "NOTE: Sender/Timestamp extraction is best-effort. Please verify.",
      "Messages are ordered from oldest to newest.",
      "-------------------------------------------",
      ""
    ].join("\n");
    
    const formattedMessages = finalMessages.map(msg => {
      return `[${msg.timestampText || 'Time N/A'}] ${msg.sender || 'Sender N/A'}: ${msg.text}`;
    }).join('\n\n'); // Double newline between messages
    
    const content = header + formattedMessages;
    console.log("📄 Preparing content for download. Approx size:", content.length);
    
    try {
      const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
      console.log("📦 Blob created, size:", blob.size);
      const url = URL.createObjectURL(blob);
      console.log("🔗 Object URL created:", url);
      
      const a = document.createElement('a');
      a.style.display = 'none';
      
      let convoName = 'InstagramChat';
      try {
        // Try to get conversation name from header
        const nameEl = document.querySelector('header div[role="button"] span[dir="auto"], header h1');
        if (nameEl) convoName = nameEl.innerText.trim();
      } catch(e) { console.warn("Could not get conversation name."); }
      const safeConvoName = convoName.replace(/[^a-zA-Z0-9_\-]+/g, '_').substring(0, 50);
      
      a.download = `${safeConvoName}_${new Date().toISOString().slice(0,10)}.txt`;
      a.href = url;
      
      document.body.appendChild(a);
      console.log("🔗 Link appended. Attempting click...");
      a.click();
      console.log("🖱️ Click simulated.");
      
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      
      console.log("💾 Download should have started! Check your browser downloads.");
      alert(`Download initiated for ${finalMessages.length} messages! Check your browser downloads.`);

    } catch (e) {
      console.error("❌ Error during download process: ", e);
      alert("An error occurred during the download process. Please check the console for details.");
    }
  };
})();

This script attempts to extract detailed message info. Its success depends on Instagram's current HTML structure.

4Run the Script and Scroll

  1. Paste the script into your browser console and press Enter.

    You might see some errors about "DTSG response" or other warnings - these are normal and won't affect message capture.

  2. Scroll MANUALLY and SLOWLY up through your conversation, all the way to the very beginning.

    This is important! Scroll slowly to give the script time to find and process each message. Watch the "🟢 Captured messages" counter in the console.

  3. When you've reached the top and the message counter stops increasing, type this command in the console and press Enter:
    stopCaptureAndDownload()
  4. Save the file that automatically downloads to your computer.

Pro Tip: If the formatting isn't perfect (sender/time missing), the core message text should still be there. Instagram's structure is tricky!

Troubleshooting

  • Download not starting: Check the console for errors after running stopCaptureAndDownload(). Ensure your browser isn't blocking pop-ups or automatic downloads from the current site.
  • Few messages captured: Scroll much slower. Try refreshing the page and starting over if needed.
  • Incorrect sender/timestamp: This is the hardest part. The script makes a best guess. The text content is the priority.
  • Jumbled order: Ensure you scroll consistently from bottom to top. The script relies on capture order then reverses it.

What to Do With Your Exported Chat

Now that you've got your conversation data, let's turn it into relationship gold

Upload to maybe.love

Head back to our analyzer and paste your conversation to get those juicy insights about your relationship dynamics.

Go to Analyzer

Manual Analysis

Feeling old-school? Open the text file and look for patterns yourself. Who texts first? Who sends longer messages?

Save for Records

Keep a backup of important conversations. Perfect for when you need receipts or just want to reminisce.

Privacy Note

This method runs entirely in your browser. Your messages are captured locally and never sent to any server unless you explicitly choose to upload them to maybe.love for analysis. The download happens directly to your computer without any intermediate storage. We're big on privacy - your spicy DMs stay between you, your chat partner, and our AI (who's sworn to secrecy).

If you encounter any issues or have questions, feel free to reach out to us at contact@maybe.love