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