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


# Clear leveling

Remove the delays and splits that [`level`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md>) wrote into a project, so every task goes back to its unleveled dates.

Leveling stores its result in two [task fields](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/task-data.md>): `leveling_delay` and [`pauses`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#pause>). To clear leveling, set `leveling_delay` to `0` and `pauses` to `[]` on every task that carries either, then reschedule. For how leveling sets these fields, see [Leveling resources](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/leveling-resources.md>).

## Prerequisites

- A solved [`JsonProject`](<https://docs.dhtmlx.com/planning-engine/wasm/api/json-project.md>) that carries leveling, either from a [`level`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md>) call or from loaded data with a non-zero `leveling_delay` or non-empty `pauses`.
- The ids of the tasks that carry a delay or pauses. Take them from your own model, or from the `updates` of the `level` result.

## Steps

1. Build one [`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/task-update.md>) per leveled task, with `leveling_delay: 0` and `pauses: []`. Put all the updates into one [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>):

   ```js
   function clearLeveling(ids) {
     return {
       tasks: {
         update: ids.map((id) => ({ id, leveling_delay: 0, pauses: [] })),
       },
     };
   }
   ```

   Leave the dates out of the update. With no date in the same update, the `leveling_delay` change moves the stored start back by the delay, and the `pauses` change keeps the start and duration and recomputes the end.

2. Apply the changes with [`apply`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-apply.md>) and check the report in the envelope's `result`:

   ```js
   const report = unwrap(project.apply(JSON.stringify(clearLeveling(ids))));
   if (report.needsResync) {
     // Resync with project.solve(dataJson, ...) before the next edit.
   }
   ```

3. Reschedule with [`solve`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) without data, and write `result.updates` back to your model:

   ```js
   const result = unwrap(project.solve(undefined, "{}"));
   ```

   The cleared dates become the accepted baseline. The resource contention that leveling resolved shows up again in [`result.overallocations`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/overallocation.md>).

The full example below levels three 2-day tasks that share `dev1`, then clears the leveling. It uses the default calendar (Monday to Friday, 08:00-12:00 and 13:00-17:00) and starts on Monday, August 10, 2026:

```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;
}

function clearLeveling(ids) {
  return {
    tasks: {
      update: ids.map((id) => ({ id, leveling_delay: 0, pauses: [] })),
    },
  };
}

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 },
    { id: "C", 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 },
    { task: "C", resource: "dev1", value: 1 },
  ],
};

const project = new JsonProject("{}");
try {
  unwrap(project.solve(JSON.stringify(data), "{}"));

  // B is delayed to Wednesday, C to Friday.
  const leveled = unwrap(project.level("{}"));

  // The tasks that leveling delayed or split.
  const ids = leveled.updates
    .filter((u) => (u.leveling_delay ?? 0) !== 0 || (u.pauses ?? []).length > 0)
    .map((u) => u.id);

  const report = unwrap(project.apply(JSON.stringify(clearLeveling(ids))));
  if (report.needsResync) {
    throw new Error("clearing was rejected, resync with solve(dataJson, ...)");
  }

  const result = unwrap(project.solve(undefined, "{}"));
  for (const u of result.updates) {
    console.log(`${u.id}: ${u.start_date} .. ${u.end_date}`);
  }
  for (const over of result.overallocations ?? []) {
    for (const w of over.windows ?? []) {
      console.log(`${over.resource}: ${w.from} .. ${w.to}, peak ${w.peak}`);
    }
  }
} finally {
  project.free();
}
```

The `level` call returns these `updates`, the input for the filter:

```json
[
  { "id": "B", "kind": "task", "start_date": "2026-08-12 08:00", "end_date": "2026-08-14 08:00", "leveling_delay": 2 },
  { "id": "C", "kind": "task", "start_date": "2026-08-14 08:00", "end_date": "2026-08-18 08:00", "leveling_delay": 4 }
]
```

## Re-level from scratch instead

To replace old leveling with a fresh run, skip the steps above and call [`level`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md>) again. [`clearFirst`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/leveling-options.md>) is `true` by default, so the run first sets `leveling_delay: 0` and `pauses: []` on every in-scope task that carries either, then levels the clean plan:

```js
const result = unwrap(project.level("{}"));
```

The call diffs its `updates` against the state it started from. A task whose stale delay was cleared and not replaced still gets an update entry, so writing `updates` back to your model keeps it in sync.

With a [`scope`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/leveling-options.md#scope-fields>), the run clears only in-scope tasks. Send `{"clearFirst": false}` to keep the existing delays and pauses and level on top of them.

## Things to know

- `null` also works: `leveling_delay: null` restores the default of 0, and `pauses: null` clears the list. Omitting a field leaves it unchanged.
- Only leveling produces pauses. Every pause counts as leveling-owned, whether or not its `by` is set, so clearing removes all of them.
- To see what clearing would do before you commit it, pass the same change set to [`preview`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-preview.md>). The project stays untouched. See [Previewing changes](<https://docs.dhtmlx.com/planning-engine/wasm/live-projects/previewing-changes.md>).
- In a live editor, set `baselineId` in the [change set](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>) to the baseline your model shows. [`apply`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-apply.md>) then returns a `project` error with code `stale-baseline` if the project has moved on. See [Error handling](<https://docs.dhtmlx.com/planning-engine/wasm/error-handling.md>).
- If `apply` rejects an operation, `report.needsResync` is `true`, and [`solve`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) without data returns `needs-resync` until you send the full data with `solve(dataJson, …)`. See [Recover from rejected changes](<https://docs.dhtmlx.com/planning-engine/wasm/how-to/recover-from-rejected-changes.md>).

## Result

Run the example with `node`. It prints `B` and `C` back on Monday, and the overallocation window on `dev1` returns:

```text
B: 2026-08-10 08:00 .. 2026-08-12 08:00
C: 2026-08-10 08:00 .. 2026-08-12 08:00
dev1: 2026-08-10 08:00 .. 2026-08-11 17:00, peak 3
```
