Skip to content

Instantly share code, notes, and snippets.

@thejoester
Last active July 31, 2025 06:06
Show Gist options
  • Select an option

  • Save thejoester/b298b852b5cae2af5d8ab39416124696 to your computer and use it in GitHub Desktop.

Select an option

Save thejoester/b298b852b5cae2af5d8ab39416124696 to your computer and use it in GitHub Desktop.
FoundryVTT: Import .csv into roll table for v12 or v13
/*
******************************************************************
Macro Title: CSV-to-RollTable CSV Importer for Foundry VTT
Author: TheJoester (https://github.com/thejoester)
Last updated 30-July-2025
License: MIT License
Description:
Promts to select a .csv file, choose a result column, and
auto-generate a RollTable from its contents.
******************************************************************
*/
(async () => {
// Step 1: Initial info dialog (still fine to use confirm here)
const firstDialogResult = await new Promise(resolve => {
new foundry.applications.api.DialogV2({
window: { title: "RollTable CSV Importer", resizable: false },
content: `
<p>This macro lets you import a <strong>.csv file</strong> to create a new <strong>RollTable</strong>.</p>
<ul>
<li>You choose which column becomes the result text.</li>
<li>Each row becomes a RollTable result.</li>
<li>Weights and ranges are auto-generated.</li>
</ul>
<p>Click <strong>Import .CSV</strong> to continue.</p>
`,
buttons: [{
action: "import",
label: "Import .CSV",
default: true,
callback: () => resolve(true)
}],
submit: () => resolve(false) // fallback if user closes dialog
}).render(true);
});
if (!firstDialogResult) return;
// Step 2: Trigger file input picker for CSV file selection
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = ".csv";
fileInput.click();
// Wait for user to select a file
const file = await new Promise(resolve => {
fileInput.addEventListener("change", () => resolve(fileInput.files[0]));
});
if (!file) {
ui.notifications.warn("No file selected.");
return;
}
// Step 3: Read file contents as text and parse into lines
const text = await file.text();
const lines = text.split(/\r?\n/).filter(l => l.trim().length);
if (lines.length === 0) {
ui.notifications.error("CSV appears empty.");
return;
}
// Step 4: Grab first 3 lines as a preview sample
const sampleLines = lines.slice(0, 3).map(l => l.split(","));
const maxCols = Math.max(...sampleLines.map(row => row.length));
const headerRow = sampleLines[0];
// Step 5: Build preview + input dialog content HTML
const previewHTML = `
<div style="min-width: 600px; max-height: 70vh; overflow-y: auto; padding-right: 1em;">
<p><strong>CSV Preview (first 3 rows):</strong></p>
<div style="overflow-x: auto;">
<table class="pf2e" style="width:100%">
${sampleLines.map(row => `<tr>${row.map(cell => `<td>${cell}</td>`).join("")}</tr>`).join("")}
</table>
</div>
<div class="form-group">
<label><input type="checkbox" name="hasHeader" checked> First row is a header</label>
</div>
<div class="form-group" style="display: flex; align-items: center; gap: 0.5em;">
<label for="columnSelect" style="white-space: nowrap; width: 140px;">Select Column:</label>
<select name="columnSelect" style="flex: 1;">
${Array.from({ length: maxCols }).map((_, i) => {
const name = headerRow[i]?.trim() || `Column ${i + 1}`;
return `<option value="${i}">${name}</option>`;
}).join("")}
</select>
</div>
<div class="form-group" style="display: flex; align-items: center; gap: 0.5em;">
<label for="tableName" style="white-space: nowrap; width: 140px;">RollTable Name:</label>
<input type="text" name="tableName" value="${file.name.replace(/\.csv$/i, '')}" style="flex: 1;">
</div>
</div>
`;
// Step 6: Show dialog to choose which column to use, and name the RollTable
let selectedColumn, hasHeader, tableName;
const dialog = new foundry.applications.api.DialogV2({
window: {
title: "Import RollTable from CSV",
resizable: true
},
content: previewHTML,
buttons: [{
action: "create",
label: "Create RollTable",
default: true,
callback: (event, button, dialog) => {
const form = button.form;
selectedColumn = parseInt(form.columnSelect.value);
hasHeader = form.hasHeader.checked;
tableName = form.tableName.value.trim();
if (!tableName) {
ui.notifications.warn("Please enter a name for the RollTable.");
return false; // Prevent dialog from closing
}
return "create";
}
}, {
action: "cancel",
label: "Cancel"
}],
// Step 7: On submit, build and create the RollTable if confirmed
submit: async result => {
if (result !== "create") {
ui.notifications.info("RollTable creation cancelled.");
return;
}
// Parse full CSV into rows
const dataRows = lines.map(l => l.split(","));
if (hasHeader) dataRows.shift();
// Extract result text from selected column, build RollTableResult[] array
const rollResults = dataRows
.map(row => row[selectedColumn]?.trim())
.filter(text => text?.length > 0)
.map((text, idx) => ({
_id: foundry.utils.randomID(),
type: 0, // Text result
text,
img: "icons/svg/d20.svg",
weight: 1,
range: [idx + 1, idx + 1],
drawn: false
}));
if (rollResults.length === 0) {
ui.notifications.error("No valid rows found to import.");
return;
}
// Create the RollTable document with generated entries
await RollTable.create({
name: tableName,
results: rollResults,
formula: `1d${rollResults.length}`
});
ui.notifications.info(`Created RollTable "${tableName}" with ${rollResults.length} results.`);
}
});
dialog.render(true);
})();
@thejoester
Copy link
Author

image image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment