HubSpot Contact Data Exporter

Export contact data from HubSpot board view to CSV format

Size

10.9 KB

Version

1.0.1

Created

Nov 27, 2025

Updated

16 days ago

1// ==UserScript==
2// @name		HubSpot Contact Data Exporter
3// @description		Export contact data from HubSpot board view to CSV format
4// @version		1.0.1
5// @match		https://*.app-eu1.hubspot.com/*
6// @icon		https://static.hsappstatic.net/StyleGuideUI/static-3.475/img/sprocket/favicon-32x32.png
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('HubSpot Contact Data Exporter initialized');
12
13    // Debounce function to prevent multiple rapid calls
14    function debounce(func, wait) {
15        let timeout;
16        return function executedFunction(...args) {
17            const later = () => {
18                clearTimeout(timeout);
19                func(...args);
20            };
21            clearTimeout(timeout);
22            timeout = setTimeout(later, wait);
23        };
24    }
25
26    // Function to extract contact data from board cards
27    function extractContactData() {
28        console.log('Extracting contact data from board view...');
29        const contacts = [];
30        
31        // Find all contact cards in the board view
32        const contactCards = document.querySelectorAll('[data-test-id="cdb-card"]');
33        console.log(`Found ${contactCards.length} contact cards`);
34        
35        contactCards.forEach((card, index) => {
36            try {
37                const contact = {};
38                
39                // Extract contact name from the title link
40                const nameLink = card.querySelector('[data-test-id="board-card-section-title-link"]');
41                if (nameLink) {
42                    contact.name = nameLink.textContent.trim();
43                    contact.profileUrl = nameLink.href;
44                }
45                
46                // Extract properties from the card
47                const properties = card.querySelectorAll('[data-selenium-test="card-property"]');
48                properties.forEach(prop => {
49                    const labelElement = prop.querySelector('[data-test-id="cdbc-property-label"]');
50                    const valueElement = prop.querySelector('[data-test-id="cdbc-property-value"]');
51                    
52                    if (labelElement && valueElement) {
53                        const label = labelElement.textContent.trim().replace(/\s*:\s*$/, '');
54                        const value = valueElement.textContent.trim();
55                        contact[label] = value;
56                    }
57                });
58                
59                // Extract associated company
60                const companyLink = card.querySelector('[data-test-id="crm-object-board-card-associations"] a[data-link-use="mention"]');
61                if (companyLink) {
62                    contact.company = companyLink.textContent.trim();
63                }
64                
65                // Extract last activity
66                const activitySection = card.querySelector('[data-test-id="activity-section-last-activity"]');
67                if (activitySection) {
68                    contact.lastActivity = activitySection.textContent.trim();
69                }
70                
71                // Extract column/stage name
72                const columnHeader = card.closest('[data-test-id="cdb-column"]')?.querySelector('[data-test-id="cdb-column-name"]');
73                if (columnHeader) {
74                    contact.stage = columnHeader.textContent.trim();
75                }
76                
77                if (contact.name) {
78                    contacts.push(contact);
79                    console.log(`Extracted contact ${index + 1}:`, contact);
80                }
81            } catch (error) {
82                console.error(`Error extracting contact ${index + 1}:`, error);
83            }
84        });
85        
86        return contacts;
87    }
88
89    // Function to convert data to CSV format
90    function convertToCSV(contacts) {
91        if (contacts.length === 0) {
92            console.log('No contacts to export');
93            return '';
94        }
95        
96        // Get all unique keys from all contacts
97        const allKeys = new Set();
98        contacts.forEach(contact => {
99            Object.keys(contact).forEach(key => allKeys.add(key));
100        });
101        
102        const headers = Array.from(allKeys);
103        console.log('CSV Headers:', headers);
104        
105        // Create CSV header row
106        const csvRows = [];
107        csvRows.push(headers.map(header => `"${header}"`).join(','));
108        
109        // Create CSV data rows
110        contacts.forEach(contact => {
111            const values = headers.map(header => {
112                const value = contact[header] || '';
113                // Escape quotes and wrap in quotes
114                return `"${String(value).replace(/"/g, '""')}"`;
115            });
116            csvRows.push(values.join(','));
117        });
118        
119        return csvRows.join('\n');
120    }
121
122    // Function to download CSV file
123    function downloadCSV(csvContent) {
124        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
125        const link = document.createElement('a');
126        const url = URL.createObjectURL(blob);
127        
128        const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
129        link.setAttribute('href', url);
130        link.setAttribute('download', `hubspot-contacts-${timestamp}.csv`);
131        link.style.visibility = 'hidden';
132        
133        document.body.appendChild(link);
134        link.click();
135        document.body.removeChild(link);
136        
137        console.log('CSV file downloaded successfully');
138    }
139
140    // Function to handle export button click
141    async function handleExport() {
142        console.log('Export button clicked');
143        
144        // Show loading indicator
145        const exportButton = document.getElementById('hubspot-export-button');
146        if (exportButton) {
147            exportButton.disabled = true;
148            exportButton.textContent = 'Exporting...';
149        }
150        
151        try {
152            // Extract contact data
153            const contacts = extractContactData();
154            
155            if (contacts.length === 0) {
156                alert('No contacts found to export. Please make sure you are on the board view with visible contacts.');
157                return;
158            }
159            
160            // Convert to CSV
161            const csvContent = convertToCSV(contacts);
162            
163            // Download CSV
164            downloadCSV(csvContent);
165            
166            alert(`Successfully exported ${contacts.length} contacts to CSV!`);
167        } catch (error) {
168            console.error('Error during export:', error);
169            alert('An error occurred while exporting contacts. Please check the console for details.');
170        } finally {
171            // Reset button
172            if (exportButton) {
173                exportButton.disabled = false;
174                exportButton.textContent = 'Export to CSV';
175            }
176        }
177    }
178
179    // Function to create and add export button
180    function addExportButton() {
181        // Check if button already exists
182        if (document.getElementById('hubspot-export-button')) {
183            console.log('Export button already exists');
184            return;
185        }
186        
187        // Find the toolbar where we'll add the button
188        const toolbar = document.querySelector('.Header__ToolBarWrapper-ySyVf .ButtonWrapper__FlexContainer-dgZiKX .Flex__StyledFlex-cHGzEF');
189        
190        if (!toolbar) {
191            console.log('Toolbar not found, will retry...');
192            return;
193        }
194        
195        console.log('Adding export button to toolbar');
196        
197        // Create button container to match HubSpot's style
198        const buttonContainer = document.createElement('div');
199        buttonContainer.className = 'AbstractColumn__ContentColumnStyles-gJIqob eaTVxy';
200        buttonContainer.style.marginRight = '8px';
201        
202        // Create the export button
203        const exportButton = document.createElement('button');
204        exportButton.id = 'hubspot-export-button';
205        exportButton.textContent = 'Export to CSV';
206        exportButton.setAttribute('data-fnd-button', 't');
207        exportButton.setAttribute('data-button-use', 'secondary');
208        exportButton.setAttribute('type', 'button');
209        exportButton.className = 'PrivateButton__StyledButton-eRHhiA cjYrwh';
210        exportButton.style.cssText = `
211            background-color: #ffffff;
212            border: 1px solid #cbd6e2;
213            color: #33475b;
214            padding: 8px 16px;
215            border-radius: 3px;
216            font-size: 14px;
217            font-weight: 500;
218            cursor: pointer;
219            transition: all 0.15s ease-in-out;
220        `;
221        
222        // Add hover effect
223        exportButton.addEventListener('mouseenter', () => {
224            exportButton.style.backgroundColor = '#f5f8fa';
225            exportButton.style.borderColor = '#b3c1d1';
226        });
227        
228        exportButton.addEventListener('mouseleave', () => {
229            exportButton.style.backgroundColor = '#ffffff';
230            exportButton.style.borderColor = '#cbd6e2';
231        });
232        
233        // Add click handler
234        exportButton.addEventListener('click', handleExport);
235        
236        // Create button text container to match HubSpot's structure
237        const textContainer = document.createElement('span');
238        textContainer.className = 'PrivateButton__StyledButtonTextContainer-gKiNOa kXEbeH';
239        textContainer.textContent = 'Export to CSV';
240        
241        exportButton.innerHTML = '';
242        exportButton.appendChild(textContainer);
243        
244        buttonContainer.appendChild(exportButton);
245        
246        // Insert before the "Import" button
247        const importButton = toolbar.querySelector('[data-test-id="import-button"]')?.closest('.AbstractColumn__ContentColumnStyles-gJIqob');
248        if (importButton) {
249            toolbar.insertBefore(buttonContainer, importButton);
250        } else {
251            toolbar.appendChild(buttonContainer);
252        }
253        
254        console.log('Export button added successfully');
255    }
256
257    // Function to initialize the extension
258    function init() {
259        console.log('Initializing HubSpot Contact Data Exporter...');
260        
261        // Wait for the page to load
262        if (document.readyState === 'loading') {
263            document.addEventListener('DOMContentLoaded', init);
264            return;
265        }
266        
267        // Add the export button after a short delay to ensure DOM is ready
268        setTimeout(() => {
269            addExportButton();
270        }, 2000);
271        
272        // Watch for DOM changes to re-add button if needed (e.g., after navigation)
273        const observer = new MutationObserver(debounce(() => {
274            // Only add button if we're on the board view
275            if (window.location.href.includes('/board') || window.location.href.includes('/views/')) {
276                addExportButton();
277            }
278        }, 1000));
279        
280        observer.observe(document.body, {
281            childList: true,
282            subtree: true
283        });
284        
285        console.log('HubSpot Contact Data Exporter ready');
286    }
287
288    // Start the extension
289    init();
290})();
HubSpot Contact Data Exporter | Robomonkey