[Canonical page](<https://docs.dhtmlx.com/planning-engine/wasm/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 JSON input uses gantt's field names (`start_date`, `duration`, `parent`, [`constraint_type`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#constrainttype>), `auto_scheduling`). Gantt's [link `type`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#link-type>) values `"0"` to `"3"`, [task `type: "project"`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#task-type>), and [assignment](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/assignment-data.md>) `task_id` / `resource_id` are accepted as they are. Fields the engine doesn't know, such as `text` or `open`, are ignored in task, link, resource and assignment records. Most of the import is a straight copy. The steps below cover the places where a straight copy gives different dates or fails validation.

## Prerequisites

- Your gantt data as plain JS objects, for example from `gantt.serialize()`.
- Each task's effective calendar, as gantt resolves it.
- The module loaded as shown in [Quick Start](<https://docs.dhtmlx.com/planning-engine/wasm/quick-start.md>).

## Steps

1. Set the [scheduling config](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-config.md>) to gantt's model. Use [`calendar_mode: "task"`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#calendar-mode>) 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 config = {
     calendar_mode: "task",
     use_calendars: ganttWorkTime, // gantt config.work_time
   };
   ```

   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 [`CalendarData`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/calendar-data.md>) entry 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. Send [dates](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#datevalue>) as `YYYY-MM-DD HH:mm` strings (`YYYY-MM-DD` and a `T` separator also work). If gantt's `date_format` gives another format, such as day first, convert the dates first. Otherwise the call fails with a `validation` error, `"invalid date"`.

4. Fix up each [task](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/task-data.md>):
   - A summary (`type: "project"`) takes both `start_date` and `end_date`, or neither. Drop its `duration`. Strict validation rejects a summary that has `start_date` and `duration` but no `end_date`.
   - 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.

5. Copy [links](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/link-data.md>) as they are, `type` and `lag` included. Leave `lag_unit` out, because gantt measures lag in `config.duration_unit`.

6. Convert each [assignment](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/assignment-data.md>) to a slice of its task's work, and drop gantt's `mode`:
   - `default` and `fixedDuration`: rename `delay` to `offset` and keep `duration`. Leave both out to book the whole task. A `delay` you forget to rename is ignored as an unknown field, so the assignment silently books from the task start.
   - `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.

7. Pass the data to [`solve`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) and write the changed fields from [`result.updates`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/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.

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

```js
const { JsonProject } = require("./pkg-node/plen_wasm.js");

function unwrap(text) {
  const response = JSON.parse(text);
  if (!response.ok) {
    const { kind, code, message, path } = response.error;
    throw new Error(`${kind} ${code ?? ""}: ${message} (at ${path})`);
  }
  return response.result;
}

// gantt.serialize() output, with each task's effective calendar already resolved.
const gantt = {
  data: [
    { id: 1, text: "Release", type: "project", parent: 0, start_date: "2026-03-02 00:00", end_date: "2026-03-07 00:00", duration: 5, calendar_id: "global" },
    { id: 2, text: "Build", parent: 1, start_date: "2026-03-02 00:00", duration: 3, calendar_id: "global" },
    { id: 3, text: "Test", parent: 1, start_date: "2026-03-02 00:00", duration: 4, calendar_id: "part-time" },
    { id: 4, text: "Ship", type: "milestone", parent: 0, 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 task(t) {
  if (t.type !== "project") return t;
  // A summary takes both dates or neither, never a duration.
  const { duration, ...summary } = t;
  if (!summary.end_date) delete summary.start_date;
  return summary;
}

function assignment({ mode, delay, ...a }) {
  // default / fixedDuration: gantt's delay is the slice offset.
  return { ...a, offset: delay };
}

const data = {
  config: {
    calendar_mode: "task", // gantt's calendar model
    use_calendars: true,   // gantt config.work_time
  },
  calendars: [
    { id: "global" },
    { id: "part-time", worktime: { days: [false, true, true, true, true, false, false] } }, // Mon-Thu
  ],
  tasks: gantt.data.map(task),
  links: gantt.links,
  resources: gantt.resources,
  assignments: gantt.assignments.map(assignment),
};

const project = new JsonProject("{}");
try {
  const result = unwrap(project.solve(JSON.stringify(data), "{}"));
  for (const m of result.updates) {
    // Only changed fields are set. Summaries are included: write them back too.
    if (m.start_date) console.log(`${m.id} start_date = ${m.start_date}`);
    if (m.end_date) console.log(`${m.id} end_date = ${m.end_date}`);
  }
} finally {
  project.free();
}
```

## Keep gantt's triggers on your side

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

- Call [`apply`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-apply.md>) and then [`solve(undefined, ...)`](<https://docs.dhtmlx.com/planning-engine/wasm/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/wasm/live-projects/editing-a-project.md>).
- `gantt.autoSchedule(id, inclusive)` is the [`subtree`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/solve-options.md>) solve option. See [Reschedule a branch](<https://docs.dhtmlx.com/planning-engine/wasm/how-to/reschedule-a-branch.md>).
- [Change sets](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>) can't add or edit calendars. If an assignment change gives a task a new merged calendar, send the whole project again with `solve(dataJson, ...)`.
- 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/wasm/api/types/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 `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/wasm/how-to/accept-messy-input.md>).
- `week`, `month` and `year` duration units and [calendars](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/calendar-data.md>) 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. Convert dates to naive local time before you build the data.
- [`result.notes`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/schedule-result.md#note>) lists the links, tasks and assignments the engine dropped. gantt drops them silently.
- Three cases give different dates on purpose. With [`project_constraint: true`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-config.md>), children inherit the parent's constraint, and gantt's engine ignores it. With `use_progress`, a completed child of a moving summary stays in place, while 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/wasm/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 trap with a `RuntimeError`. See [Error handling](<https://docs.dhtmlx.com/planning-engine/wasm/error-handling.md#engine-failures>).

## Result

Run the example with `node`. It 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/wasm/how-to/load-all-task-dates.md>).
