Skip to main content
Back to examples
Popular features/Export and import hub
Loading live demo…from dhtmlxcode.com
Popular features

Export and import hub

Export the chart to PDF, PNG, iCal, Excel, MS Project, or Primavera P6, and import a project back from an uploaded MS Project or Primavera file.

gantt.plugins({ export_api: true });

let SERVER = "https://export.dhtmlx.com/gantt";
let PROJECT_SERVER = "https://export.dhtmlx.com/gantt/project";

gantt.config.date_format = "%Y-%m-%d %H:%i";
gantt.config.row_height   = 32;
gantt.config.bar_height   = 22;
gantt.config.scale_height = 50;
gantt.config.auto_types   = true;
gantt.config.static_background = true;
gantt.config.min_column_width = 40;

gantt.config.columns = [
	{ name: "text", label: "Task", tree: true, width: 260, resize: true },
	{ name: "start_date", label: "Start", align: "center", width: 90, resize: true },
	{ name: "duration", label: "Duration", align: "center", width: 70, resize: true }
];

gantt.config.scales = [
	{ unit: "month", step: 1, format: "%F %Y" },
	{ unit: "day", step: 1, format: "%j" }
];

/* imported files can carry summary rows without a type: normalize on every parse */
gantt.attachEvent("onParse", function () {
	gantt.eachTask(function (task) {
		if (gantt.isSummaryTask(task)) {
			task.type = gantt.config.types.project;
		} else if (task.duration === 0 && task.type !== gantt.config.types.milestone) {
			task.type = gantt.config.types.milestone;
		}
	});
});

DHX.dd.initClickOutside();
gantt.init("gantt_here");

/* --- the seed plan (anchored to the current month, so it never looks stale) - */
function mondayOnOrAfter(d) {
	d = gantt.date.day_start(new Date(d));
	d.setDate(1);
	while (d.getDay() !== 1) d = gantt.date.add(d, 1, "day");
	return d;
}
function fmt(base, offsetDays) {
	return gantt.date.date_to_str("%Y-%m-%d %H:%i")(gantt.date.add(base, offsetDays, "day"));
}

