Import from Excel/CSV
While Gantt loads data as JSON, SVAR provides a visual tool to convert spreadsheet data into that JSON. The ExcelImport widget opens a modal wizard where the user uploads an Excel (.xlsx) or CSV file and maps its columns to task fields, then returns an array of task objects you pass to Gantt.
ExcelImport ships in its own package. Install it alongside Gantt:
npm install @svar-ui/svelte-excel-import
How import works
ExcelImport is a self-contained wizard that runs next to Gantt and walks the user through three stages:
- Upload — the user picks a file. Gantt reads the sheet, detects each column's type, and builds a preview.
- Mapping — each Gantt field (defined by you) is matched to a spreadsheet column. Auto-detection fills in the obvious matches; the user can adjust them.
- Result — a short summary of how many rows were imported and which were skipped.
The connection between a spreadsheet and Gantt is the fields array. Each field has an id that maps to a Gantt task property: id, text, start, end, duration, progress, type, parent, and a label shown in the mapping UI. keywords help auto-detection find the right column, and expectedType filters out values that don't fit:
const taskFields = [
{ id: "id", label: "ID", keywords: ["id", "identifier"] },
{
id: "text",
label: "Task name",
expectedType: "text",
keywords: ["name", "title", "task"],
},
{
id: "start",
label: "Start date",
expectedType: "date",
required: true,
keywords: ["start", "from", "begin"],
},
{
id: "end",
label: "End date",
expectedType: "date",
keywords: ["end", "finish", "to"],
},
{
id: "duration",
label: "Duration",
expectedType: "number",
keywords: ["duration", "length", "days"],
},
{
id: "progress",
label: "Progress",
expectedType: "number",
keywords: ["progress", "percent", "complete"],
},
{ id: "type", label: "Type", keywords: ["type", "kind"] },
{ id: "parent", label: "Parent ID", keywords: ["parent", "group"] },
];
When the user confirms, the onimport callback fires with two arguments: the parsed rows and a result summary. Each row is an object keyed by field id, with values already converted to native types. Date columns return Date objects, number columns return numbers, and so on.
Loading the result into Gantt
Map the parsed rows to task objects and assign them to the tasks state. Mapping is where you apply defaults and drop empty cells, so a partly-filled spreadsheet still produces valid tasks:
<script>
import { Gantt } from "@svar-ui/svelte-gantt";
import { ExcelImport } from "@svar-ui/svelte-excel-import";
import { Button } from "@svar-ui/svelte-core";
const taskFields = [
{ id: "id", label: "ID", keywords: ["id", "identifier"] },
{ id: "text", label: "Task name", keywords: ["name", "title", "task"] },
{ id: "start", label: "Start date", expectedType: "date", required: true },
{ id: "end", label: "End date", expectedType: "date" },
{ id: "duration", label: "Duration", expectedType: "number" },
{ id: "progress", label: "Progress", expectedType: "number" },
{ id: "type", label: "Type" },
{ id: "parent", label: "Parent ID" },
];
let showImport = $state(false);
let tasks = $state([]);
function handleTasksImport(rows, result) {
tasks = rows.map(row => {
const task = {
id: row.id,
text: row.text,
type: row.type || "task",
progress: row.progress ?? 0,
parent: row.parent || 0,
};
if (row.start instanceof Date) task.start = row.start;
if (row.end instanceof Date) task.end = row.end;
if (row.duration != null) task.duration = row.duration;
if (task.type === "summary") task.open = true;
return task;
});
showImport = false;
}
</script>
<Button type="primary" onclick={() => (showImport = true)}>
Import tasks
</Button>
<Gantt {tasks} />
{#if showImport}
<ExcelImport
fields={taskFields}
onimport={handleTasksImport}
onclose={() => (showImport = false)}
autoClose={true}
/>
{/if}
Importing links
The same wizard imports links. Render a second ExcelImport with a link-specific fields array and feed the result into the links state. A typical link spreadsheet has source/target task IDs and a connection type:
<script>
import { Gantt } from "@svar-ui/svelte-gantt";
import { ExcelImport } from "@svar-ui/svelte-excel-import";
const linkFields = [
{ id: "id", label: "ID", keywords: ["id", "identifier"] },
{
id: "source",
label: "Source task ID",
required: true,
keywords: ["source", "from", "predecessor"],
},
{
id: "target",
label: "Target task ID",
required: true,
keywords: ["target", "to", "successor"],
},
{ id: "type", label: "Type", keywords: ["type", "kind"] },
];
let showLinks = $state(false);
let links = $state([]);
function handleLinksImport(rows, result) {
links = rows
.filter(row => row.source != null && row.target != null)
.map(row => ({
id: row.id,
source: row.source,
target: row.target,
type: row.type || "e2s",
}));
showLinks = false;
}
</script>
<Gantt {links} />
{#if showLinks}
<ExcelImport
fields={linkFields}
onimport={handleLinksImport}
onclose={() => (showLinks = false)}
autoClose={true}
/>
{/if}
Improving column matching
Auto-detection is on by default and matches columns by their header text. Add keywords to a field to catch alternative or localized header names:
const taskFields = [
{ id: "text", label: "Task name", keywords: ["name", "title", "task"] },
{ id: "start", label: "Start date", keywords: ["start", "from", "begin"] },
];
To turn auto-detection off and have the user map every column manually, set autoDetection={false}:
<ExcelImport
fields={taskFields}
onimport={handleTasksImport}
autoDetection={false}
/>
Filtering rows by type
Set expectedType on a field to drop rows where the value doesn't match. This validates data without changing the output, the date field still returns Date objects; rows that aren't valid dates are skipped and listed in the result:
const taskFields = [
{ id: "text", label: "Task name" },
{ id: "start", label: "Start date", expectedType: "date" },
{ id: "duration", label: "Duration", expectedType: "number" },
];
A row with a non-date start or a non-numeric duration is excluded and reported in result.errors.
Requiring fields
Mark a field with required: true to force the user to map it. Rows where a required value is empty are skipped and reported in the result:
const taskFields = [
{ id: "text", label: "Task name", required: true },
{ id: "start", label: "Start date", required: true, expectedType: "date" },
{ id: "duration", label: "Duration", expectedType: "number" },
];
Validating the mapping
For checks beyond per-field requirements, pass a validate callback. It receives the current mappings (field id to column id) and returns errors and warnings. Errors block the import; warnings are shown but let the user continue:
<script>
function validateTasks(mappings) {
const errors = [];
if (!mappings.start) {
errors.push({
fieldId: "start",
code: "required",
message: "Start date is required",
});
}
if (mappings.type !== "milestone" && !mappings.duration && !mappings.end) {
errors.push({
fieldId: "duration",
code: "required",
message: "Map at least one of: Start date, End date, Duration",
});
}
return { valid: errors.length === 0, errors, warnings: [] };
}
</script>
<ExcelImport
fields={taskFields}
validate={validateTasks}
onimport={handleTasksImport}
/>
Handling files without a header row
By default the first row is treated as a header. For raw exports that start with data, set headerRow={false}, the first row becomes data and columns get generic labels (Column A, Column B, ...) for mapping:
<ExcelImport
fields={taskFields}
onimport={handleTasksImport}
headerRow={false}
/>
Generating task IDs
When a spreadsheet has no ID column, set generateIds={true} to assign a unique numeric id to every imported row, so Gantt can track and link tasks:
<ExcelImport
fields={taskFields}
onimport={handleTasksImport}
generateIds={true}
/>
Skipping the result screen
Set autoClose={true} to close the wizard the moment the import completes, skipping the summary screen (useful when you show your own confirmation):
<ExcelImport
fields={taskFields}
onimport={handleTasksImport}
onclose={() => (showImport = false)}
autoClose={true}
/>
Localizing the wizard
The wizard ships with translations and is localized through the SVAR Locale provider, the same way the rest of Gantt is (see Localization). Wrap ExcelImport in Locale and pass the locale words you need:
<script>
import { Locale } from "@svar-ui/svelte-core";
import { ExcelImport } from "@svar-ui/svelte-excel-import";
import { de } from "@svar-ui/excel-import-locales";
</script>
{#if showImport}
<Locale words={de}>
<ExcelImport
fields={taskFields}
onimport={handleTasksImport}
onclose={() => (showImport = false)}
/>
</Locale>
{/if}
Related sample: Import from Excel / CSV
Related articles: