[Canonical page](<https://docs.dhtmlx.com/planning-engine/net/how-to/import-from-dhtmlxgantt/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Import from dhtmlxGantt

Load a dhtmlxGantt project into Planning Engine and get the same task dates the gantt auto-scheduler would give you.

Planning Engine's scheduling step is a port of the dhtmlxGantt v2 auto-scheduling engine. Its records use gantt's fields under PascalCase names (`StartDate`, `Duration`, `Parent`, `ConstraintType`, `AutoScheduling`), so most of the import copies fields one to one. The steps below cover the places where a straight copy gives different dates or fails validation.

## Prerequisites

- Your gantt tasks, links, resources and assignments are parsed into your own C# records. [`Plen.Core`](<https://docs.dhtmlx.com/planning-engine/net/api/core/overview.md>) has no JSON input. To read gantt-style JSON directly, use the separate `Plen.Wire` package. See [Exchange JSON with a client](<https://docs.dhtmlx.com/planning-engine/net/how-to/exchange-json-with-a-client.md>).
- You know each task's effective calendar, as gantt resolves it.

## Steps

1. Set the scheduling config to gantt's model. Use [`CalendarMode.Task`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/calendar-mode.md>) for task calendars only, or `CalendarMode.TaskInherited` if gantt has `inherit_calendar: true`. Copy gantt's `work_time` into [`UseCalendars`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#usecalendars>) explicitly. If you leave `UseCalendars` unset, it stays `true` and Planning Engine schedules on calendars:

   ```csharp
   var ganttWorkTime = true; // gantt `config.work_time`

   var data = new ProjectData
   {
       Config =
       {
           CalendarMode = CalendarMode.Task,
           UseCalendars = ganttWorkTime,
       },
   };
   ```

   Set [`RootId`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#rootid>) only when gantt's virtual root isn't `0`. The other auto-scheduling options ([`ApplyConstraints`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#applyconstraints>), [`GapBehavior`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#gapbehavior>), [`MoveProjects`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#moveprojects>), [`ScheduleFromEnd`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#schedulefromend>), [`ProjectStart`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#projectstart>), [`UseProgress`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#useprogress>)) are gantt's options under PascalCase names.

2. Add one [`CalendarData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/calendar-data.md>) per calendar your tasks use, and set each task's `CalendarId` to its effective calendar. gantt picks that calendar in its adapter: the task's own, else its resource's, else a calendar merged from all its resources, else the parent's. Send the result, not the rules. A merged resource calendar goes in once, under its synthetic id. Don't repeat gantt's precedence or merging in your import code.

3. Map each task to a [`TaskData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/task-data.md>):
   - gantt `type: "project"` becomes [`TaskType.Summary`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/task-data.md#tasktype>). A summary takes both `StartDate` and `EndDate`, or neither. Drop its `Duration`, or strict validation rejects the task.
   - A `Parent` equal to `RootId` means "no parent". You can copy it as is.
   - A leaf task needs two of `StartDate`, `EndDate` and `Duration`. If you set all three, they must agree.
   - If your gantt event handlers cancel auto-scheduling for a task, set `AutoScheduling = false` on it. The planner never moves it.

4. Map gantt link `type` values `"0"`, `"1"`, `"2"` and `"3"` to [`LinkType.Fs`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md>), `Ss`, `Ff` and `Sf`. Copy `lag` and leave [`LagUnit`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md#lagunit>) as `null`, because gantt measures lag in `config.duration_unit`.

5. Convert each assignment to a slice of its task's work in [`AssignmentData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/assignment-data.md>). gantt's `mode` has no counterpart, so don't carry it over.
   - `default` and `fixedDuration`: `delay` becomes [`Offset`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/assignment-data.md#offset-duration>) and `duration` stays `Duration`. Leave both `null` to book the whole task.
   - `fixedDates`: set `Offset` and `Duration` to the worked time its dates cover, measured from the task start.

   A slice ends at the task end. gantt's `fixedDuration` can run past it.

6. Solve the data with [`Project.Solve`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>) and write the changed fields from [`result.Updates`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/schedule-result.md#updates>) back into gantt. The updates include summaries, which the gantt JS adapter leaves to gantt core. Apply those too, or your summary spans go stale.

This example imports a summary with two tasks, a milestone, two links, a part-time calendar and one `default`-mode assignment:

```csharp
using System.Globalization;
using Plen;

GanttTask[] ganttTasks =
[
    new(1, 0, "project", "2026-03-02 00:00", "2026-03-07 00:00", 5, "global"),
    new(2, 1, "task", "2026-03-02 00:00", null, 3, "global"),
    new(3, 1, "task", "2026-03-02 00:00", null, 4, "part-time"),
    new(4, 0, "milestone", "2026-03-02 00:00", null, 0, "global"),
];
GanttLink[] ganttLinks =
[
    new(1, 2, 3, "0", 0),
    new(2, 1, 4, "0", 0),
];
GanttAssignment[] ganttAssignments =
[
    new(1, 3, "dev", 1.0, Delay: 1, Duration: 2),
];

var data = new ProjectData
{
    Config =
    {
        CalendarMode = CalendarMode.Task, // gantt's calendar model
        UseCalendars = true,              // gantt `config.work_time`
    },
    Calendars =
    [
        new CalendarData { Id = "global" },
        new CalendarData
        {
            Id = "part-time",
            Worktime = new Worktime { Days = [false, true, true, true, true, false, false] }, // Mon-Thu
        },
    ],
    Tasks = ganttTasks.Select(ToTask).ToList(),
    Links = ganttLinks.Select(g => new LinkData
    {
        Id = g.Id,
        Source = g.Source,
        Target = g.Target,
        LinkType = ToLinkType(g.Type),
        Lag = g.Lag, // LagUnit stays null: gantt lag is in config.duration_unit
    }).ToList(),
    Resources = [new ResourceData { Id = "dev" }],
    Assignments = ganttAssignments.Select(g => new AssignmentData
    {
        Id = g.Id,
        Task = g.TaskId,
        Resource = g.ResourceId,
        Value = g.Value,
        Offset = g.Delay,
        Duration = g.Duration,
    }).ToList(),
};

var project = new Project();
var result = project.Solve(data, new SolveOptions());

foreach (var m in result.Updates)
{
    // Only changed fields are set. Summaries are included: write them back too.
    if (m.StartDate is { } start) Console.WriteLine($"{m.Id} start_date = {start}");
    if (m.EndDate is { } end) Console.WriteLine($"{m.Id} end_date = {end}");
}

static Moment ParseDate(string s) =>
    // Use the format your gantt `date_format` produces.
    Moment.FromDateTime(DateTime.ParseExact(s, "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture));

static LinkType ToLinkType(string type) => type switch
{
    "0" => LinkType.Fs,
    "1" => LinkType.Ss,
    "2" => LinkType.Ff,
    "3" => LinkType.Sf,
    _ => throw new ArgumentException($"unknown gantt link type {type}"),
};

static TaskData ToTask(GanttTask g)
{
    var t = new TaskData(g.Id)
    {
        Parent = g.Parent, // gantt's root (0) means "no parent"
        StartDate = ParseDate(g.StartDate),
        CalendarId = g.CalendarId,
    };
    switch (g.Type)
    {
        case "project":
            // A summary takes both dates or neither, never a duration.
            t.TaskType = TaskType.Summary;
            if (g.EndDate is null) t.StartDate = null;
            else t.EndDate = ParseDate(g.EndDate);
            break;
        case "milestone":
            t.TaskType = TaskType.Milestone;
            t.Duration = 0;
            break;
        default:
            t.Duration = g.Duration;
            break;
    }
    return t;
}

// Records as your server holds them after parsing gantt's JSON.
record GanttTask(int Id, int Parent, string Type, string StartDate, string? EndDate, double Duration, string CalendarId);
record GanttLink(int Id, int Source, int Target, string Type, double Lag);
record GanttAssignment(int Id, int TaskId, string ResourceId, double Value, double? Delay, double? Duration);
```

## Keep gantt's triggers on your side

Planning Engine has no UI triggers and no events. Your code decides when to schedule.

- Call [`Project.Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) and then [`Solve(null, …)`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>) wherever gantt would run auto-scheduling: after the user adds a link, drags a task, or saves the lightbox. See [Editing a project](<https://docs.dhtmlx.com/planning-engine/net/live-projects/editing-a-project.md>).
- `gantt.autoSchedule(id, inclusive)` is [`SolveOptions.Subtree`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/solve-options.md>). See [Reschedule a branch](<https://docs.dhtmlx.com/planning-engine/net/how-to/reschedule-a-branch.md>).
- Change sets can't add or edit calendars. If an assignment change gives a task a new merged calendar, send the whole project again with `Solve(data, …)`.
- gantt core rewrites descendant dates when inherited calendars or effort change. Forward those date and duration edits as ordinary [`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/task-update.md>) changes. Planning Engine doesn't recreate gantt's effort rules.

## Differences to expect

- Strict validation rejects input that gantt coerces: duplicate ids, a dated constraint with no `ConstraintDate`, a leaf task with fewer than two of start, end and duration, a dateless summary with no dated children. To get gantt's coercions with a report of each fix, see [Accept messy input](<https://docs.dhtmlx.com/planning-engine/net/how-to/accept-messy-input.md>).
- [`TimeUnit`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/time-unit.md>) has no `week`, `month` or `year`, and calendars with `customWeeks` can't be represented. A gantt merged calendar can contain `customWeeks`, so such projects can't be imported yet.
- [`Moment`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/moment.md>) is a naive local date-time with no timezone and no DST. gantt's JS engine uses the host time zone, so its arithmetic follows DST. `Moment.FromDateTime` reads the date and time fields as they are and ignores `DateTime.Kind`, so convert UTC values to local time before you build the data.
- `result.Notes` lists the links, tasks and assignments the engine dropped. gantt drops them silently.
- Three cases give different dates on purpose. With [`ProjectConstraint = true`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#projectconstraint>), children inherit the parent's constraint. gantt's engine ignores it. With [`UseProgress`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#useprogress>), a completed child of a moving summary stays in place. gantt drags it along. With [`DescendantLinks = true`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#descendantlinks>) in block mode, gantt fails with a cycle error and Planning Engine schedules the project. See [Links, constraints and summaries](<https://docs.dhtmlx.com/planning-engine/net/scheduling/links-constraints-summaries.md>).
- Summary-to-summary links in block mode, combined with leaf links between the same summaries, can give different dates. On very large generated graphs the dates drift out of range and the call throws `InvalidOperationException`. See [Error handling](<https://docs.dhtmlx.com/planning-engine/net/error-handling.md#date-overflow-on-summary-link-graphs>).

## Result

The example prints the changed fields:

```text
3 start_date = 2026-03-05 00:00
3 end_date = 2026-03-12 00:00
1 end_date = 2026-03-12 00:00
4 start_date = 2026-03-12 00:00
4 end_date = 2026-03-12 00:00
```

Task `3` starts when task `2` finishes and works Monday to Thursday only, so it ends a week later. The summary `1` grows to cover it. Task `2` didn't move, so it isn't listed. To get every date on the first load, see [Load all task dates](<https://docs.dhtmlx.com/planning-engine/net/how-to/load-all-task-dates.md>).
