[Canonical page](<https://docs.dhtmlx.com/planning-engine/js/api/project-apply/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Project#apply

Writes edits into the accepted state of a [`Project`](<https://docs.dhtmlx.com/planning-engine/js/api/project.md>) and marks it unsolved. It doesn't schedule anything: follow it with [`solve(null)`](<https://docs.dhtmlx.com/planning-engine/js/api/project-solve.md>) or [`level`](<https://docs.dhtmlx.com/planning-engine/js/api/project-level.md>) to get the new dates. To try edits without committing them, use [`preview`](<https://docs.dhtmlx.com/planning-engine/js/api/project-preview.md>).

## Signature

```ts
apply(changes: SchedulingChanges): ApplyReport
```

Synchronous. Runs on the calling thread.

## Parameters

| Name | Type | Description |
|---|---|---|
| `changes` | [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/js/api/types/scheduling-changes.md>) | The edits to commit. Never mutated. |

## Behavior

- Operations run in order and are accepted one by one. A rejected operation goes into `rejected` in the report; `apply` doesn't throw for it.
- Any rejection sets `needsResync`. From then on, `apply` throws `ProjectError` `"needs-resync"` until a full `solve(data)`.
- The state is unsolved afterwards, so [`preview`](<https://docs.dhtmlx.com/planning-engine/js/api/project-preview.md>), [`loadProfile`](<https://docs.dhtmlx.com/planning-engine/js/api/project-load-profile.md>), [`evaluate`](<https://docs.dhtmlx.com/planning-engine/js/api/project-evaluate.md>), [`slots`](<https://docs.dhtmlx.com/planning-engine/js/api/project-slots.md>) and [`suggestAssignments`](<https://docs.dhtmlx.com/planning-engine/js/api/project-suggest-assignments.md>) need a `solve(null)` or `level` first.
- Tasks added or updated by accepted operations accumulate across calls. The next acyclic `solve(null)` or `level` reports each of them in `updates`, even when its dates don't move.

:::note
A start date that falls on non-working time is kept unsnapped when the task has no predecessor. The end is computed forward from it on the working calendar.
:::

## Returns

An `ApplyReport`, a fresh object that shares nothing with `changes` or the project.

### ApplyReport

The outcome of one `apply` call. Every operation of the change set appears in exactly one of `applied` and `rejected`.

| Field | Type | Description |
|---|---|---|
| `applied` | `{ path: string }[]` | Accepted operations, in the order they ran. `path` names the operation, e.g. `"tasks.update[0]"`. |
| `rejected` | [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/js/api/types/common.md#rejectedoperation>)`[]` | Refused operations, in the same order, with a `path` and a `message`. A rejected operation leaves no partial edit. Empty when the whole change set was accepted. |
| `effects` | [`ChangeEffects`](<https://docs.dhtmlx.com/planning-engine/js/api/project-apply.md#changeeffects>) | Ids removed, generated or dropped by the accepted operations. |
| `needsResync` | `boolean` | `true` when `rejected` isn't empty. |
| `revision` | `number` | The project's state revision after this call. Every `apply` raises it, so a larger value from the same project means a later state. |

### ChangeEffects

Side effects of the accepted operations beyond the records they named. Each list holds ids in the order the operations ran and is empty when there was no such effect. A rejected operation adds nothing.

| Field | Type | Description |
|---|---|---|
| `removedTasks` | [`Id`](<https://docs.dhtmlx.com/planning-engine/js/api/types/common.md#id>)`[]` | Each task named in `tasks.remove`, followed by its descendants, which are removed with it. |
| `removedLinks` | `Id[]` | Links named in `links.remove`, and links removed because their source or target task was removed. |
| `generatedLinks` | `Id[]` | Ids assigned to added links that had no `id`, in the form `"$generated_link_1"`, `"$generated_link_2"`, and so on. Use them to update or remove those links later. |
| `droppedLinks` | `Id[]` | Links still in the project that became inactive because of this change set. Links that were already inactive aren't listed. |
| `removedAssignments` | `Id[]` | Assignments named in `assignments.remove`, and assignments removed because their task or resource was removed. A cascaded assignment without an id isn't listed. |
| `generatedAssignments` | `Id[]` | Ids assigned to added assignments that had no `id`, in the form `"$generated_assignment_1"`, `"$generated_assignment_2"`, and so on. |
| `droppedAssignments` | `Id[]` | Assignments still in the project that became inactive because of this change set. Assignments that were already inactive aren't listed. |

## Errors

| Error | When |
|---|---|
| [`ProjectError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md>) `"stale-baseline"` | `changes.baselineId` is set and doesn't name the current baseline. |
| `ProjectError` `"needs-resync"` | An earlier `apply` had rejections and no `solve(data)` followed. Checked before `changes.baselineId`, so it wins over `"stale-baseline"`. |
| [`InputError`](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md>) | The change set is malformed: an unknown key on the change set or one of its buckets, a wrong type, a non-finite number or an invalid `Date`. `path` names the field. |
| `TypeError` | `changes` has a circular reference or a `BigInt`. |
| `Error("Project has been disposed")` | The project was disposed. |

## Example

Commit an edit, resync with the full data if part of it was rejected, and reschedule.

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

const data = {
  tasks: [{ id: 1, start_date: "2026-08-10 08:00", duration: 2 }],
};

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

const report = project.apply({
  baselineId: solved.baselineId,
  tasks: { update: [{ id: 1, duration: 3 }] },
});

for (const op of report.rejected) {
  console.log(`rejected at ${op.path}: ${op.message}`);
}

const result = report.needsResync ? project.solve(data) : project.solve(null);
console.log(`${result.updates.length} task(s) moved`);
```

## Related

- [`Project`](<https://docs.dhtmlx.com/planning-engine/js/api/project.md>) - the class and its other methods
- [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/js/api/types/scheduling-changes.md>) - the change set
- [Errors](<https://docs.dhtmlx.com/planning-engine/js/api/types/errors.md#projecterror>) - `stale-baseline` and `needs-resync`
- [Editing a project](<https://docs.dhtmlx.com/planning-engine/js/live-projects/editing-a-project.md>) - change sets, applying and rescheduling
- [Recover from rejected changes](<https://docs.dhtmlx.com/planning-engine/js/how-to/recover-from-rejected-changes.md>) - handling `needsResync`
