[Canonical page](<https://docs.dhtmlx.com/planning-engine/js/how-to/preview-leveling/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Preview leveling

Show the user what leveling would change before it touches the project, then keep the leveled plan or throw it away.

[`project.level()`](<https://docs.dhtmlx.com/planning-engine/js/api/project-level.md>) commits. It writes `leveling_delay` and `pauses` into the project and accepts the leveled dates as the new baseline. To get a proposal instead, level a copy made with [`project.clone()`](<https://docs.dhtmlx.com/planning-engine/js/api/project.md#clone>).

:::note
`clone()` copies the whole project at once, so its cost grows with the project's size.
:::

## Prerequisites

- A solved [`Project`](<https://docs.dhtmlx.com/planning-engine/js/api/project.md>): [`project.solve(data)`](<https://docs.dhtmlx.com/planning-engine/js/api/project-solve.md>) has run.
- The data has `resources` and `assignments`, so there's overallocation to level.
- The project is forward-scheduled. `level` refuses `config.schedule_from_end: true` with an [`InputError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md#inputerror>).

## Steps

1. Clone the solved project:

   ```js
   const draft = project.clone();
   ```

2. Level the clone. The original keeps its dates, its baseline and its overallocation:

   ```js
   const proposal = draft.level();
   ```

3. Show the proposal. `proposal.updates` lists the tasks leveling moved, with their new dates, `leveling_delay` and `pauses`. The diff is relative to the state the call started from. That state matches the original, so you can apply the entries to the model you already hold.

   ```js
   for (const u of proposal.updates) {
     console.log(`${u.id}: start ${u.start_date}, delay ${u.leveling_delay}, pauses ${JSON.stringify(u.pauses ?? [])}`);
   }
   ```

   :::note
   The leveling result is a [`ScheduleResult`](<https://docs.dhtmlx.com/planning-engine/js/api/types/schedule-result.md>) with an added `rounds` field; read `updates` directly on it.
   :::

4. Show what leveling couldn't fix. `proposal.overallocations` holds the windows left after leveling. Each window has a `reason`, such as `fixed-task` or `priority-1000`:

   ```js
   for (const over of proposal.overallocations ?? []) {
     for (const w of over.windows ?? []) {
       console.log(`${over.resource}: ${w.from} .. ${w.to}, ${w.reason}`);
     }
   }
   ```

   :::note
   `windows` is absent, not an empty array, when a resource has no overallocated window.
   :::

5. Apply the user's choice:
   - Accept: replace the original with the clone, `project = draft;`. The project now holds the leveled baseline.
   - Reject: drop `draft`. Nothing else needs undoing.

   Replace the original only if it hasn't been edited since you cloned it. Otherwise the proposal was computed from an older state, so level a new clone.

The full example levels two tasks that book the same developer full time on Monday and Tuesday:

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

// Monday, August 10, 2026, on the default calendar.
const data = {
  tasks: [
    { id: "A", start_date: "2026-08-10 08:00", duration: 2 },
    { id: "B", start_date: "2026-08-10 08:00", duration: 2 },
  ],
  resources: [{ id: "dev1" }],
  assignments: [
    { task: "A", resource: "dev1", value: 1 },
    { task: "B", resource: "dev1", value: 1 },
  ],
};

let project = new Project();
project.solve(data);

const draft = project.clone();
const proposal = draft.level();

for (const u of proposal.updates) {
  console.log(`${u.id}: ${u.start_date} .. ${u.end_date}, delay ${u.leveling_delay}`);
}
const left = proposal.overallocations?.length ?? 0;
console.log(`overallocated resources left: ${left}`);

const userAccepts = true;
if (userAccepts) {
  project = draft;
}
const plan = project.solve(null);
console.log(`baseline: ${plan.baselineId === proposal.baselineId ? "leveled" : "original"}`);
```

## Result

The example prints the one task leveling moved, and nothing is left overallocated:

```text
B: 2026-08-12 08:00 .. 2026-08-14 08:00, delay 2
overallocated resources left: 0
baseline: leveled
```

`B` waits two days for `A`, so its `leveling_delay` is `2`. Until you replace `project` with `draft`, `project` still reports the Monday-Tuesday window on `dev1`, and a [`solve(null)`](<https://docs.dhtmlx.com/planning-engine/js/api/project-solve.md>) on it returns no updates. After the swap, `baselineId` is the token from the leveling result.

The clone is a full solved project, so you can also call [`draft.loadProfile()`](<https://docs.dhtmlx.com/planning-engine/js/api/project-load-profile.md>) on it to draw the leveled load. See [Resource load](<https://docs.dhtmlx.com/planning-engine/js/resources-and-assignment/resource-load.md>). To compare leveling across several alternative change sets with scores, use [`evaluate`](<https://docs.dhtmlx.com/planning-engine/js/api/project-evaluate.md>) with the `level` option instead. See [Evaluating alternatives](<https://docs.dhtmlx.com/planning-engine/js/resources-and-assignment/evaluating-alternatives.md>). For options, reasons and splits, see [Leveling resources](<https://docs.dhtmlx.com/planning-engine/js/resources-and-assignment/leveling-resources.md>).
