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


# JsonProject.suggestAssignments

Proposes resources for requirement slots of a [`JsonProject`](<https://docs.dhtmlx.com/planning-engine/wasm/api/json-project.md>). It fills empty slots, repairs broken ones and rebalances valid ones, and scores each step like [`evaluate`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-evaluate.md>). The project isn't modified: the proposal comes back as a change set to commit with [`apply`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-apply.md>). Needs a solved project with no edits applied since the last [`solve`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) or [`level`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-level.md>).

## Signature

```ts
suggestAssignments(slots_json: string, options_json: string): string
```

Synchronous and read-only. Bad input comes back as an error envelope. The call doesn't throw.

## Request

| Argument | Payload | Required | Description |
|---|---|---|---|
| `slots_json` | [`Slot`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/slot.md>)`[]` | yes | The slots to fill, repair or rebalance. [`slots`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-slots.md>) builds them from skills and requirements, and its output can be passed as is. `"[]"` returns an empty proposal |
| `options_json` | [`SuggestOptions`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-suggest-assignments.md#suggestoptions>) | yes | `"{}"` for defaults |

### SuggestOptions

The options object for `suggestAssignments`. Every field can be omitted, and `null` reads as the default. Unknown fields, such as `mode`, are rejected with a `parse` error.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `baselineId` | string | no | absent (not checked) | Must name the current baseline, or the call fails with `stale-baseline` |
| `targets` | string[] | no | `["empty"]` | Which slots may change, judged by the slot's current resource: `"empty"` (no assignment; filled), `"broken"` (resource not in `allowed`; repaired), `"valid"` (resource in `allowed`; rebalanced). Each value at most once. `[]` changes no assignment and only reports `invalid` |
| `fixed` | [`Id`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#id>)`[]` | no | `[]` | Assignment ids that are never changed. A slot bound to one of them is never visited |
| `newResourcePenalty` | number | no | `0` | Cost added for each resource used beyond the reference state, `>= 0`. `0` balances load only; a large value favors the fewest people |
| `level` | [`LevelingOptions`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/leveling-options.md>) \| `false` | no | `false` | With leveling options, every candidate is leveled and scored on the leveled stage, and `proposed.leveled` is filled. `false` scores the scheduled stage. `true` is a `validation` error |
| `passes` | number | no | `1` | Rebalance passes over valid slots, `>= 1`. Used only when `targets` contains `"valid"` |
| `clearFirst` | boolean | no | `false` | Scores against a copy with in-scope `leveling_delay` / `pauses` cleared. The clears are added to `changes.tasks.update`. Separate from `level.clearFirst` |

## Behavior

- Greedy: slots are visited one at a time, and later slots build on earlier picks.
- It never removes an assignment. It adds one per filled slot, with the slot id as the assignment id, and changes the resource of a repaired or rebalanced one.
- A slot that can't be filled or repaired isn't an error: it's listed in `unfilled` or `invalid`.
- Commit the proposal with `apply(JSON.stringify(result.changes))`, then `solve` without data, or `level` with the same leveling options when `level` was set.

How picks are made and scored: [Suggesting assignments](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/suggesting-assignments.md#how-picks-are-made>).

## Response

The success [envelope](<https://docs.dhtmlx.com/planning-engine/wasm/wire-format.md#envelope>) carries a [`SuggestResult`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-suggest-assignments.md#suggestresult>).

### SuggestResult

| Field | Type | Description |
|---|---|---|
| `baselineId` | string | The accepted baseline the call scored against |
| `changes` | [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>) | The net proposal, with `changes.baselineId` set. `assignments.add` entries are `{id, task, resource, value}`, `assignments.update` entries are `{id, resource}`. With `clearFirst`, `tasks.update` holds `{id, leveling_delay: 0, pauses: []}` per cleared task. Empty buckets and unset fields are omitted, so a proposal with no change is `{baselineId}` alone. Pass it to `apply` unchanged |
| `steps` | [`SuggestStep`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-suggest-assignments.md#suggeststep>)`[]` | The decisions that changed the proposal, in order |
| `unfilled` | [`Id`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#id>)`[]` | Targeted empty slots left empty, in visit order |
| `invalid` | Id[] | Slots whose final resource isn't in `allowed`, fixed slots included, in visit order |
| `baseline` | `{ cost, resources }` | Cost and number of used resources of the reference state, on the scoring stage |
| `proposed` | [`SuggestProposal`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-suggest-assignments.md#suggestproposal>) | The proposed state measured against the reference state |

### SuggestStep

| Field | Type | Description |
|---|---|---|
| `slot` | [`Id`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#id>) | The slot that changed |
| `task` | Id | The slot's task |
| `pass` | number | `0` for a fill or a repair, `1` and up for a rebalance pass |
| `fromResource` | Id \| `null` | The previous resource. `null` for a fill |
| `toResource` | Id | The chosen resource |
| `cost` | `{ before, after }` | Cost of the state before and after the step, penalty included. `before` is the previous step's `after`, or `baseline.cost` for the first step |

### SuggestProposal

| Field | Type | Description |
|---|---|---|
| `cost` | number | Cost of the proposed state, penalty included |
| `resources` | number | Number of used resources in the proposed state |
| `scheduled` | [`Metrics`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/metrics.md>) | Scheduled-stage metrics against the reference state |
| `leveled` | [`LeveledMetrics`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/metrics.md#leveledmetrics>) | Leveled-stage metrics. Present only when `options.level` is set |

## Errors

| Kind | Code | When |
|---|---|---|
| `project` | `no-baseline` | The project has never been solved, or `apply` ran since the last solve |
| `project` | `stale-baseline` | `options.baselineId` doesn't name the current baseline |
| `validation` | `null` | Invalid options: `newResourcePenalty` negative or not finite (`newResourcePenalty`), `passes` below 1 (`passes`), a duplicate in `targets` (`targets[i]`), an unknown, inactive or duplicate `fixed` entry (`fixed[i]`), invalid `level` options |
| `validation` | `null` | The accepted state has a cycle (`path: null`) |
| `validation` | `null` | An invalid slot: duplicate id (`slots[i].id`), unknown, unscheduled or summary task (`slots[i].task`), unknown allowed resource (`slots[i].allowed[j]`), `value` not finite or `<= 0` (`slots[i].value`), an `assignment` that is unknown, inactive, on another task or bound by two slots (`slots[i].assignment`), a slot id equal to another existing assignment's id (`slots[i].id`) |
| `validation` | `null` | A value the decoder rejects, with `path: null`: an unknown `targets` value, `level: true` |
| `parse` | `null` | Malformed JSON, a wrong JSON type, or an unknown field in a slot or the options |

The JSON is decoded before the project is checked. After that, `no-baseline` and `stale-baseline` come first, then the options, the cycle check and the slots, in the order above. The accepted slot values are on the [`Slot`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/slot.md>) page.

## Example

Fill an open position on task `C`, then commit the proposal.

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

const data = {
  config: { use_calendars: false },
  tasks: [
    { id: "A", start_date: "2026-08-10", duration: 2 },
    { id: "C", start_date: "2026-08-10", duration: 2 },
  ],
  resources: [{ id: "dev1" }, { id: "dev2" }],
  assignments: [{ id: "aA", task: "A", resource: "dev1", value: 1 }],
};

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

  // Task C needs one person: dev1 or dev2.
  const slots = [{ id: "sC", task: "C", allowed: ["dev1", "dev2"] }];
  const options = { baselineId: solved.baselineId };
  const response = JSON.parse(
    project.suggestAssignments(JSON.stringify(slots), JSON.stringify(options)),
  );

  if (response.ok) {
    for (const step of response.result.steps) {
      console.log(`${step.slot} -> ${step.toResource}`); // sC -> dev2
    }
    const report = JSON.parse(project.apply(JSON.stringify(response.result.changes)));
    if (report.ok && report.result.rejected.length === 0) {
      project.solve(undefined, "{}");
    }
  } else {
    const { kind, code, message, path } = response.error;
    console.error(kind, code, message, path);
  }
} finally {
  project.free();
}
```

`slots_json`:

```json
[{ "id": "sC", "task": "C", "allowed": ["dev1", "dev2"] }]
```

`options_json` (the token comes from the `solve` result):

```json
{ "baselineId": "1-b0605af8cba3d7c9" }
```

Response:

```json
{
  "ok": true,
  "result": {
    "baseline": { "cost": 0, "resources": 1 },
    "baselineId": "1-b0605af8cba3d7c9",
    "changes": {
      "assignments": {
        "add": [{ "id": "sC", "resource": "dev2", "task": "C", "value": 1 }]
      },
      "baselineId": "1-b0605af8cba3d7c9"
    },
    "invalid": [],
    "proposed": {
      "cost": 0,
      "resources": 2,
      "scheduled": {
        "conflicts": { "delta": 0, "value": 0 },
        "cost": 0,
        "cycles": { "delta": 0, "value": 0 },
        "finish": {
          "project": { "date": "2026-08-12 00:00", "delta": 0 },
          "tasks": []
        },
        "overload": {
          "focus": [],
          "resources": [],
          "total": { "delta": 0, "value": 0 }
        }
      }
    },
    "steps": [
      {
        "cost": { "after": 0, "before": 0 },
        "fromResource": null,
        "pass": 0,
        "slot": "sC",
        "task": "C",
        "toResource": "dev2"
      }
    ],
    "unfilled": []
  }
}
```

`dev2` wins with cost `0`. With `dev1` the step would cost `2`: the overload from working on `A` and `C` on the same days.

## Related

- [`Slot`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/slot.md>) - the slots to fill
- [`JsonProject.slots`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-slots.md>) - builds slots from skills and requirements
- [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>) - the format of the proposal
- [`Metrics`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/metrics.md>) - how each state is scored
- [Suggesting assignments](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/suggesting-assignments.md>) - how picks are made and committed
