[Canonical page](<https://docs.dhtmlx.com/planning-engine/js/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, and its input uses gantt's field names (`start_date`, `duration`, `parent`, `constraint_type`, `auto_scheduling`). It also accepts several gantt forms as is: task `type: "project"`, link `type` values `"0"` to `"3"`, and assignment `task_id` / `resource_id`. Unknown fields such as `text` are ignored. 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 plain objects with gantt's field names.
- You know each task's effective calendar, as gantt resolves it.

## Steps

1. Set the [scheduling config](<https://docs.dhtmlx.com/planning-engine/js/api/types/scheduling-config.md>) to gantt's model. Use `calendar_mode: "task"` for task calendars only, or `"task-inherited"` if gantt has `inherit_calendar: true`. Copy gantt's `work_time` into `use_calendars` explicitly. If you leave `use_calendars` out, Planning Engine schedules on calendars:

   ```js
   const ganttWorkTime = true; // gantt `config.work_time`

   const config = {
     calendar_mode: "task",
     use_calendars: ganttWorkTime,
   };
   ```

   Set `root_id` only when gantt's virtual root isn't `0`. The other auto-scheduling options (`apply_constraints`, `gap_behavior`, `move_projects`, `schedule_from_end`, `project_start`, `use_progress`) keep their gantt names.

2. Add one [calendar](<https://docs.dhtmlx.com/planning-engine/js/api/types/calendar-data.md>) per calendar your tasks use, and set each task's `calendar_id` 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 [task record](<https://docs.dhtmlx.com/planning-engine/js/api/types/task-data.md>):
   - Keep gantt's `type: "project"`. Planning Engine reads it as a summary. A summary takes both `start_date` and `end_date`, or neither. Drop its `duration`, or strict validation rejects the task.
   - A `parent` equal to `root_id` means "no parent". You can copy it as is.
   - A leaf task needs two of `start_date`, `end_date` and `duration`. If you send all three, they must agree.
   - If your gantt event handlers cancel auto-scheduling for a task, set `auto_scheduling: false` on it. The planner never moves it.

4. Copy gantt links as they are. [Link](<https://docs.dhtmlx.com/planning-engine/js/api/types/link-data.md>) `type` values `"0"`, `"1"`, `"2"` and `"3"` mean `"fs"`, `"ss"`, `"ff"` and `"sf"`. Copy `lag` and leave `lag_unit` unset, because gantt measures lag in `config.duration_unit`.

5. Convert each [assignment](<https://docs.dhtmlx.com/planning-engine/js/api/types/assignment-data.md>) to a slice of its task's work. Don't send gantt's `mode`.
   - `default` and `fixedDuration`: `delay` becomes `offset` and `duration` stays `duration`. Leave both unset 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/js/api/project-solve.md>) and write the changed fields from `result.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.

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

```js
import { Project } from "plen-js";

// gantt data as your app holds it: gantt's field names, dates as strings.
const gantt = {
  tasks: [
    { id: 1, parent: 0, type: "project", text: "Release", start_date: "2026-03-02 00:00", end_date: "2026-03-07 00:00", duration: 5, calendar_id: "global" },
    { id: 2, parent: 1, type: "task", text: "Build", start_date: "2026-03-02 00:00", duration: 3, calendar_id: "global" },
    { id: 3, parent: 1, type: "task", text: "Test", start_date: "2026-03-02 00:00", duration: 4, calendar_id: "part-time" },
    { id: 4, parent: 0, type: "milestone", text: "Ship", start_date: "2026-03-02 00:00", duration: 0, calendar_id: "global" },
  ],
  links: [
    { id: 1, source: 2, target: 3, type: "0", lag: 0 },
    { id: 2, source: 1, target: 4, type: "0", lag: 0 },
  ],
  resources: [{ id: "dev" }],
  assignments: [
    { id: 1, task_id: 3, resource_id: "dev", value: 1, mode: "default", delay: 1, duration: 2 },
  ],
};

function toTask(t) {
  const task = {
    id: t.id,
    parent: t.parent,
    type: t.type,
    start_date: t.start_date,
    calendar_id: t.calendar_id,
  };
  if (t.type === "project") {
    // A summary takes both dates or neither, never a duration.
    if (t.end_date) task.end_date = t.end_date;
    else delete task.start_date;
  } else {
    task.duration = t.duration;
  }
  return task;
}

const data = {
  config: {
    calendar_mode: "task", // gantt's calendar model
    use_calendars: true, // gantt `config.work_time`
  },
  calendars: [
    { id: "global" },
    // Mon-Thu, as gantt resolved it for task 3.
    { id: "part-time", worktime: { days: [0, 1, 1, 1, 1, 0, 0] } },
  ],
  tasks: gantt.tasks.map(toTask),
  links: gantt.links, // link types "0".."3" and lag are accepted as is
  resources: gantt.resources.map(({ id }) => ({ id })),
  // `default` and `fixedDuration` modes: `delay` becomes `offset`, no `mode`.
  assignments: gantt.assignments.map(({ id, task_id, resource_id, value, delay, duration }) => ({
    id,
    task_id,
    resource_id,
    value,
    offset: delay,
    duration,
  })),
};

const project = new Project();
const result = project.solve(data);

// Only changed fields are set. Summaries are included: write them back too.
for (const update of result.updates) {
  if (update.start_date) console.log(`${update.id} start_date = ${update.start_date}`);
  if (update.end_date) console.log(`${update.id} end_date = ${update.end_date}`);
}
```

## 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/js/api/project-apply.md>) and then [`project.solve(null)`](<https://docs.dhtmlx.com/planning-engine/js/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/js/live-projects/editing-a-project.md>).
- `gantt.autoSchedule(id, inclusive)` is the `subtree` [solve option](<https://docs.dhtmlx.com/planning-engine/js/api/types/solve-options.md>). See [Reschedule a branch](<https://docs.dhtmlx.com/planning-engine/js/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 `project.solve(data)`.
- gantt core rewrites descendant dates when inherited calendars or effort change. Forward those date and duration edits as ordinary [task updates](<https://docs.dhtmlx.com/planning-engine/js/api/types/task-update.md>). 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 `constraint_date`, 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/js/how-to/accept-messy-input.md>).
- `week`, `month` and `year` duration units and calendars with `customWeeks` are rejected. A gantt merged calendar can contain `customWeeks`, so such projects can't be imported yet.
- Dates are naive local date-times with no timezone and no DST. gantt's JS engine uses the host time zone, so its arithmetic follows DST. A `Date` you pass in is read from its local components, so gantt's `Date` objects and its formatted strings both work. See [Plain objects and dates](<https://docs.dhtmlx.com/planning-engine/js/plain-objects-and-dates.md>).
- `result.notes` lists the links, tasks and assignments the engine dropped. gantt drops them silently.
- Three cases give different dates on purpose. With `project_constraint: true`, children inherit the parent's constraint. gantt's engine ignores it. With `use_progress`, a completed child of a moving summary stays in place. gantt drags it along. With `descendant_links: true` 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/js/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 call can throw an `Error` named `Panic`. See [Error handling](<https://docs.dhtmlx.com/planning-engine/js/error-handling.md>).

## 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/js/how-to/load-all-task-dates.md>).
