# Local and Global State

Store values for the **current run** (local state) or **across runs on the same Desktop Agent** (global state). Stays on this device; data is never sent to ZeroWork servers.

**API** `zw.state.*`, `zw.globalState.*`

```js
// @zw-run-locally

// Local per-run state — cleared automatically when the run ends
const runState = zw.state.access();
runState.runStartedAt = Date.now();

// Shared Desktop Agent global state — persists across runs on this device
const globalState = zw.globalState.access();
globalState.totalRuns = (globalState.totalRuns ?? 0) + 1;
```

```js
// In the browser — work with a JSON-safe snapshot of local or global state
const runStateCopy = await zw.state.browser.getCopy();
runStateCopy.lastSeenAt = Date.now();
await zw.state.browser.commit({ state: runStateCopy });
```

***

### 1. Local Per-Run State — `zw.state.*`

A per-run scratchpad automatically initiated on run start. It is **deleted when the run ends**.

#### **At a Glance**

* `zw.state.access()` \
  → returns a **live mutable object** you can read and update anywhere in the run. It can hold anything (e.g., helpers, counters, functions, Maps, Playwright objects like pages or contexts, etc.). It's deleted automatically when the run ends. Not available in the browser (see [**Use state in the browser**](#id-3.-use-state-in-the-browser-zw.state.browser.-and-zw.globalstate.browser) further below).
* <mark style="color:$info;">async/sync\*</mark> \
  `await zw.state.clear()` \
  → clears the whole run state.

\*`async` in browser, `sync` locally (see [Broken link](https://docs.zerowork.io/using-zerowork/using-building-blocks/write-javascript/broken-reference "mention")).

#### **Examples**

**Add a type-safe validator and stringifier around zw\.setRef()**

```js
// @zw-run-locally

zw.state.access().setRefSafely = ({ ref_id, name, value }, expectedType) => {
  if (typeof value !== expectedType) {
    throw new Error(`Value for ${name} is not ${expectedType}`);
  }
  const valueStr = typeof value === "string"
    ? value
    : (value === null || value === undefined)
      ? ""
      : JSON.stringify(value);
  zw.setRef({ ref_id, name, value: valueStr });
};

// Use the helper in later blocks within the same run
zw.state.access().setRefSafely({ ref_id, name: "age", value: 42 }, "number");
```

**Counters or flags across multiple Write JS blocks in the same run**

```js
// @zw-run-locally

const runState = zw.state.access();
runState.processed = (runState.processed ?? 0) + 1;
await zw.log("processed", runState.processed);
```

**Clear mid-run if needed**

```js
await zw.state.clear();
```

***

### 2. Global State — `zw.globalState.*`

A device-level scratchpad shared across runs on the same Desktop Agent. Persists between runs until the Desktop Agent quits or restarts. Use it when you need to reuse something across runs on one machine (e.g., cached data, a counter of all runs, etc.).

#### At a Glance

* `zw.globalState.access()` \
  → a **live mutable object** you can read and update across runs. It can hold anything. Not available in the browser (see [**Use state in the browser**](#id-3.-use-state-in-the-browser-zw.state.browser.-and-zw.globalstate.browser) further below).
* <mark style="color:$info;">async/sync\*</mark> \
  `await zw.globalState.clear()` \
  → clears the whole global state.

\*`async` in browser, `sync` locally (see [Broken link](https://docs.zerowork.io/using-zerowork/using-building-blocks/write-javascript/broken-reference "mention")).

#### **Example**

**Track which TaskBots ran today (per device)**

```js
// @zw-run-locally

const gs = zw.globalState.access();
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD

if (!gs.botsRanToday || gs.botsRanToday.date !== today) {
  gs.botsRanToday = { date: today, bots: [] };
}

const taskbotInfo = await zw.getTaskbotInfo();
if (!gs.botsRanToday.bots.includes(taskbotInfo.id)) {
  gs.botsRanToday.bots.push(taskbotInfo.id);
}
```

#### **Notes**

* It's **not durable** storage. It's **lost when the Desktop Agent quits or restarts**.\
  For durable data use:
  * **Variables/tables** via `zw.getRef()` / `zw.setRef()`
  * **Device storage** via `zw.deviceStorage.*`&#x20;
* As long as the Desktop Agent isn't closed or restarted, global state is **not auto-cleaned**. Clear what you no longer need.

***

### 3. Use State in the Browser — `zw.state.browser.*` and `zw.globalState.browser.*`

In the browser you **cannot** use `*.access()`. Use **JSON-safe snapshots** instead.

#### **At a Glance**

* <mark style="color:$info;">async</mark>\
  `await zw.state.browser.getCopy({ key?: string })` \
  `await zw.globalState.browser.getCopy({ key?: string })` \
  → returns the current state (or the section under `key`).
* <mark style="color:$info;">async</mark>\
  `await zw.state.browser.commit({ state: any, key?: string })`  \
  `await zw.globalState.browser.commit({ state: any, key?: string })` \
  → accepts any **serializable** (JSON-safe) value. If `key` is provided, only that section is updated.

{% hint style="info" %}
`key` is optional and lets you read/update just a section (safer, smaller payloads; no need to snapshot/commit the whole state if it’s large).
{% endhint %}

#### **Examples**

**Whole snapshot**

```js
const stateCopy = await zw.state.browser.getCopy();
stateCopy.step = (stateCopy.step ?? 0) + 1;
await zw.state.browser.commit({ state: stateCopy });
```

**Section-only snapshot with `key`**

```js
const prefs = (await zw.state.browser.getCopy({ key: "prefs" })) ?? {};
prefs.theme = "dark";
await zw.state.browser.commit({ key: "prefs", state: prefs });
```

#### Notes

* **JSON-safe only when using `*.browser.getCopy()` or `*.browser.commit()`.**\
  When using `*.browser.getCopy()` or `*.browser.commit()`, snapshots must be JSON-safe. Functions, Maps, circular references, and similar types cannot be serialized for browser use. If your state contains such values, use `key` to fetch only a JSON-safe section that you need in the browser.
* **In-browser snapshot** **size limits.**\
  While total state size is unlimited, passing snapshots as argument larger than **\~3,000,000 characters** fails. If your state is large, use `key` to work with just a section. Serialization of smaller objects is also faster.
* **In-browser calls require `await`.**\
  In the browser, `*.browser.getCopy()`, `*.browser.commit()`, and `*.clear()` are async.

  ```js
  // 🚫 Won't work
  const stateCopy = zw.state.browser.getCopy(); // missing await
  stateCopy.runData = {}; // throws because stateCopy is a Promise
  await zw.state.browser.commit({ state: stateCopy }); // this line never runs

  // ✅ Works
  const stateCopy = await zw.state.browser.getCopy();
  stateCopy.runData = {};
  await zw.state.browser.commit({ state: stateCopy });
  ```
* **`*.browser.*` scope is reserved for the browser only.**\
  `zw.state.browser.*` is meant for the browser context. In local runs, just use `zw.state.access()` and make a copy yourself if needed:

  ```js
  // @zw-run-locally
  const s = zw.state.access();
  const shallowCopy = { ...s };
  ```
