> For the complete documentation index, see [llms.txt](https://docs.zerowork.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zerowork.io/using-zerowork/using-building-blocks/write-javascript/write-and-read-variables-and-tables.md).

# Write and Read Variables and Tables

Read and write persistent values in variables and tables with `zw.getRef()` and `zw.setRef()`.

**API** `zw.getRef()`, `zw.setRef()`

```js
// Get a value from a table column or a variable
const profileName = await zw.getRef({ ref_id: 5079, name: "Profile Name" });

// Save a value to a table column or a variable
await zw.setRef({ ref_id: 4372, name: "Profile Name Copy", value: profileName });
```

***

#### At a Glance

* <mark style="color:$info;">async</mark>\
  `await zw.getRef({ ref_id: number, name: string })` → `string`\
  → returns the `string` value from a variable or table column.
* <mark style="color:$info;">async/sync\*</mark> \
  `await zw.setRef({ ref_id: number, name: string, value: string })` → `void`\
  → saves a `string` value to a variable or table column.

\*`async` in browser, `sync` locally (see [/pages/MF29c3EkhotatZrl92B3#local-vs.-browser-execution](https://docs.zerowork.io/using-zerowork/using-building-blocks/write-javascript/pages/MF29c3EkhotatZrl92B3#local-vs.-browser-execution "mention")).

***

#### What’s `ref_id`?

In ZeroWork, **tables** and **variables** are data groups. Each data group has an ID called `ref_id`.

* **Table `ref_id`** is shown in the left sidebar next to the table name.\
  ![](/files/EPgOAFhjkO7QnaCqF2NU)<br>

* **Variables `ref_id`** is shown in the Variables dialog.

  <figure><img src="/files/2att8rQSEuhNYijBxapq" alt=""><figcaption></figcaption></figure>

* **`name`** identifies a table column or a variable in the Variables group. Names are **case-sensitive**.

  ```javascript
  // Read a table column by its table ref_id and column name
  const profileName = await zw.getRef({ ref_id: 5079, name: "Profile Name" });

  // Same variables ref_id for different variable names
  const date = await zw.getRef({ ref_id: 4379, name: "date" });
  const counter = await zw.getRef({ ref_id: 4379, name: "counter" });
  ```

***

#### Saving Non-String Values

Values are stored as **strings**. To store arrays or objects, **stringify on write** and **parse on read**. Passing a non-string `value` to `zw.setRef()` throws an error.

```javascript
// 🚫 Won't work (value must be a string)
await zw.setRef({ ref_id: 4379, name: "chars", value: ["a", "b", "c"] });
// Error: The value argument must be a string.
```

**Example**

```js
// Save objects or arrays (stringify on write)
await zw.setRef({
  ref_id: 4379,
  name: "animals",
  value: JSON.stringify([{ category: "cat" }, { category: "dog" }]),
});

// Read and use it (parse on read)
const animalsArray = JSON.parse(
  await zw.getRef({ ref_id: 4379, name: "animals" })
);
```

***

#### Loops and Row Behavior

`zw.getRef()` and `zw.setRef()` follow ZeroWork’s loop logic:

* **Read existing rows** → place **Start Repeat** with **dynamic** loop **before** your Write JavaScript building block.
* **Append new rows** → place **Start Repeat** with **standard** loop before the block **or** use a custom append with `appendIndex`.

Read more about how loops work in ZeroWork: [Start Repeat](/using-zerowork/using-building-blocks/start-repeat.md).&#x20;

**Custom append without Start Repeat**

Use `appendIndex` to add rows from a loop in your code.\
`appendIndex: 0` is the first appended row. If a table already has 3 rows, `appendIndex: 0` becomes the 4th row.

> ⚠️ When using `appendIndex`, placing this Write JavaScript building block inside a Start Repeat loop can mix the built-in loop index with a custom `appendIndex`, causing mismatched rows.

**Example**

```js
const valuesToSave = ["a", "b", "c"];
const TABLE_REF_ID = 4382;

let i = 0;
for (const val of valuesToSave) {
  await zw.setRef({
    ref_id: TABLE_REF_ID,
    name: "value",
    value: val,
    appendIndex: i,
  });
  i++;
}
```

The resulting table:\
![](/files/u21KrkZtKpbfhSCN4eZt)

**Update existing rows in a custom code loop**

Add a **dynamic** Start Repeat building block to iterate rows and use the Write JavaScript building block within that loop. As long as your Write JavaScript is inside that loop, it will have access to the values inside the row that is currently being iterated over. If you need true in-code row access, share your use case in [Discord](https://discord.gg/JkkCNmVBeg) in the #ideas-feature-suggestions channel.

***

#### Common Pitfalls

* **Always use `await` in the browser.**\
  In the browser, `zw.getRef()` and `zw.setRef()` are async — use `await`.&#x20;

  ```javascript
  // 🚫 Won't work (running in the browser)
  const name = zw.getRef({ ref_id: 5079, name: "Profile Name" }); // missing await → name is a Promise, not a string
  await zw.setRef({ ref_id: 4379, name: "Copy", value: name }); // throws error
  // Error: The value argument must be a string.

  // ✅ Works
  const name2 = await zw.getRef({ ref_id: 5079, name: "Profile Name" });
  await zw.setRef({ ref_id: 4379, name: "Copy", value: name2 });
  ```
* **Match the exact name (case-sensitive).**

  ```javascript
  // 🚫 Won't work (case mismatch)
  await zw.getRef({ ref_id: 5079, name: "profile name" });
  // Error: This variable or column does not exist.

  // ✅ Works
  await zw.getRef({ ref_id: 5079, name: "Profile Name" });
  ```