(function seed() {
	let b = mondayOnOrAfter(new Date());
	gantt.parse({
	tasks: [
			{ id: 1, text: "Mobile App Launch", type: "project", open: true, progress: 0.46 },

			/* Discovery: three workstreams run side by side, gated by one milestone. */
			{ id: 10, text: "Discovery", type: "project", parent: 1, open: true },
			{ id: 11, text: "Market research",      parent: 10, start_date: fmt(b, 0), duration: 5,  progress: 1 },
			{ id: 12, text: "User interviews",      parent: 10, start_date: fmt(b, 0), duration: 4,  progress: 1 },
			{ id: 14, text: "Competitor analysis",  parent: 10, start_date: fmt(b, 0), duration: 4,  progress: 1 },
			{ id: 13, text: "Requirements approved", parent: 10, type: "milestone", start_date: fmt(b, 5) },

			/* Design: four parallel tracks off the same milestone. */
			{ id: 20, text: "Design", type: "project", parent: 1, open: true },
			{ id: 21, text: "Wireframes",           parent: 20, start_date: fmt(b, 5), duration: 5,  progress: 1 },
			{ id: 22, text: "Visual design",        parent: 20, start_date: fmt(b, 5), duration: 8,  progress: .8 },
			{ id: 23, text: "Design system",        parent: 20, start_date: fmt(b, 7), duration: 6,  progress: .6 },
			{ id: 24, text: "Accessibility review", parent: 20, start_date: fmt(b, 5), duration: 5,  progress: .4 },

			/* Development: setup first, then six workstreams in parallel. */
			{ id: 30, text: "Development", type: "project", parent: 1, open: true },
			{ id: 31, text: "Project setup",        parent: 30, start_date: fmt(b, 13), duration: 3,  progress: 1 },
			{ id: 32, text: "Auth & onboarding",    parent: 30, start_date: fmt(b, 16), duration: 7,  progress: .5 },
			{ id: 33, text: "Core features",        parent: 30, start_date: fmt(b, 16), duration: 12, progress: .25 },
			{ id: 34, text: "Push notifications",   parent: 30, start_date: fmt(b, 16), duration: 6,  progress: 0 },
			{ id: 35, text: "Offline sync",         parent: 30, start_date: fmt(b, 16), duration: 5,  progress: 0 },
			{ id: 36, text: "Analytics integration", parent: 30, start_date: fmt(b, 16), duration: 4,  progress: 0 },
			{ id: 37, text: "Payments integration", parent: 30, start_date: fmt(b, 16), duration: 9,  progress: .1 },

			/* QA & Testing: plan first, then five checks in parallel. */
			{ id: 40, text: "QA & Testing", type: "project", parent: 1, open: true },
			{ id: 41, text: "Test plan",            parent: 40, start_date: fmt(b, 28), duration: 3,  progress: 0 },
			{ id: 42, text: "Regression testing",   parent: 40, start_date: fmt(b, 31), duration: 6,  progress: 0 },
			{ id: 44, text: "Performance testing",  parent: 40, start_date: fmt(b, 31), duration: 4,  progress: 0 },
			{ id: 45, text: "Accessibility audit",  parent: 40, start_date: fmt(b, 31), duration: 3,  progress: 0 },
			{ id: 43, text: "Beta feedback",        parent: 40, start_date: fmt(b, 31), duration: 5,  progress: 0 },
			{ id: 46, text: "Load testing",         parent: 40, start_date: fmt(b, 31), duration: 5,  progress: 0 },

			/* Launch: three workstreams run side by side into one milestone. */
			{ id: 50, text: "Launch", type: "project", parent: 1, open: true },
			{ id: 51, text: "Store submission",     parent: 50, start_date: fmt(b, 37), duration: 4,  progress: 0 },
			{ id: 52, text: "Marketing campaign",   parent: 50, start_date: fmt(b, 37), duration: 5,  progress: 0 },
			{ id: 54, text: "Press kit",            parent: 50, start_date: fmt(b, 37), duration: 3,  progress: 0 },
			{ id: 53, text: "Go live",              parent: 50, type: "milestone", start_date: fmt(b, 42) },

			/* Post-launch: monitoring and support run side by side after go-live. */
			{ id: 60, text: "Post-launch", type: "project", parent: 1, open: true },
			{ id: 61, text: "Monitor crash reports",   parent: 60, start_date: fmt(b, 42), duration: 5, progress: 0 },
			{ id: 62, text: "Customer support triage", parent: 60, start_date: fmt(b, 42), duration: 5, progress: 0 },
			{ id: 64, text: "Usage analytics review",  parent: 60, start_date: fmt(b, 42), duration: 6, progress: 0 },
			{ id: 63, text: "Hotfix release",          parent: 60, start_date: fmt(b, 44), duration: 4, progress: 0 },
			{ id: 65, text: "Retrospective",           parent: 60, type: "milestone", start_date: fmt(b, 48) }
		],
		links: [
			{ id: 1, source: 11, target: 13, type: "0" },
			{ id: 2, source: 12, target: 13, type: "0" },
			{ id: 3, source: 14, target: 13, type: "0" },
			{ id: 4, source: 13, target: 21, type: "0" },
			{ id: 5, source: 13, target: 22, type: "0" },
			{ id: 6, source: 13, target: 23, type: "0" },
			{ id: 7, source: 13, target: 24, type: "0" },
			{ id: 8, source: 22, target: 31, type: "0" },
			{ id: 9, source: 31, target: 32, type: "0" },
			{ id: 10, source: 31, target: 33, type: "0" },
			{ id: 11, source: 31, target: 34, type: "0" },
			{ id: 12, source: 31, target: 35, type: "0" },
			{ id: 13, source: 31, target: 36, type: "0" },
			{ id: 14, source: 31, target: 37, type: "0" },
			{ id: 15, source: 33, target: 41, type: "0" },
			{ id: 16, source: 41, target: 42, type: "0" },
			{ id: 17, source: 41, target: 44, type: "0" },
			{ id: 18, source: 41, target: 45, type: "0" },
			{ id: 19, source: 41, target: 43, type: "0" },
			{ id: 20, source: 41, target: 46, type: "0" },
			{ id: 21, source: 42, target: 51, type: "0" },
			{ id: 22, source: 42, target: 52, type: "0" },
			{ id: 23, source: 42, target: 54, type: "0" },
			{ id: 24, source: 51, target: 53, type: "0" },
			{ id: 25, source: 52, target: 53, type: "0" },
			{ id: 26, source: 54, target: 53, type: "0" },
			{ id: 27, source: 53, target: 61, type: "0" },
			{ id: 28, source: 53, target: 62, type: "0" },
			{ id: 29, source: 53, target: 64, type: "0" },
			{ id: 30, source: 61, target: 63, type: "0" },
			{ id: 31, source: 62, target: 65, type: "0" },
			{ id: 32, source: 63, target: 65, type: "0" },
			{ id: 33, source: 64, target: 65, type: "0" }
		]
	});
})();

/* --- export --------------------------------------------------------------- */
let EXPORTERS = {
	ical:  { fn: "exportToICal",        name: "plan.ics",  server: SERVER },
	excel: { fn: "exportToExcel",       name: "plan.xlsx", server: SERVER },
	msp:   { fn: "exportToMSProject",   name: "plan.xml",  server: PROJECT_SERVER },
	p6:    { fn: "exportToPrimaveraP6", name: "plan.xml",  server: PROJECT_SERVER }
};

function doExport(kind) {
	DHX.dd.closeAll();
	if (!gantt.getTaskCount()) {
		gantt.message({ type: "error", text: "Nothing to export yet." });
		return;
	}

	let exporter = EXPORTERS[kind];
	gantt[exporter.fn]({
		server: exporter.server,
		name: exporter.name
	});
}

/* --- import: selected file or drag-and-drop ------------------------------- */
let selectedImportFile = null;
let importAfterPicker = false;
let importInProgress = false;
let fileInput = document.getElementById("file_input");
let uploadButton = document.getElementById("upload_file_btn");
let fileNameNode = document.getElementById("import_file_name");
let statusNode = document.getElementById("import_status");
let ganttNode = document.getElementById("gantt_here");

function fileExtension(file) {
	let parts = String(file.name || "").split(".");
	return parts.length > 1 ? parts.pop().toLowerCase() : "";
}

function setSelectedImportFile(file) {
	selectedImportFile = file || null;
	fileNameNode.textContent = file ? file.name : "No file selected";
	fileNameNode.classList.toggle("has_file", !!file);
}

function setImportStatus(text, isError) {
	statusNode.textContent = text || "";
	statusNode.classList.toggle("error", !!isError);
}

function setImportBusy(busy) {
	importInProgress = busy;
	uploadButton.disabled = busy;
	uploadButton.textContent = busy ? "Importing..." : "Upload File";
}

function chooseImportFile(autoImport) {
	if (importInProgress) {
		return;
	}

	importAfterPicker = !!autoImport;
	fileInput.value = "";
	fileInput.click();
}

function uploadSelectedFile() {
	if (importInProgress) {
		return;
	}

	if (selectedImportFile) {
		sendFile(selectedImportFile);
	} else {
		chooseImportFile(true);
	}
}

fileInput.addEventListener("change", function () {
	let file = this.files && this.files[0];

	if (!file) {
		importAfterPicker = false;
		return;
	}

	setSelectedImportFile(file);
	setImportStatus("Ready to import", false);

	if (importAfterPicker) {
		importAfterPicker = false;
		sendFile(file);
	}
});

