Firebase Users CSV Exporter

Export all Firebase Authentication users to CSV with identifier, created date, and signed in date

Size

11.0 KB

Version

1.0.1

Created

Nov 29, 2025

Updated

3 months ago

1// ==UserScript==
2// @name		Firebase Users CSV Exporter
3// @description		Export all Firebase Authentication users to CSV with identifier, created date, and signed in date
4// @version		1.0.1
5// @match		https://*.console.firebase.google.com/*
6// @icon		https://www.gstatic.com/mobilesdk/240501_mobilesdk/firebase_16dp.png
7// @grant		GM.xmlhttpRequest
8// ==/UserScript==
9(function() {
10    'use strict';
11
12    console.log('Firebase Users CSV Exporter: Extension loaded');
13
14    // Debounce function to prevent multiple rapid calls
15    function debounce(func, wait) {
16        let timeout;
17        return function executedFunction(...args) {
18            const later = () => {
19                clearTimeout(timeout);
20                func(...args);
21            };
22            clearTimeout(timeout);
23            timeout = setTimeout(later, wait);
24        };
25    }
26
27    // Function to convert data to CSV format
28    function convertToCSV(data) {
29        if (data.length === 0) return '';
30        
31        const headers = ['Identifier', 'Created', 'Signed In'];
32        const csvRows = [headers.join(',')];
33        
34        for (const row of data) {
35            const values = [
36                `"${row.identifier || ''}"`,
37                `"${row.created || ''}"`,
38                `"${row.signedIn || ''}"`
39            ];
40            csvRows.push(values.join(','));
41        }
42        
43        return csvRows.join('\n');
44    }
45
46    // Function to download CSV file
47    function downloadCSV(csvContent, filename) {
48        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
49        const link = document.createElement('a');
50        const url = URL.createObjectURL(blob);
51        
52        link.setAttribute('href', url);
53        link.setAttribute('download', filename);
54        link.style.visibility = 'hidden';
55        document.body.appendChild(link);
56        link.click();
57        document.body.removeChild(link);
58        
59        console.log('Firebase Users CSV Exporter: CSV file downloaded');
60    }
61
62    // Function to extract user data from the current page
63    function extractUserDataFromPage() {
64        const users = [];
65        const rows = document.querySelectorAll('table[role="grid"] tbody tr[role="row"]');
66        
67        console.log(`Firebase Users CSV Exporter: Found ${rows.length} user rows on current page`);
68        
69        rows.forEach(row => {
70            const identifierCell = row.querySelector('td.cdk-column-identifier');
71            const createdCell = row.querySelector('td.cdk-column-created-at');
72            const signedInCell = row.querySelector('td.cdk-column-last-login');
73            
74            if (identifierCell) {
75                const identifier = identifierCell.textContent.trim();
76                const created = createdCell ? createdCell.textContent.trim() : '';
77                const signedIn = signedInCell ? signedInCell.textContent.trim() : '';
78                
79                users.push({
80                    identifier: identifier,
81                    created: created,
82                    signedIn: signedIn
83                });
84            }
85        });
86        
87        return users;
88    }
89
90    // Function to get total number of pages
91    function getTotalPages() {
92        const paginatorLabel = document.querySelector('.mat-mdc-paginator-range-label');
93        if (paginatorLabel) {
94            const text = paginatorLabel.textContent.trim();
95            // Format: "1 – 50 of 3101"
96            const match = text.match(/of\s+(\d+)/);
97            if (match) {
98                const totalUsers = parseInt(match[1]);
99                const rowsPerPageSelect = document.querySelector('.mat-mdc-paginator-page-size-select .mat-mdc-select-value-text');
100                const rowsPerPage = rowsPerPageSelect ? parseInt(rowsPerPageSelect.textContent.trim()) : 50;
101                return Math.ceil(totalUsers / rowsPerPage);
102            }
103        }
104        return 1;
105    }
106
107    // Function to click next page button
108    function clickNextPage() {
109        const nextButton = document.querySelector('button.mat-mdc-paginator-navigation-next:not(.mat-mdc-button-disabled)');
110        if (nextButton) {
111            nextButton.click();
112            return true;
113        }
114        return false;
115    }
116
117    // Function to wait for page to load
118    function waitForPageLoad(timeout = 5000) {
119        return new Promise((resolve) => {
120            let elapsed = 0;
121            const interval = 100;
122            
123            const checkLoading = setInterval(() => {
124                const rows = document.querySelectorAll('table[role="grid"] tbody tr[role="row"]');
125                if (rows.length > 0) {
126                    clearInterval(checkLoading);
127                    // Wait a bit more to ensure data is fully loaded
128                    setTimeout(resolve, 500);
129                } else if (elapsed >= timeout) {
130                    clearInterval(checkLoading);
131                    resolve();
132                }
133                elapsed += interval;
134            }, interval);
135        });
136    }
137
138    // Main export function
139    async function exportAllUsers(button) {
140        console.log('Firebase Users CSV Exporter: Starting export process');
141        
142        // Disable button and show loading state
143        const originalText = button.textContent;
144        button.disabled = true;
145        button.textContent = 'Exporting...';
146        button.style.opacity = '0.6';
147        
148        try {
149            const allUsers = [];
150            const totalPages = getTotalPages();
151            console.log(`Firebase Users CSV Exporter: Total pages to process: ${totalPages}`);
152            
153            // Extract data from current page
154            const currentPageUsers = extractUserDataFromPage();
155            allUsers.push(...currentPageUsers);
156            console.log(`Firebase Users CSV Exporter: Collected ${currentPageUsers.length} users from page 1`);
157            
158            // Navigate through all pages
159            for (let page = 2; page <= totalPages; page++) {
160                const hasNextPage = clickNextPage();
161                if (!hasNextPage) {
162                    console.log('Firebase Users CSV Exporter: No more pages available');
163                    break;
164                }
165                
166                // Wait for next page to load
167                await waitForPageLoad();
168                
169                // Extract data from this page
170                const pageUsers = extractUserDataFromPage();
171                allUsers.push(...pageUsers);
172                console.log(`Firebase Users CSV Exporter: Collected ${pageUsers.length} users from page ${page} (Total: ${allUsers.length})`);
173                
174                // Update button text with progress
175                button.textContent = `Exporting... (${allUsers.length} users)`;
176            }
177            
178            console.log(`Firebase Users CSV Exporter: Total users collected: ${allUsers.length}`);
179            
180            // Convert to CSV and download
181            const csvContent = convertToCSV(allUsers);
182            const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
183            const filename = `firebase-users-${timestamp}.csv`;
184            downloadCSV(csvContent, filename);
185            
186            // Show success message
187            button.textContent = `✓ Exported ${allUsers.length} users`;
188            button.style.backgroundColor = '#4caf50';
189            
190            // Reset button after 3 seconds
191            setTimeout(() => {
192                button.textContent = originalText;
193                button.disabled = false;
194                button.style.opacity = '1';
195                button.style.backgroundColor = '';
196            }, 3000);
197            
198        } catch (error) {
199            console.error('Firebase Users CSV Exporter: Error during export:', error);
200            button.textContent = '✗ Export failed';
201            button.style.backgroundColor = '#f44336';
202            
203            // Reset button after 3 seconds
204            setTimeout(() => {
205                button.textContent = originalText;
206                button.disabled = false;
207                button.style.opacity = '1';
208                button.style.backgroundColor = '';
209            }, 3000);
210        }
211    }
212
213    // Function to create and add the export button
214    function addExportButton() {
215        // Check if we're on the authentication users page
216        if (!window.location.href.includes('/authentication/users')) {
217            console.log('Firebase Users CSV Exporter: Not on authentication users page');
218            return;
219        }
220        
221        // Check if button already exists
222        if (document.getElementById('firebase-export-csv-button')) {
223            return;
224        }
225        
226        // Find the "Add User" button
227        const addUserButton = document.querySelector('button[data-test-id="add-user-button"]');
228        if (!addUserButton) {
229            console.log('Firebase Users CSV Exporter: Add User button not found yet');
230            return;
231        }
232        
233        console.log('Firebase Users CSV Exporter: Adding export button');
234        
235        // Create export button with matching style
236        const exportButton = document.createElement('button');
237        exportButton.id = 'firebase-export-csv-button';
238        exportButton.className = addUserButton.className;
239        exportButton.innerHTML = `
240            <span class="mat-mdc-button-persistent-ripple mdc-button__ripple"></span>
241            <span class="mdc-button__label">Export to CSV</span>
242            <span class="mat-focus-indicator"></span>
243            <span class="mat-mdc-button-touch-target"></span>
244        `;
245        exportButton.style.marginLeft = '12px';
246        
247        // Add click event listener
248        exportButton.addEventListener('click', () => exportAllUsers(exportButton));
249        
250        // Insert button next to Add User button
251        addUserButton.parentNode.insertBefore(exportButton, addUserButton.nextSibling);
252        
253        console.log('Firebase Users CSV Exporter: Export button added successfully');
254    }
255
256    // Initialize the extension
257    function init() {
258        console.log('Firebase Users CSV Exporter: Initializing');
259        
260        // Try to add button immediately
261        addExportButton();
262        
263        // Watch for DOM changes to add button when page loads
264        const debouncedAddButton = debounce(addExportButton, 500);
265        const observer = new MutationObserver(debouncedAddButton);
266        
267        observer.observe(document.body, {
268            childList: true,
269            subtree: true
270        });
271        
272        // Also check when URL changes (for SPA navigation)
273        let lastUrl = location.href;
274        new MutationObserver(() => {
275            const url = location.href;
276            if (url !== lastUrl) {
277                lastUrl = url;
278                console.log('Firebase Users CSV Exporter: URL changed, checking for button');
279                setTimeout(addExportButton, 1000);
280            }
281        }).observe(document, { subtree: true, childList: true });
282    }
283
284    // Start when DOM is ready
285    if (document.readyState === 'loading') {
286        document.addEventListener('DOMContentLoaded', init);
287    } else {
288        init();
289    }
290})();
Firebase Users CSV Exporter | Robomonkey