The Challenge
Qualtrics lets you export any individual response as a formatted PDF, but only one at a time. There is no native "export all as PDF" button. Every export requires navigating through the same four clicks: open the row's action menu, select Export to PDF, confirm the modal, then close the Manage Downloads dialog.
For surveys with dozens or hundreds of responses, this becomes a serious time problem:
- 50 responses requires 200+ clicks and up to 30 minutes of manual work
- PDF exports are processed server-side and queued asynchronously, so you cannot simply loop and grab files immediately
- There is no Qualtrics API endpoint for triggering individual response PDF exports
- The Manage Downloads queue accumulates old jobs and needs manual cleanup
- Interrupting the process mid-way leaves partial exports in an unknown state
This guide gives you a free three-stage browser script that handles the entire process automatically: queueing every export, downloading all the files, and cleaning up the jobs queue, without leaving the page.
The Solution
Three lightweight JavaScript scripts that run directly in your browser console. No installation, no API key, no external service. Your data never leaves your machine.
- Automatically loops every visible response row and queues a PDF export
- Opens the Manage Downloads modal and triggers all available file downloads
- Deletes completed jobs from the queue to keep it clean (optional)
- Logs every action to the console with a summary at each stage
- Handles modal timing and DOM re-renders automatically
| Stage | What it does | When to run |
|---|---|---|
| Stage 1: Queue Exports | Loops every response row and triggers Export to PDF for each | Immediately, on the Data & Analytics page |
| Stage 2: Download Files | Opens Manage Downloads and clicks every available download button | After a short wait for Qualtrics to process the exports |
| Stage 3: Clean Up (optional) | Deletes all completed jobs from the Manage Downloads queue | After confirming all PDFs are in your Downloads folder |
How to Use
Watch the Demo
Stage 1: Queue PDF Exports
Navigate to the Data & Analytics Page
Open your Qualtrics survey and click the Data & Analytics tab. Make sure the response table is fully loaded and all rows are visible before running the script.
The script targets the response rows on the current page. If your survey has more responses than fit on one page, see the Pagination note below.
Open the Browser Console
Right-click anywhere on the response table and select Inspect or Inspect Element from the context menu.
In the developer tools panel that opens, click the Console tab.
Copy and Run the Stage 1 Script
Click the Copy Script button below, paste it into the console, and press Enter. The script runs automatically and you do not need to call any additional functions.
/* STAGE 1 SCRIPT PLACEHOLDER */
/**
* Qualtrics Bulk Response PDF Exporter — STAGE 1: Queue Exports
* Loops through every response row and queues a PDF export for each.
* After this completes, run Stage 2 to download the queued files.
*
* Developed by Pirai AI — piraiai.com
*/
(async () => {
// ============================================================================
// HELPERS
// ============================================================================
function waitForElement(selector, timeoutMs = 10000) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) { resolve(el); return; }
const observer = new MutationObserver(() => {
const found = document.querySelector(selector);
if (found) {
observer.disconnect();
resolve(found);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timed out waiting for: ${selector}`));
}, timeoutMs);
});
}
function findByText(text) {
return Array.from(document.querySelectorAll('*')).find(el =>
el.textContent.trim() === text && el.offsetParent !== null
) || null;
}
const sleep = ms => new Promise(r => setTimeout(r, ms));
function getActionButtons() {
return Array.from(
document.querySelectorAll('td.actions-column button[aria-label="record actions"]')
);
}
// ============================================================================
// PREFLIGHT CHECK
// ============================================================================
const initialButtons = getActionButtons();
if (initialButtons.length === 0) {
console.error('❌ No response rows found.');
console.error(' Make sure you are on the Data & Analytics page with the response table visible.');
return;
}
const totalRows = initialButtons.length;
console.log(`📊 Found ${totalRows} response rows. Queueing PDF exports...\n`);
// ============================================================================
// MAIN LOOP — Queue a PDF export for every row
// ============================================================================
let successCount = 0;
let failCount = 0;
for (let i = 0; i < totalRows; i++) {
console.log(`--- Queueing Row ${i + 1} of ${totalRows} ---`);
// Re-query each iteration — DOM re-renders after each export action.
const buttons = getActionButtons();
if (i >= buttons.length) {
console.warn(`⚠️ Row ${i + 1}: button no longer in DOM, skipping.`);
failCount++;
continue;
}
const button = buttons[i];
try {
button.scrollIntoView({ behavior: 'smooth', block: 'center' });
await sleep(300);
button.click();
console.log(` 🔘 Opened action menu`);
await sleep(1000);
// Find "Export to PDF" in the dropdown
const allOptions = Array.from(
document.querySelectorAll('[role="option"], [role="menuitem"]')
).filter(el => el.textContent.includes('PDF') && el.offsetParent !== null);
const target = findByText('Export to PDF') || allOptions[0] || null;
if (!target) {
console.warn(` ⚠️ "Export to PDF" option not found for row ${i + 1}`);
document.body.click();
await sleep(500);
failCount++;
continue;
}
target.click();
console.log(` ✅ Clicked "Export to PDF"`);
// Wait for the export confirmation modal
let exportBtn;
try {
exportBtn = await waitForElement('[data-testid="pdf-export-export-btn"]', 5000);
} catch {
console.warn(` ⚠️ Export modal did not appear for row ${i + 1}`);
const closeEl = document.querySelector('[data-testid="pdf-export-close-btn"], [aria-label="Close"]');
if (closeEl) closeEl.click();
failCount++;
continue;
}
exportBtn.click();
console.log(` 🚀 Export queued`);
await sleep(3000);
// Close the "Manage Downloads" dialog that appears after queuing
try {
const closeBtn = await waitForElement('[data-testid="pdf-export-close-btn"]', 5000);
closeBtn.click();
console.log(` ✔️ Dismissed dialog`);
} catch {
// Dialog may not appear for every row
}
successCount++;
await sleep(2000);
} catch (err) {
console.error(` ❌ Error on row ${i + 1}: ${err.message}`);
failCount++;
try {
const closeEl = document.querySelector('[data-testid="pdf-export-close-btn"], [aria-label="Close"]');
if (closeEl) closeEl.click();
else document.body.click();
} catch { /* ignore */ }
await sleep(1000);
}
}
// ============================================================================
// SUMMARY — prompt user to run Stage 2
// ============================================================================
console.log(`
╔════════════════════════════════════════════════════════════╗
║ STAGE 1 COMPLETE — PDF Exports Queued ║
╠════════════════════════════════════════════════════════════╣
║ Total rows: ${String(totalRows).padEnd(42)}║
║ Queued: ${String(successCount).padEnd(42)}║
║ Skipped/Error: ${String(failCount).padEnd(42)}║
╠════════════════════════════════════════════════════════════╣
║ ║
║ ▶ NEXT STEP: Run the Stage 2 script to download all ║
║ queued PDF files from the Manage Downloads modal. ║
║ ║
║ Paste the Stage 2 script into this console and press ║
║ Enter. Stay on this Data & Analytics page. ║
║ ║
╚════════════════════════════════════════════════════════════╝
`);
})();
Wait for the Stage 1 Summary
The script loops through every row on the page. Watch the console for per-row progress logs. When it finishes you will see a summary box:
╔══════════════════════════════════════════════════════════╗║ STAGE 1 COMPLETE — PDF Exports Queued ║╠══════════════════════════════════════════════════════════╣║ Total rows: 50 ║║ Queued: 50 ║║ Skipped/Error: 0 ║╚══════════════════════════════════════════════════════════╝
Wait for Qualtrics to Process the Exports
After Stage 1 completes, Qualtrics begins generating the PDF files on its servers. For simple surveys this typically takes a few seconds per response. For surveys with many questions or complex display logic, allow a minute or two before running Stage 2.
Stage 2: Download the Files
Copy and Run the Stage 2 Script
Paste the Stage 2 script into the same console and press Enter. The script opens the Manage Downloads modal automatically and clicks every available download button.
/* STAGE 2 SCRIPT PLACEHOLDER */
/**
* Qualtrics Bulk Response PDF Exporter — STAGE 2: Download Queued Files
* Opens the Manage Previous Downloads modal and downloads every queued PDF.
* Run this after Stage 1 has finished queueing all exports.
*
* Stay on the Data & Analytics page before running this script.
*
* Developed by Pirai AI — piraiai.com
*/
(async () => {
// ============================================================================
// HELPERS
// ============================================================================
function waitForElement(selector, timeoutMs = 10000) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) { resolve(el); return; }
const observer = new MutationObserver(() => {
const found = document.querySelector(selector);
if (found) {
observer.disconnect();
resolve(found);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timed out waiting for: ${selector}`));
}, timeoutMs);
});
}
const sleep = ms => new Promise(r => setTimeout(r, ms));
// ============================================================================
// STEP 1: Open the Export & Import menu
// ============================================================================
console.log('📋 STAGE 2: Downloading queued PDF files...\n');
const exportMenuBtn = document.querySelector('[data-testid="export-and-import-menu"]');
if (!exportMenuBtn) {
console.error('❌ Could not find the Export & Import menu button.');
console.error(' Make sure you are on the Data & Analytics page.');
return;
}
exportMenuBtn.click();
console.log('✅ Opened Export & Import menu');
await sleep(1000);
// ============================================================================
// STEP 2: Click "Manage Previous Downloads..."
// ============================================================================
const manageOption = Array.from(document.querySelectorAll('*')).find(el =>
el.textContent.trim() === 'Manage Previous Downloads...' && el.offsetParent !== null
);
if (!manageOption) {
console.error('❌ Could not find "Manage Previous Downloads..." option.');
document.body.click(); // close menu
return;
}
manageOption.click();
console.log('✅ Clicked "Manage Previous Downloads..."');
// ============================================================================
// STEP 3: Wait for the Manage Downloads modal
// ============================================================================
try {
await waitForElement('#pdf-export-modal', 15000);
console.log('📊 Manage Downloads modal opened\n');
} catch {
console.error('❌ Manage Downloads modal did not appear within 15 seconds.');
console.error(' The exports from Stage 1 may still be processing. Wait a moment and try again.');
return;
}
await sleep(1000); // let the modal fully render
// ============================================================================
// STEP 4: Find all download buttons in the modal
// ============================================================================
const downloadButtons = Array.from(
document.querySelectorAll('[data-testid^="pdf-export-download-button-"]')
);
if (downloadButtons.length === 0) {
console.warn('⚠️ No download buttons found in the modal.');
console.warn(' The exports may still be processing. Wait a few moments and run Stage 2 again.');
return;
}
console.log(`📁 Found ${downloadButtons.length} files ready to download\n`);
// ============================================================================
// STEP 5: Click each download button
// ============================================================================
let successCount = 0;
let failCount = 0;
for (let i = 0; i < downloadButtons.length; i++) {
const button = downloadButtons[i];
console.log(`--- Downloading file ${i + 1} of ${downloadButtons.length} ---`);
try {
button.scrollIntoView({ behavior: 'smooth', block: 'center' });
await sleep(300);
button.click();
console.log(` ⬇️ Download triggered`);
successCount++;
await sleep(1000);
} catch (err) {
console.error(` ❌ Error downloading file ${i + 1}: ${err.message}`);
failCount++;
}
}
// ============================================================================
// STEP 6: Close the modal
// ============================================================================
await sleep(500);
try {
const closeBtn = document.querySelector('[data-testid="pdf-export-close-btn"]');
if (closeBtn) {
closeBtn.click();
console.log('\n✅ Closed Manage Downloads modal');
}
} catch { /* ignore */ }
// ============================================================================
// SUMMARY
// ============================================================================
console.log(`
╔════════════════════════════════════════════════════════════╗
║ STAGE 2 COMPLETE — Downloads Triggered ║
╠════════════════════════════════════════════════════════════╣
║ Files found: ${String(downloadButtons.length).padEnd(42)}║
║ Triggered: ${String(successCount).padEnd(42)}║
║ Errors: ${String(failCount).padEnd(42)}║
╠════════════════════════════════════════════════════════════╣
║ ║
║ 📁 Check your Downloads folder for the PDF files. ║
║ ║
║ If some exports were still processing when Stage 2 ran, ║
║ wait a moment and run Stage 2 again to pick them up. ║
║ ║
╚════════════════════════════════════════════════════════════╝
`);
})();
Confirm Downloads
When Stage 2 finishes you will see a summary in the console. Open your browser's Downloads folder and confirm the PDF files are there.
Files are named by Qualtrics using the response ID, for example: Response_Report_R_d4HCfnbqgWogD54.pdf. Each filename maps directly back to a row in the Data & Analytics table.
Stage 3: Clean Up Jobs (Optional)
Run the Stage 3 Script
Stage 3 is optional but recommended. Qualtrics keeps completed export jobs in the Manage Downloads queue indefinitely. Running Stage 3 deletes all jobs, keeping the queue clean for next time.
If the Manage Downloads modal is still open from Stage 2, Stage 3 will use it directly. If you closed it, the script reopens it automatically.
/* STAGE 3 SCRIPT PLACEHOLDER */
/**
* Qualtrics Bulk Response PDF Exporter — STAGE 3: Delete Queued Jobs
* Deletes all Response Report jobs from the Manage Downloads modal.
* Run this after Stage 2 has finished downloading all PDFs.
*
* Can be run with the modal already open (faster), or from scratch —
* the script will open the modal automatically if it is not visible.
*
* Developed by Pirai AI — piraiai.com
*/
(async () => {
// ============================================================================
// HELPERS
// ============================================================================
function waitForElement(selector, timeoutMs = 10000) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) { resolve(el); return; }
const observer = new MutationObserver(() => {
const found = document.querySelector(selector);
if (found) {
observer.disconnect();
resolve(found);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timed out waiting for: ${selector}`));
}, timeoutMs);
});
}
const sleep = ms => new Promise(r => setTimeout(r, ms));
// ============================================================================
// STEP 1: Ensure the Manage Downloads modal is open
// ============================================================================
console.log('🗑️ STAGE 3: Deleting all downloaded jobs...\n');
const modalAlreadyOpen = !!document.querySelector('#pdf-export-modal');
if (!modalAlreadyOpen) {
console.log(' Modal not open — opening via Export & Import menu...');
const exportMenuBtn = document.querySelector('[data-testid="export-and-import-menu"]');
if (!exportMenuBtn) {
console.error('❌ Could not find the Export & Import menu button.');
console.error(' Make sure you are on the Data & Analytics page.');
return;
}
exportMenuBtn.click();
await sleep(1000);
const manageOption = Array.from(document.querySelectorAll('*')).find(el =>
el.textContent.trim() === 'Manage Previous Downloads...' && el.offsetParent !== null
);
if (!manageOption) {
console.error('❌ Could not find "Manage Previous Downloads..." option.');
document.body.click();
return;
}
manageOption.click();
try {
await waitForElement('#pdf-export-modal', 15000);
console.log(' ✅ Manage Downloads modal opened\n');
} catch {
console.error('❌ Manage Downloads modal did not appear.');
return;
}
await sleep(800);
} else {
console.log(' ✅ Modal already open\n');
}
// ============================================================================
// STEP 2: Count rows before starting
// ============================================================================
// Fresh count from live DOM — always re-query, never cache across deletions.
function getDeleteButtons() {
return Array.from(
document.querySelectorAll('#pdf-export-modal tr.pdf-export-row button[aria-label="Delete Job"]')
).filter(btn => btn.offsetParent !== null);
}
const initialCount = getDeleteButtons().length;
if (initialCount === 0) {
console.log('ℹ️ No jobs found in the Manage Downloads modal. Nothing to delete.');
return;
}
console.log(`📋 Found ${initialCount} job(s) to delete.\n`);
// ============================================================================
// STEP 3: Delete each job one at a time
//
// After each deletion the row is removed from the DOM, so the index always
// stays at 0 — we always delete the first remaining button.
// ============================================================================
let deletedCount = 0;
let failCount = 0;
for (let i = 0; i < initialCount; i++) {
// Always grab the first visible delete button — list shrinks after each click.
const deleteButtons = getDeleteButtons();
if (deleteButtons.length === 0) {
console.log(` ℹ️ No more delete buttons found after ${deletedCount} deletions.`);
break;
}
const btn = deleteButtons[0];
// Extract the Response ID from the sibling type cell for logging.
const row = btn.closest('tr.pdf-export-row');
const typeCell = row ? row.querySelector('td.pdf-export-job-type') : null;
const jobLabel = typeCell ? typeCell.textContent.trim() : `Job ${i + 1}`;
console.log(`--- Deleting ${i + 1} of ${initialCount}: ${jobLabel} ---`);
try {
btn.scrollIntoView({ behavior: 'smooth', block: 'center' });
await sleep(200);
btn.click();
console.log(` 🗑️ Deleted`);
deletedCount++;
// Wait for the row to be removed from the DOM before proceeding.
await sleep(600);
} catch (err) {
console.error(` ❌ Error deleting job ${i + 1}: ${err.message}`);
failCount++;
await sleep(400);
}
}
// ============================================================================
// STEP 4: Verify and close
// ============================================================================
await sleep(500);
const remaining = getDeleteButtons().length;
if (remaining > 0) {
console.warn(`\n⚠️ ${remaining} job(s) still remain in the modal. You may need to run Stage 3 again.`);
}
// Close the modal
try {
const closeBtn = document.querySelector('[data-testid="pdf-export-close-btn"]');
if (closeBtn) {
closeBtn.click();
console.log('\n✅ Closed Manage Downloads modal');
}
} catch { /* ignore */ }
// ============================================================================
// SUMMARY
// ============================================================================
console.log(`
╔════════════════════════════════════════════════════════════╗
║ STAGE 3 COMPLETE — Jobs Deleted ║
╠════════════════════════════════════════════════════════════╣
║ Jobs found: ${String(initialCount).padEnd(42)}║
║ Deleted: ${String(deletedCount).padEnd(42)}║
║ Errors: ${String(failCount).padEnd(42)}║
║ Remaining: ${String(remaining).padEnd(42)}║
╚════════════════════════════════════════════════════════════╝
`);
})();
Confirm Deletion
The console will log each deleted job by its Response Report ID, then display a summary. The Manage Downloads modal will be empty and automatically closed.
Understanding Your Output
Each downloaded file is a self-contained PDF of a single survey response, formatted exactly as you would see if you opened the response individually in Qualtrics.
| Column | Description |
|---|---|
| Filename | Named by Qualtrics as Response_Report_R_XXXXXXXXXXXX.pdf where the suffix is the unique response ID |
| Contents | All questions and answers for that response, formatted exactly as Qualtrics renders them in the individual response view |
| Traceability | The response ID in the filename matches the Response ID column in the Data & Analytics table, making it easy to cross-reference |
What about pagination?
The Data & Analytics table shows up to 50 responses per page by default (configurable via the page size dropdown). Stage 1 only processes the rows currently visible on screen. If you have more than 50 responses, run Stage 1 on each page in turn, then run Stage 2 and Stage 3 once at the end after all pages have been queued.
Troubleshooting
Stage 1: No response rows found
- Confirm you are on the Data & Analytics tab with the response table fully loaded
- Scroll through the table to ensure all rows have rendered before running the script
- Refresh the page and try again
Stage 1: "Export to PDF" option not found for some rows
- This can occur if Qualtrics's action menu closes before the script can click it. The row will be logged as skipped.
- Rerun Stage 1; the script re-queries the DOM fresh each iteration and will attempt the skipped rows again
- Avoid interacting with the browser while Stage 1 is running
Stage 2: No download buttons found in the modal
- Qualtrics may still be processing the exports. Wait 30 to 60 seconds and run Stage 2 again.
- For large or complex surveys, processing can take several minutes. Check the Manage Downloads modal manually to see if the Download buttons have appeared yet.
Stage 2: Downloads not saving to my folder
- Check that your browser is not blocking downloads from
qualtrics.com. Look for a blocked download notification in the browser's address bar. - Temporarily disable any pop-up or download blockers and run Stage 2 again
- In Chrome, go to Settings → Privacy and Security → Site Settings → Additional permissions → Automatic downloads and allow qualtrics.com
Stage 3: Jobs not deleting or modal not opening
- Make sure you are still on the Data & Analytics page — navigating away breaks the script's ability to find the Export & Import menu
- If some jobs remain after Stage 3, run the script again — it always deletes from the top of the list and will pick up anything that was missed
Advanced Usage
If you queued exports for a large number of responses, Qualtrics may finish generating them in batches rather than all at once. Stage 2 only downloads files that have a Download button at the moment it runs. Files still processing are skipped.
You can safely run Stage 2 multiple times. Each run opens the Manage Downloads modal and downloads whatever is available. Run it once after a short wait, then again a minute later to catch any stragglers, until the summary shows no new files.
By default, Qualtrics shows 50 responses per page. To bulk export more than 50 responses:
- Run Stage 1 on page 1, wait for the summary, then navigate to page 2 and run Stage 1 again
- Repeat for each page. Qualtrics queues all jobs centrally in Manage Downloads regardless of which page they came from.
- Once all pages have been queued, run Stage 2 once (or multiple times) to download everything
- Run Stage 3 once at the end to clean up all jobs
You can also increase the page size to 100 responses via the dropdown at the top of the Data & Analytics table, which reduces the number of Stage 1 runs needed.
Qualtrics names files using the response ID (e.g. Response_Report_R_d4HCfnbqgWogD54.pdf). To link each PDF back to respondent data:
- Export your Data & Analytics data as CSV — it includes a
Response IDcolumn - Use the response ID to join the PDF filename to the corresponding row of survey data in Excel or any data tool
- This is useful for attaching individual PDFs to CRM records, case management systems, or audit packages
A few habits worth following when exporting a large number of responses:
- Do a test run on a single page with a small number of rows before committing to a full export
- Check the Manage Downloads modal manually after Stage 1 to confirm jobs are appearing before running Stage 2
- Avoid navigating away from the Data & Analytics page during any stage, as the scripts rely on remaining in this context
- Run Stage 3 only after you have verified all PDFs are saved. Deleted jobs cannot be recovered, though the underlying response data is unaffected.
- Use the browser's Downloads panel (Ctrl+J in Chrome) to monitor files as they arrive during Stage 2
Need to convert surveys into Qualtrics?
This tool automates your Qualtrics response PDF exports. If you also need to convert Word, PDF or Excel questionnaires into QSF files for Qualtrics import, Pirai AI handles that automatically across 20+ survey platforms with no manual scripting required.