function readFileHead(file) {
	return new Promise(function (resolve, reject) {
		let reader = new FileReader();

		reader.onload = function () {
			resolve(String(reader.result || ""));
		};
		reader.onerror = function () {
			reject(reader.error || new Error("The file could not be read."));
		};

		reader.readAsText(file.slice(0, 65536));
	});
}

function detectXmlProjectType(file) {
	return readFileHead(file).then(function (text) {
		let source = text.toLowerCase();

		if (
			source.indexOf("apibusinessobjects") !== -1 ||
			source.indexOf("xmlns.oracle.com/primavera") !== -1 ||
			source.indexOf("primavera p6") !== -1
		) {
			return "p6";
		}

		if (
			source.indexOf("schemas.microsoft.com/project") !== -1 ||
			source.indexOf("<mspdi") !== -1
		) {
			return "msp";
		}

		/* Most generic Project XML files accepted by this demo are MS Project.
		   Primavera XML exported by P6 or DHTMLX contains a Primavera namespace. */
		return "msp";
	});
}

function detectImportType(file) {
	let ext = fileExtension(file);

	if (ext === "xer") {
		return Promise.resolve("p6");
	}
	if (ext === "mpp") {
		return Promise.resolve("msp");
	}
	if (ext === "xlsx" || ext === "xls") {
		return Promise.resolve("excel");
	}
	if (ext === "xml") {
		return detectXmlProjectType(file);
	}

	// The current functionality allows importing various files that are partially supported by MPXJ
	// If the file is really not supported, the server returns an error
	// Otherwise, allow users to experiment with their files
	return Promise.resolve("p6");
	// return Promise.reject(new Error("Unrecognized file type: ." + ext));
}

function sendFile(file) {
	if (!file || importInProgress) {
		return;
	}

	setSelectedImportFile(file);
	setImportBusy(true);
	setImportStatus("Detecting file format...", false);

	detectImportType(file).then(function (kind) {
		if (kind === "excel") {
			importExcel(file);
		} else if (kind === "p6") {
			importProjectFile(file, {
				fn: "importFromPrimaveraP6",
				label: "Primavera P6",
				durationUnit: "day"
			});
		} else {
			importProjectFile(file, {
				fn: "importFromMSProject",
				label: "MS Project"
			});
		}
	}).catch(function (error) {
		setImportBusy(false);
		importFailed(error && error.message);
	});
}

function importExcel(file) {
	setImportStatus("Importing Excel...", false);

	try {
		gantt.importFromExcel({
			server: SERVER,
			data: file,
			callback: function (rows) {
				setImportBusy(false);

				if (rows && rows.length) {
					gantt.clearAll();
					gantt.parse({ tasks: excelRowsToTasks(rows), links: [] });
					afterImport("Excel");
				} else {
					importFailed();
				}
			}
		});
	} catch (error) {
		setImportBusy(false);
		importFailed(error && error.message);
	}
}

function importProjectFile(file, importer) {
	setImportStatus("Importing " + importer.label + "...", false);

	try {
		gantt[importer.fn]({
			server: PROJECT_SERVER,
			data: file,
			durationUnit: importer.durationUnit,
			callback: function (project) {
				setImportBusy(false);

				if (project && project.data) {
					gantt.clearAll();

					if (project.config && project.config.duration_unit) {
						gantt.config.duration_unit = project.config.duration_unit;
					}

					gantt.parse(project.data);
					afterImport(importer.label);
				} else {
					importFailed();
				}
			}
		});
	} catch (error) {
		setImportBusy(false);
		importFailed(error && error.message);
	}
}

function importFailed(details) {
	let message = details || "The service could not read that file. Check the format and your connection.";
	setImportStatus(message, true);
	gantt.message({ type: "error", text: DHX.ui.escape(message) });
}

