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})();