> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ably/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# LiveMap

`LiveMap` is a synchronized key/value data structure that stores primitive values, such as numbers, strings, booleans, binary data, JSON-serializable objects or arrays and other live object types. It ensures that all updates are correctly applied and synchronized across clients in realtime, automatically resolving conflicts with last-write-wins (LWW) semantics.

You interact with `LiveMap` through a PathObject or by obtaining a specific Instance.

## Create a map

Create a `LiveMap` using the `LiveMap.create()` static method and assign it to a path:

```javascript theme={null}
import { LiveMap } from 'ably/liveobjects';

// Create an empty map
await myObject.set('settings', LiveMap.create());

// Create a map with initial data
await myObject.set('user', LiveMap.create({
  name: 'Alice',
  score: 42,
  active: true
}));

// Create nested maps
await myObject.set('config', LiveMap.create({
  theme: LiveMap.create({
    color: 'dark',
    fontSize: 14
  })
}));
```

`LiveMap.create()` returns a value type that describes the initial data for a new map. The actual object is created when assigned to a path. Each assignment creates a distinct object with its own unique ID:

```javascript theme={null}
const mapValue = LiveMap.create({ theme: 'dark' });

// Each assignment creates a different object
await myObject.set('map1', mapValue);
await myObject.set('map2', mapValue);

// map1 and map2 are different objects with different IDs
const id1 = myObject.get('map1').instance()?.id();
const id2 = myObject.get('map2').instance()?.id();
console.log(id1 === id2); // false
```

## Get map values

Access a `LiveMap` through a PathObject for path-based operations, or obtain a specific Instance to work with the underlying object directly. Use the `get()` method to navigate to entries within the map.

What you can do with the result depends on the type of value stored in the entry:

* For entries containing primitive values or `LiveCounter` objects, use the `value()` method to get the current value.
* For entries containing nested `LiveMap` objects, use `get()` to continue navigating deeper into the structure.

```javascript theme={null}
const myObject = await channel.object.get();

// PathObject access: path-based operations that resolve at runtime
const theme = myObject.get('settings').get('theme'); // settings holds a LiveMap with a 'theme' key
console.log(theme.value()); // e.g. 'dark'
const visits = myObject.get('visits'); // visits holds a LiveCounter
console.log(visits.value()); // e.g. 5

// Instance access: reference to a specific map object
const settingsInstance = myObject.get('settings').instance();
console.log(settingsInstance?.get('theme')?.value()); // e.g. 'dark' (primitive string)
```

<Aside data-type="note">
  Calling `value()` on a `PathObject` may return `undefined` if a primitive value or `LiveCounter` instance cannot be found at that path. Calling `value()` on an `Instance` returns `undefined` if the instance is not a primitive or `LiveCounter`.
</Aside>

## Get compact object

Get a JavaScript object representation of the map using the `compact()` or `compactJson()` methods:

```javascript theme={null}
// Get a PathObject to a LiveMap stored in 'settings'
const settings = myObject.get('settings');
console.log(settings.compact());
// e.g. { theme: 'dark', fontSize: 14, notifications: true }

// Get the Instance of a LiveMap stored in 'settings'
const settingsInstance = myObject.get('settings').instance();
console.log(settingsInstance?.compact());
// e.g. { theme: 'dark', fontSize: 14, notifications: true }
```

## Set map values

Set a value for a key in the map using the `set()` method. You can store primitive values or other live object types:

```javascript theme={null}
// PathObject: set values at path
await myObject.get('settings').set('theme', 'dark');
await myObject.get('settings').set('fontSize', 14);
await myObject.get('settings').set('notifications', true);

// Set a nested map
await myObject.get('settings').set('advanced', LiveMap.create({
  debugMode: false,
  logLevel: 'info'
}));

// Set a counter
await myObject.get('settings').set('visits', LiveCounter.create(0));

// Instance: set values on specific map instance
const settingsInstance = myObject.get('settings').instance();
await settingsInstance?.set('theme', 'dark');
await settingsInstance?.set('fontSize', 14);
```

<Aside data-type="note">
  A `LiveMap` can store other `LiveMap` or `LiveCounter` objects as values for its keys, enabling you to build a complex, hierarchical channel object. This enables you to represent complex data models in your applications while ensuring realtime synchronization across clients.
</Aside>

## Remove a key

Remove a key from the map using the `remove()` method:

```javascript theme={null}
// PathObject: remove key at path
await myObject.get('settings').remove('oldSetting');

// Instance: remove key on specific map instance
const settingsInstance = myObject.get('settings').instance();
await settingsInstance?.remove('oldSetting');
```

## Get map size

Get the number of keys in the map using the `size()` method:

```javascript theme={null}
// PathObject: get size at path
const settings = myObject.get('settings');
console.log(settings.size()); // e.g. 5

// Instance: get size of specific map instance
const settingsInstance = myObject.get('settings').instance();
console.log(settingsInstance?.size()); // e.g. 5
```

<Aside data-type="note">
  Calling `size()` on a `PathObject` may return `undefined` if a `LiveMap` instance cannot be found at that path. Calling `size()` on an `Instance` returns `undefined` if the instance is not a `LiveMap`.
</Aside>

## Enumerate entries

Iterate over the map's keys, values, or entries. When iterating using a `PathObject`, values are returned as a `PathObject` for the nested path. When iterating using an `Instance`, values are returned as an `Instance` for the entry:

```javascript theme={null}
// PathObject: iterate with PathObject values
const settings = myObject.get('settings');
for (const [key, value] of settings.entries()) {
  console.log(`${key}:`, value.value());
}
for (const key of settings.keys()) {
  console.log('Key:', key);
}
for (const value of settings.values()) {
  console.log('Value:', value.value());
}

// Instance: iterate with Instance values
const settingsInstance = myObject.get('settings').instance();
if (settingsInstance) {
  for (const [key, valueInstance] of settingsInstance.entries()) {
    console.log(`${key}:`, valueInstance.value());
  }
  for (const key of settingsInstance.keys()) {
    console.log('Key:', key);
  }
  for (const value of settingsInstance.values()) {
    console.log('Value:', value.value());
  }
}
```

<Aside data-type="note">
  These methods don't guarantee entries are returned in insertion order.
</Aside>

## Batch multiple operations

Group multiple map operations into a single atomic message using the `batch()` method. All operations within the batch are sent as one logical unit which succeed or fail together:

```javascript theme={null}
// PathObject: batch operations on map at path
await myObject.get('settings').batch((ctx) => {
  ctx.set('theme', 'dark');
  ctx.set('fontSize', 14);
  ctx.set('notifications', true);
  ctx.remove('oldSetting');
});

// Instance: batch operations on specific map instance
const settingsInstance = myObject.get('settings').instance();
await settingsInstance?.batch((ctx) => {
  ctx.set('theme', 'dark');
  ctx.set('fontSize', 14);
});
```

<Aside data-type="further-reading">
  Learn more about [Batch operations](/docs/liveobjects/batch-operations).
</Aside>

## Subscribe to updates

Subscribe to `LiveMap` updates to receive realtime notifications when the map changes.

`PathObject` subscriptions observe a location and automatically track changes even if the `LiveMap` instance at that path is replaced. `Instance` subscriptions track a specific `LiveMap` instance, following it even if it moves in the channel object.

```javascript theme={null}
// PathObject: observe location - tracks changes even if map instance is replaced
const settings = myObject.get('settings');
const { unsubscribe } = settings.subscribe(() => {
  console.log('Settings:', settings.compact());
});

// Later, stop listening to changes
unsubscribe();

// Instance: track specific map instance - follows it even if moved in object tree
const settingsInstance = myObject.get('settings').instance();
if (settingsInstance) {
  const { unsubscribe } = settingsInstance.subscribe(() => {
    console.log('Settings:', settingsInstance.compact());
  });

  // Later, stop listening to changes
  unsubscribe();
}
```

Alternatively, use the `subscribeIterator()` method for an async iterator syntax:

```javascript theme={null}
// PathObject: observe location - tracks changes even if map instance is replaced
const settings = myObject.get('settings');
for await (const _ of settings.subscribeIterator()) {
  console.log('Settings:', settings.compact());

  if (someCondition) {
    break; // Unsubscribes
  }
}

// Instance: track specific map instance - follows it even if moved in object tree
const settingsInstance = myObject.get('settings').instance();
if (settingsInstance) {
  for await (const _ of settingsInstance.subscribeIterator()) {
    console.log('Settings:', settingsInstance.compact());

    if (someCondition) {
      break; // Unsubscribes
    }
  }
}
```