function afterImport(label) {
	gantt.render();

	let first = gantt.getTaskByIndex(0);
	if (first) {
		gantt.showTask(first.id);
	}

	setImportStatus(label + " imported successfully", false);
}

/* Drop a supported file anywhere inside the Gantt to import immediately. */
let dragDepth = 0;

function containsFiles(event) {
	let types = event.dataTransfer && event.dataTransfer.types;
	return types && Array.prototype.indexOf.call(types, "Files") !== -1;
}

ganttNode.addEventListener("dragenter", function (event) {
	if (!containsFiles(event)) {
		return;
	}

	event.preventDefault();
	dragDepth++;
	ganttNode.classList.add("is_file_dragover");
});

ganttNode.addEventListener("dragover", function (event) {
	if (!containsFiles(event)) {
		return;
	}

	event.preventDefault();
	event.dataTransfer.dropEffect = "copy";
	ganttNode.classList.add("is_file_dragover");
});

ganttNode.addEventListener("dragleave", function (event) {
	if (!containsFiles(event)) {
		return;
	}

	dragDepth = Math.max(0, dragDepth - 1);
	if (!dragDepth) {
		ganttNode.classList.remove("is_file_dragover");
	}
});

ganttNode.addEventListener("drop", function (event) {
	event.preventDefault();
	dragDepth = 0;
	ganttNode.classList.remove("is_file_dragover");

	let files = event.dataTransfer && event.dataTransfer.files;
	let file = files && files[0];

	if (file) {
		setSelectedImportFile(file);
		sendFile(file);
	}
});

/* Excel rows arrive as plain objects with spreadsheet column names as keys.
   Guess which column is which gantt field from the header text, same heuristic
   as 05_import_msp_excel.html, applied straight away instead of via a dialog. */
