Firebase Users CSV Exporter

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

Size

8.7 KB

Version

1.0.1

Created

Nov 16, 2025

Updated

about 1 month ago

1// ==UserScript==
2// @name		Firebase Users CSV Exporter
3// @description		Export all Firebase 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 loaded');
13
14    // Debounce function to avoid 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 array of objects to CSV
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 || '').replace(/"/g, '""')}"`,
37                `"${(row.created || '').replace(/"/g, '""')}"`,
38                `"${(row.signedIn || '').replace(/"/g, '""')}"`
39            ];
40            csvRows.push(values.join(','));
41        }
42        
43        return csvRows.join('\n');
44    }
45
46    // Function to download CSV
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('CSV file downloaded:', filename);
60    }
61
62    // Function to extract user data from current page
63    function extractUserDataFromPage() {
64        const rows = document.querySelectorAll('table[role="grid"][aria-label="Users"] tbody tr[role="row"]');
65        const userData = [];
66        
67        rows.forEach(row => {
68            const cells = row.querySelectorAll('td');
69            if (cells.length >= 4) {
70                userData.push({
71                    identifier: cells[0]?.textContent?.trim() || '',
72                    created: cells[2]?.textContent?.trim() || '',
73                    signedIn: cells[3]?.textContent?.trim() || ''
74                });
75            }
76        });
77        
78        console.log(`Extracted ${userData.length} users from current page`);
79        return userData;
80    }
81
82    // Function to click next page button
83    function clickNextPage() {
84        const nextButton = document.querySelector('button.mat-mdc-paginator-navigation-next:not([aria-disabled="true"])');
85        if (nextButton) {
86            nextButton.click();
87            return true;
88        }
89        return false;
90    }
91
92    // Function to wait for page to load
93    function waitForPageLoad(timeout = 5000) {
94        return new Promise((resolve) => {
95            let timeoutId;
96            const observer = new MutationObserver(debounce(() => {
97                const rows = document.querySelectorAll('table[role="grid"][aria-label="Users"] tbody tr[role="row"]');
98                if (rows.length > 0) {
99                    clearTimeout(timeoutId);
100                    observer.disconnect();
101                    resolve();
102                }
103            }, 500));
104            
105            observer.observe(document.body, {
106                childList: true,
107                subtree: true
108            });
109            
110            timeoutId = setTimeout(() => {
111                observer.disconnect();
112                resolve();
113            }, timeout);
114        });
115    }
116
117    // Main export function
118    async function exportAllUsers(button) {
119        try {
120            // Disable button and show loading state
121            button.disabled = true;
122            const originalText = button.textContent;
123            button.textContent = 'Exporting...';
124            
125            console.log('Starting export of all users...');
126            
127            const allUserData = [];
128            let pageCount = 0;
129            
130            // Extract data from first page
131            let currentPageData = extractUserDataFromPage();
132            allUserData.push(...currentPageData);
133            pageCount++;
134            
135            // Navigate through all pages
136            while (true) {
137                const hasNextPage = clickNextPage();
138                if (!hasNextPage) {
139                    console.log('No more pages to process');
140                    break;
141                }
142                
143                // Wait for next page to load
144                await waitForPageLoad();
145                
146                currentPageData = extractUserDataFromPage();
147                if (currentPageData.length === 0) {
148                    console.log('No data found on page, stopping');
149                    break;
150                }
151                
152                allUserData.push(...currentPageData);
153                pageCount++;
154                
155                button.textContent = `Exporting... (${allUserData.length} users)`;
156                
157                console.log(`Processed page ${pageCount}, total users: ${allUserData.length}`);
158            }
159            
160            // Convert to CSV and download
161            const csvContent = convertToCSV(allUserData);
162            const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
163            const filename = `firebase-users-${timestamp}.csv`;
164            
165            downloadCSV(csvContent, filename);
166            
167            // Show success message
168            button.textContent = `✓ Exported ${allUserData.length} users`;
169            setTimeout(() => {
170                button.textContent = originalText;
171                button.disabled = false;
172            }, 3000);
173            
174            console.log(`Export complete: ${allUserData.length} users exported`);
175            
176        } catch (error) {
177            console.error('Error during export:', error);
178            button.textContent = '✗ Export failed';
179            setTimeout(() => {
180                button.textContent = 'Export to CSV';
181                button.disabled = false;
182            }, 3000);
183        }
184    }
185
186    // Function to create and add the export button
187    function addExportButton() {
188        // Check if button already exists
189        if (document.getElementById('firebase-csv-export-btn')) {
190            return;
191        }
192        
193        // Find the "Add user" button
194        const addUserButton = document.querySelector('button[data-test-id="add-user-button"]');
195        if (!addUserButton) {
196            console.log('Add user button not found, will retry...');
197            return;
198        }
199        
200        console.log('Add user button found, creating export button');
201        
202        // Create export button with matching styles
203        const exportButton = document.createElement('button');
204        exportButton.id = 'firebase-csv-export-btn';
205        exportButton.className = 'mdc-button mat-mdc-button-base mdc-button--raised mat-mdc-raised-button mat-primary';
206        exportButton.style.marginLeft = '12px';
207        exportButton.innerHTML = `
208            <span class="mat-mdc-button-persistent-ripple mdc-button__ripple"></span>
209            <span class="mdc-button__label">Export to CSV</span>
210            <span class="mat-focus-indicator"></span>
211            <span class="mat-mdc-button-touch-target"></span>
212        `;
213        
214        // Add click event listener
215        exportButton.addEventListener('click', () => {
216            exportAllUsers(exportButton);
217        });
218        
219        // Insert button next to "Add user" button
220        addUserButton.parentNode.insertBefore(exportButton, addUserButton.nextSibling);
221        
222        console.log('Export button added successfully');
223    }
224
225    // Initialize the extension
226    function init() {
227        console.log('Initializing Firebase Users CSV Exporter');
228        
229        // Try to add button immediately
230        addExportButton();
231        
232        // Watch for DOM changes to add button when page loads
233        const observer = new MutationObserver(debounce(() => {
234            addExportButton();
235        }, 500));
236        
237        observer.observe(document.body, {
238            childList: true,
239            subtree: true
240        });
241        
242        console.log('Observer set up to watch for Add user button');
243    }
244
245    // Wait for page to be ready
246    if (document.readyState === 'loading') {
247        document.addEventListener('DOMContentLoaded', init);
248    } else {
249        init();
250    }
251})();
Firebase Users CSV Exporter | Robomonkey