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

10 days 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