Microsoft Project Import And Export
Live example
Section titled “Live example”Import the sample Microsoft Project XML into the grid, export it again, and inspect conversion diagnostics. File conversion runs locally in your browser.
Source code
import { defineCustomElements } from '@revolist/revogrid/loader';
import { GanttPlugin } from '@revolist/gantt';
import { currentTheme } from '../composables/useRandomData';
import { createMicrosoftProjectView, describeMicrosoftProject, downloadMicrosoftProject, importMicrosoftProjectFile, importSampleProject, microsoftProjectColumns, microsoftProjectError } from './gantt-microsoft-project-data';
import './gantt-microsoft-project.css';
defineCustomElements();
export function load(parentSelector: string) {
const parent = document.querySelector(parentSelector);
if (!parent) return () => {};
const root = document.createElement('section');
root.className = 'gantt-msp-demo';
root.innerHTML = `<div class="gantt-msp-controls">
<button type="button" data-action="sample">Import sample XML</button>
<label>Import XML or MPP <input type="file" accept=".xml,.mpp" /></label>
<button type="button" data-action="export">Export current XML</button>
</div><p>Edit a task, then export the current project as Microsoft Project XML. Native MPP files can be imported; exports use XML.</p>
<div class="gantt-msp-feedback" aria-live="polite"><p data-status></p><p class="gantt-msp-error" role="alert" hidden></p><ul aria-label="Conversion warnings"></ul></div>`;
const sampleButton = root.querySelector<HTMLButtonElement>('[data-action="sample"]')!;
const exportButton = root.querySelector<HTMLButtonElement>('[data-action="export"]')!;
const input = root.querySelector<HTMLInputElement>('input')!;
const status = root.querySelector<HTMLElement>('[data-status]')!;
const error = root.querySelector<HTMLElement>('[role="alert"]')!;
const warnings = root.querySelector<HTMLUListElement>('ul')!;
const grid = document.createElement('revo-grid');
grid.className = 'gantt-msp-grid';
grid.theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
grid.hideAttribution = true;
grid.plugins = [GanttPlugin];
grid.columns = microsoftProjectColumns;
root.append(grid);
parent.append(root);
function showWarnings(items: readonly { message: string }[]) {
warnings.replaceChildren(...items.map((warning) => {
const item = document.createElement('li'); item.textContent = warning.message; return item;
}));
warnings.hidden = !items.length;
}
function apply(result: ReturnType<typeof importSampleProject>, filename?: string) {
const view = createMicrosoftProjectView(result);
// Reset the project config before replacing its collections, matching the import plugin.
grid.gantt = undefined;
grid.ganttDependencies = view.dependencies; grid.ganttCalendars = view.calendars;
grid.ganttResources = view.resources; grid.ganttAssignments = view.assignments; grid.ganttBaselines = view.baselines;
grid.gantt = view.gantt;
grid.source = view.tasks;
status.textContent = describeMicrosoftProject(result, filename); showWarnings(result.warnings);
error.hidden = true; error.textContent = '';
}
function setBusy(value: boolean) { sampleButton.disabled = exportButton.disabled = input.disabled = value; }
function showError(cause: unknown) { error.textContent = microsoftProjectError(cause); error.hidden = false; }
sampleButton.onclick = () => apply(importSampleProject());
input.onchange = async () => {
const file = input.files?.[0]; if (!file) return;
setBusy(true); error.hidden = true;
try { const result = await importMicrosoftProjectFile(file); if (root.isConnected) apply(result, file.name); }
catch (cause) { if (root.isConnected) showError(cause); }
finally { setBusy(false); input.value = ''; }
};
exportButton.onclick = async () => {
setBusy(true); error.hidden = true;
try { const result = await downloadMicrosoftProject(grid); showWarnings(result.warnings); status.textContent = 'Exported the current project as Microsoft Project XML.'; }
catch (cause) { showError(cause); }
finally { setBusy(false); }
};
apply(importSampleProject());
return () => { grid.remove(); root.remove(); };
}
import { createDefaultTaskTableColumn, type GanttPluginConfig, type ProjectSnapshot } from '@revolist/gantt';
import { getGanttScheduleSnapshot } from './recipes/current-schedule-snapshot';
import { exportMicrosoftProjectXml, importMicrosoftProjectFile, importMicrosoftProjectXml, type GanttMspImportResult } from '@revolist/revogrid-gantt-msp';
// A small, authored MSPDI document. The example runs the real converter on load.
export const microsoftProjectSampleXml = `<?xml version="1.0" encoding="UTF-8"?>
<Project xmlns="http://schemas.microsoft.com/project/2003">
<Title>Website launch — Microsoft Project import</Title>
<Tasks>
<Task><UID>1</UID><Name>Website launch</Name><Summary>1</Summary><OutlineLevel>1</OutlineLevel>
<Start>2026-04-06T09:00:00</Start><Finish>2026-04-17T17:00:00</Finish><Duration>PT80H0M0S</Duration><PercentComplete>40</PercentComplete></Task>
<Task><UID>2</UID><Name>Design</Name><OutlineLevel>2</OutlineLevel>
<Start>2026-04-06T09:00:00</Start><Finish>2026-04-08T17:00:00</Finish><Duration>PT24H0M0S</Duration><PercentComplete>100</PercentComplete>
<Notes>Imported task names, progress, hierarchy and dates are editable.</Notes></Task>
<Task><UID>3</UID><Name>Build</Name><OutlineLevel>2</OutlineLevel>
<Start>2026-04-09T09:00:00</Start><Finish>2026-04-16T17:00:00</Finish><Duration>PT48H0M0S</Duration><PercentComplete>25</PercentComplete>
<PredecessorLink><PredecessorUID>2</PredecessorUID><Type>1</Type></PredecessorLink>
<Baseline><Number>0</Number><Start>2026-04-09T09:00:00</Start><Finish>2026-04-15T17:00:00</Finish><Duration>PT40H0M0S</Duration></Baseline></Task>
<Task><UID>4</UID><Name>Launch</Name><Milestone>1</Milestone><OutlineLevel>2</OutlineLevel>
<Start>2026-04-17T09:00:00</Start><Finish>2026-04-17T09:00:00</Finish><Duration>PT0H0M0S</Duration><PercentComplete>0</PercentComplete>
<PredecessorLink><PredecessorUID>3</PredecessorUID><Type>1</Type></PredecessorLink></Task>
</Tasks>
</Project>`;
export const microsoftProjectColumns = (['name', 'startDate', 'endDate', 'percentDone'] as const).map((prop) => createDefaultTaskTableColumn(prop));
export function importSampleProject() {
return importMicrosoftProjectXml(microsoftProjectSampleXml, { updatedAt: '2026-04-06T00:00:00Z' });
}
export function createMicrosoftProjectView(result: GanttMspImportResult) {
const project = result.project;
const gantt: GanttPluginConfig = {
id: project.id, name: project.name, version: project.version, currency: project.currency,
timeZone: project.timeZone, primaryCalendarId: project.primaryCalendarId, updatedAt: project.updatedAt,
durationSettings: project.durationSettings, calendars: project.calendars, zoomPreset: 'day-week',
scheduling: { excludeHolidaysFromDuration: true },
};
return {
gantt,
tasks: project.tasks.map((task) => ({ ...task, percentDone: task.progressPercent })),
dependencies: [...project.dependencies], calendars: [...project.calendars],
resources: [...project.resources], assignments: [...project.assignments], baselines: [...project.baselines],
};
}
export function describeMicrosoftProject(result: GanttMspImportResult, filename = 'Sample XML') {
const calendars = result.project.calendars.length;
return `${filename}: imported ${result.project.tasks.length} tasks, ${result.project.dependencies.length} dependencies, ${calendars} ${calendars === 1 ? 'calendar' : 'calendars'}.`;
}
export { importMicrosoftProjectFile };
export function exportMicrosoftProjectSnapshot(project: ProjectSnapshot) {
return exportMicrosoftProjectXml(project);
}
export async function downloadMicrosoftProject(grid: HTMLRevoGridElement | null | undefined) {
const project = await getGanttScheduleSnapshot(grid);
const result = exportMicrosoftProjectSnapshot(project);
const url = URL.createObjectURL(new Blob([result.xml], { type: 'application/xml' }));
const link = document.createElement('a');
link.href = url;
link.download = 'gantt-microsoft-project.xml';
link.click();
setTimeout(() => URL.revokeObjectURL(url), 0);
return result;
}
export function microsoftProjectError(error: unknown) {
return error instanceof Error ? error.message : 'The Microsoft Project operation failed.';
}
<template>
<section class="gantt-msp-demo">
<div class="gantt-msp-controls">
<button type="button" :disabled="busy" @click="resetSample">Import sample XML</button>
<label>Import XML or MPP <input type="file" accept=".xml,.mpp" :disabled="busy" @change="importFile" /></label>
<button type="button" :disabled="busy" @click="exportXml">Export current XML</button>
</div>
<p>Edit a task, then export the current project as Microsoft Project XML. Native MPP files can be imported; exports use XML.</p>
<div class="gantt-msp-feedback" aria-live="polite">
<p>{{ status }}</p>
<p v-if="error" class="gantt-msp-error" role="alert">{{ error }}</p>
<ul v-if="warnings.length" aria-label="Conversion warnings"><li v-for="(warning, index) in warnings" :key="index">{{ warning.message }}</li></ul>
</div>
<RevoGrid ref="gridRef" class="gantt-msp-grid" hide-attribution :theme="isDark ? 'darkCompact' : 'compact'"
:plugins="plugins" :source="tasks" :columns="columns" :gantt.prop="view.gantt"
:gantt-dependencies.prop="view.dependencies" :gantt-calendars.prop="view.calendars"
:gantt-resources.prop="view.resources" :gantt-assignments.prop="view.assignments" :gantt-baselines.prop="view.baselines" />
</section>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import { GanttPlugin } from '@revolist/gantt';
import { currentThemeVue } from '../composables/useRandomData';
import { createMicrosoftProjectView, describeMicrosoftProject, downloadMicrosoftProject, importMicrosoftProjectFile, importSampleProject, microsoftProjectColumns, microsoftProjectError } from './gantt-microsoft-project-data';
import './gantt-microsoft-project.css';
const { isDark } = currentThemeVue();
const plugins = [GanttPlugin];
const columns = microsoftProjectColumns;
const imported = ref(importSampleProject());
const view = computed(() => createMicrosoftProjectView(imported.value));
const tasks = ref(view.value.tasks);
const warnings = ref(imported.value.warnings);
const status = ref(describeMicrosoftProject(imported.value));
const busy = ref(false);
const error = ref('');
const gridRef = ref<InstanceType<typeof RevoGrid> | HTMLRevoGridElement | null>(null);
function resetSample() {
imported.value = importSampleProject(); tasks.value = view.value.tasks;
warnings.value = imported.value.warnings; status.value = describeMicrosoftProject(imported.value); error.value = '';
}
async function importFile(event: Event) {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;
busy.value = true; error.value = '';
try {
imported.value = await importMicrosoftProjectFile(file); tasks.value = view.value.tasks;
warnings.value = imported.value.warnings; status.value = describeMicrosoftProject(imported.value, file.name);
} catch (cause) { error.value = microsoftProjectError(cause); }
finally { busy.value = false; input.value = ''; }
}
async function exportXml() {
const value = gridRef.value as (InstanceType<typeof RevoGrid> & { $el?: HTMLRevoGridElement }) | HTMLRevoGridElement | null;
const grid = value && '$el' in value ? value.$el : value;
busy.value = true; error.value = '';
try { const result = await downloadMicrosoftProject(grid as HTMLRevoGridElement); warnings.value = result.warnings; status.value = 'Exported the current project as Microsoft Project XML.'; }
catch (cause) { error.value = microsoftProjectError(cause); }
finally { busy.value = false; }
}
</script>
import React, { useMemo, useRef, useState } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { GanttPlugin } from '@revolist/gantt';
import { currentTheme } from '../composables/useRandomData';
import { createMicrosoftProjectView, describeMicrosoftProject, downloadMicrosoftProject, importMicrosoftProjectFile, importSampleProject, microsoftProjectColumns, microsoftProjectError } from './gantt-microsoft-project-data';
import './gantt-microsoft-project.css';
export default function GanttMicrosoftProject(_props: { rows?: any[] } = {}) {
const [imported, setImported] = useState(importSampleProject);
const [warnings, setWarnings] = useState(imported.warnings);
const [status, setStatus] = useState(() => describeMicrosoftProject(imported));
const [error, setError] = useState('');
const [busy, setBusy] = useState(false);
const gridRef = useRef<HTMLRevoGridElement>(null);
const plugins = useMemo(() => [GanttPlugin], []);
const view = useMemo(() => createMicrosoftProjectView(imported), [imported]);
const ganttProps = useMemo(() => ({
gantt: view.gantt, ganttDependencies: view.dependencies, ganttCalendars: view.calendars,
ganttResources: view.resources, ganttAssignments: view.assignments, ganttBaselines: view.baselines,
}), [view]);
function resetSample() {
const result = importSampleProject(); setImported(result); setWarnings(result.warnings);
setStatus(describeMicrosoftProject(result)); setError('');
}
async function importFile(event: React.ChangeEvent<HTMLInputElement>) {
const input = event.currentTarget; const file = input.files?.[0]; if (!file) return;
setBusy(true); setError('');
try { const result = await importMicrosoftProjectFile(file); setImported(result); setWarnings(result.warnings); setStatus(describeMicrosoftProject(result, file.name)); }
catch (cause) { setError(microsoftProjectError(cause)); }
finally { setBusy(false); input.value = ''; }
}
async function exportXml() {
setBusy(true); setError('');
try { const result = await downloadMicrosoftProject(gridRef.current); setWarnings(result.warnings); setStatus('Exported the current project as Microsoft Project XML.'); }
catch (cause) { setError(microsoftProjectError(cause)); }
finally { setBusy(false); }
}
return <section className="gantt-msp-demo">
<div className="gantt-msp-controls">
<button type="button" disabled={busy} onClick={resetSample}>Import sample XML</button>
<label>Import XML or MPP <input type="file" accept=".xml,.mpp" disabled={busy} onChange={importFile} /></label>
<button type="button" disabled={busy} onClick={exportXml}>Export current XML</button>
</div>
<p>Edit a task, then export the current project as Microsoft Project XML. Native MPP files can be imported; exports use XML.</p>
<div className="gantt-msp-feedback" aria-live="polite">
<p>{status}</p>{error && <p className="gantt-msp-error" role="alert">{error}</p>}
{warnings.length > 0 && <ul aria-label="Conversion warnings">{warnings.map((warning, index) => <li key={index}>{warning.message}</li>)}</ul>}
</div>
<RevoGrid ref={gridRef} className="gantt-msp-grid" hideAttribution theme={currentTheme().isDark() ? 'darkCompact' : 'compact'}
plugins={plugins} source={view.tasks} columns={microsoftProjectColumns} {...ganttProps} />
</section>;
}
import { ChangeDetectorRef, inject, Component, ElementRef, NO_ERRORS_SCHEMA, ViewChild, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RevoGrid } from '@revolist/angular-datagrid';
import { GanttPlugin } from '@revolist/gantt';
import { currentTheme } from '../composables/useRandomData';
import { createMicrosoftProjectView, describeMicrosoftProject, downloadMicrosoftProject, importMicrosoftProjectFile, importSampleProject, microsoftProjectColumns, microsoftProjectError } from './gantt-microsoft-project-data';
@Component({
selector: 'gantt-microsoft-project', standalone: true, schemas: [NO_ERRORS_SCHEMA], imports: [CommonModule, RevoGrid],
styleUrls: ['./gantt-microsoft-project.css'], encapsulation: ViewEncapsulation.None,
template: `<section class="gantt-msp-demo">
<div class="gantt-msp-controls">
<button type="button" [disabled]="busy" (click)="resetSample()">Import sample XML</button>
<label>Import XML or MPP <input type="file" accept=".xml,.mpp" [disabled]="busy" (change)="importFile($event)" /></label>
<button type="button" [disabled]="busy" (click)="exportXml()">Export current XML</button>
</div>
<p>Edit a task, then export the current project as Microsoft Project XML. Native MPP files can be imported; exports use XML.</p>
<div class="gantt-msp-feedback" aria-live="polite">
<p>{{ status }}</p><p *ngIf="error" class="gantt-msp-error" role="alert">{{ error }}</p>
<ul *ngIf="warnings.length" aria-label="Conversion warnings"><li *ngFor="let warning of warnings">{{ warning.message }}</li></ul>
</div>
<revo-grid #grid class="gantt-msp-grid" [theme]="theme" [hideAttribution]="true" [plugins]="plugins"
[source]="view.tasks" [columns]="columns" [gantt]="view.gantt" [ganttDependencies]="view.dependencies"
[ganttCalendars]="view.calendars" [ganttResources]="view.resources" [ganttAssignments]="view.assignments" [ganttBaselines]="view.baselines"></revo-grid>
</section>`,
})
export class GanttMicrosoftProjectComponent {
private readonly changeDetector = inject(ChangeDetectorRef);
@ViewChild('grid', { read: ElementRef }) gridRef?: ElementRef<HTMLRevoGridElement>;
theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
plugins = [GanttPlugin]; columns = microsoftProjectColumns;
imported = importSampleProject(); view = createMicrosoftProjectView(this.imported);
warnings = this.imported.warnings; status = describeMicrosoftProject(this.imported); error = ''; busy = false;
resetSample() {
this.imported = importSampleProject(); this.view = createMicrosoftProjectView(this.imported);
this.warnings = this.imported.warnings; this.status = describeMicrosoftProject(this.imported); this.error = '';
}
async importFile(event: Event) {
const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (!file) return;
this.busy = true; this.error = '';
try {
this.imported = await importMicrosoftProjectFile(file); this.view = createMicrosoftProjectView(this.imported);
this.warnings = this.imported.warnings; this.status = describeMicrosoftProject(this.imported, file.name);
} catch (cause) { this.error = microsoftProjectError(cause); }
finally { this.busy = false; input.value = ''; this.changeDetector.markForCheck(); }
}
async exportXml() {
this.busy = true; this.error = '';
try { const result = await downloadMicrosoftProject(this.gridRef?.nativeElement); this.warnings = result.warnings; this.status = 'Exported the current project as Microsoft Project XML.'; }
catch (cause) { this.error = microsoftProjectError(cause); }
finally { this.busy = false; this.changeDetector.markForCheck(); }
}
}
Microsoft Project conversion is shipped as an optional package so applications that do not exchange Project files do not include the XML or native MPP parser.
pnpm add @revolist/revogrid-gantt-mspInstall the plugin
Section titled “Install the plugin”Register GanttMspImportPlugin beside GanttPlugin from @revolist/gantt:
import { GanttPlugin } from '@revolist/gantt';import { GanttMspImportPlugin } from '@revolist/revogrid-gantt-msp';
grid.plugins = [GanttPlugin, GanttMspImportPlugin];Get the installed converter and import a file selected by the user:
const plugins = await grid.getPlugins();const converter = plugins.find( (plugin): plugin is GanttMspImportPlugin => plugin instanceof GanttMspImportPlugin,);
const result = await converter?.importFile(file, { projectId: 'customer-rollout', currency: 'EUR', timeZone: 'Europe/Lisbon',});
console.table(result?.warnings);The plugin applies imported tasks and project collections through the same reactive properties used by GanttPlugin. It returns the complete ProjectSnapshot as well, allowing the application to persist or inspect the imported data.
Convert without a grid
Section titled “Convert without a grid”The framework-neutral conversion functions can be used in upload handlers, persistence services, tests, and export actions:
import { exportMicrosoftProjectXml, importMicrosoftProjectMpp, importMicrosoftProjectXml,} from '@revolist/revogrid-gantt-msp';
const { project, warnings } = importMicrosoftProjectXml(xml, { projectId: 'customer-rollout',});
const exported = exportMicrosoftProjectXml(project);downloadXml(exported.xml);
const native = await importMicrosoftProjectMpp(await mppFile.arrayBuffer(), { projectId: 'native-customer-rollout',});File support
Section titled “File support”| Format | Import | Export | Notes |
|---|---|---|---|
Microsoft Project XML (.xml) | Yes | Yes | Full initial XML mapping. |
MPP14 (.mpp) | Yes | No | Native format produced by Project 2010 and later. |
| MPP8, MPP9, or MPP12 | No | No | Returns unsupported-mpp-version. |
| Encrypted MPP | No | No | Returns encrypted-mpp-not-supported. |
importFile() detects the OLE compound-document signature rather than trusting the extension. Browser File objects already provide the required arrayBuffer() method. A custom .mpp file-like object without binary access returns binary-reader-unavailable, and invalid native content returns invalid-mpp. Custom XML-only file-like objects can continue implementing only name and text().
Supported XML data
Section titled “Supported XML data”The adapter maps:
- task hierarchy, names, dates, durations, and notes;
- tasks, summary tasks, and milestones;
- completion progress;
- finish-to-start, start-to-start, finish-to-finish, and start-to-finish dependencies;
- the first task baseline exposed by the MSP XML converter.
The conversion result includes warnings when a safe default is introduced or a field cannot be represented. The initial adapter creates a standard Monday-Friday calendar because the underlying converter does not expose Microsoft Project calendar definitions.
Native MPP14 data
Section titled “Native MPP14 data”The native importer maps task names, hierarchy, dates, durations, summary tasks, milestones, finish-to-start, start-to-start, finish-to-finish, and start-to-finish dependencies, plus first-baseline dates. It generates WBS codes and a standard Monday-Friday calendar.
The first native release intentionally defaults progress and dependency lag to zero, and omits notes, resources, assignments, Microsoft Project calendars, and additional baselines. The returned unsupported-fields warning makes this loss explicit. Native MPP export is not supported; export XML when the resulting schedule must be opened in Microsoft Project.
The converter package ships a genuine legacy sample.mpp under examples/third-party/mpxj/ for signature and version testing. The fixture comes from MPXJ’s LGPL-2.1 test corpus and includes its upstream license and exact provenance. Because it is MPP9, importing it returns unsupported-mpp-version.