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

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