function guessField(header) {
	let h = (header + "").toLowerCase();
	if (h.indexOf("wbs") > -1) return "wbs";
	if (h.indexOf("start") > -1 || h.indexOf("begin") > -1) return "start_date";
	if (h.indexOf("end") > -1 || h.indexOf("finish") > -1) return "end_date";
	if (h.indexOf("duration") > -1 || h.indexOf("days") > -1) return "duration";
	if (h.indexOf("parent") > -1) return "parent";
	if (h === "id" || h.indexOf("code") > -1) return "id";
	return "text";
}
function excelRowsToTasks(rows) {
	let header = Object.keys(rows[0]);
	let mapping = {};
	header.forEach(function (col) { mapping[col] = guessField(col); });

	return rows.map(function (row) {
		let task = { open: true };
		for (let col in row) {
			task[mapping[col]] = row[col];
		}
		if (task.wbs) {   // rebuild the tree from WBS codes like 2.4.1
			let parts = String(task.wbs).split(".");
			task.id = String(task.wbs);
			parts.pop();
			task.parent = parts.join(".");
		}
		return task;
	});
}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Export &amp; import hub: iCal, Excel, MS Project, Primavera P6, and other formats | dhtmlxGantt</title>
	<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
	<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
	<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
	<script src="../common/demo-controls/dhx_controls.js"></script>
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
	<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
	<style>
		body { display: flex; flex-direction: column; }
		#gantt_here {
			position: relative;
			width: 100%;
			flex: 1 1 auto;
			min-height: 0;
		}

		#gantt_here.is_file_dragover::after {
			content: "Drop the file to import it";
			position: absolute;
			inset: 10px;
			z-index: 100;
			display: flex;
			align-items: center;
			justify-content: center;
			border: 2px dashed var(--dhx-accent);
			border-radius: 12px;
			background: rgba(239, 246, 255, .92);
			color: var(--dhx-accent);
			font: 600 15px var(--dhx-font);
			pointer-events: none;
		}

		.gantt_task_line { border: none; border-radius: 7px; box-shadow: 0 1px 2px rgba(16,24,40,.18); }
		.gantt_task_line .gantt_task_progress { border-radius: 7px 0 0 7px; }
		.gantt_task_line.gantt_project { border-radius: 5px; }

		.hint { font: 500 12px var(--dhx-font); color: var(--dhx-muted); }
		.hint b { color: var(--dhx-ink-2); font-weight: 600; }

		.export_menu { min-width: 170px; padding: 6px; }

		.import_control {
			display: inline-flex;
			align-items: center;
			gap: 8px;
			min-width: 0;
		}

		.import_file {
			display: inline-flex;
			align-items: center;
			gap: 8px;
			min-width: 280px;
			max-width: 420px;
			height: 34px;
			padding: 0 6px 0 10px;
			box-sizing: border-box;
			border: 1px solid var(--dhx-line);
			border-radius: 8px;
			background: var(--dhx-surface);
		}

		.import_file__name {
			flex: 1 1 auto;
			min-width: 0;
			overflow: hidden;
			text-overflow: ellipsis;
			white-space: nowrap;
			font: 500 12px var(--dhx-font);
			color: var(--dhx-muted);
		}

		.import_file__name.has_file {
			color: var(--dhx-ink-2);
		}

		.import_file__choose {
			flex: none;
			height: 26px;
			padding: 0 9px;
			border: 0;
			border-radius: 6px;
			background: #f2f4f7;
			color: var(--dhx-ink-2);
			font: 600 11px var(--dhx-font);
			cursor: pointer;
		}

		.import_file__choose:hover {
			background: #e7eaee;
			color: var(--dhx-ink);
		}

		:root[data-gantt-theme="dark"] .import_file__choose {
			background: #323a45;
			color: var(--dhx-ink);
		}

		:root[data-gantt-theme="dark"] .import_file__choose:hover {
			background: #3c4550;
		}

		.import_status {
			max-width: 280px;
			overflow: hidden;
			text-overflow: ellipsis;
			white-space: nowrap;
			font: 500 11px var(--dhx-font);
			color: var(--dhx-muted);
		}

		.import_status.error {
			color: #c0392b;
		}

		@media (max-width: 820px) {
			.dhx_toolbar.import_toolbar {
				height: auto;
				flex-wrap: wrap;
				padding-top: 8px;
				padding-bottom: 8px;
			}

			.import_control {
				flex-wrap: wrap;
				justify-content: center;
			}

			.import_file {
				min-width: min(360px, calc(100vw - 32px));
			}
		}
	</style>
</head>
<body>

<div class="dhx_toolbar import_toolbar" style="justify-content:center; gap:10px">
	<div class="dhx_dd" id="dd_export">
		<button class="dhx_btn dhx_btn--surface" onclick="DHX.dd.toggle('dd_export')">
			Export <span class="dhx_caret"><svg class="dhx_ic dhx_ic--sm" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></span>
		</button>
		<div class="dhx_menu export_menu" id="export_menu">
			<div class="dhx_menu__item" onclick="doExport('ical')">iCal</div>
			<div class="dhx_menu__item" onclick="doExport('excel')">Excel</div>
			<div class="dhx_menu__item" onclick="doExport('msp')">MS Project</div>
			<div class="dhx_menu__item" onclick="doExport('p6')">Primavera P6</div>
		</div>
	</div>

	<div class="import_control">
		<div class="import_file" title="Excel, MS Project or Primavera P6">
			<span class="import_file__name" id="import_file_name">No file selected</span>
			<button type="button" class="import_file__choose" onclick="chooseImportFile(false)">Choose file</button>
		</div>
		<button type="button" class="dhx_btn dhx_btn--primary" id="upload_file_btn" onclick="uploadSelectedFile()">
			Upload File
		</button>
		<span class="import_status" id="import_status">or drop a file onto the Gantt</span>
		<input type="file" id="file_input" accept=".mpp,.xml,.xer,.xlsx,.xls" hidden>
	</div>
</div>

<div id="gantt_here"></div>


</body>
</html>