This January we’ve released new toolbars, icons, and better error handling.

- [Formatting toolbars](#formatting-toolbars) - Ready-made, customizable
  toolbars for Tiptap & Lexical.
- [AI toolbar](#ai-toolbar) - Rolling out AI Copilots with a smart suggestions
  toolbar for Tiptap.
- [Icon set](#icon-set) - We’re now exporting our custom-made icons for you to
  use yourself.
- [New error types](#new-error-types) - A number of uncommon errors can now be
  handled.
- [Filter threads by absent metadata](#filter-threads-by-absent-metadata) -
  Fetch threads without a certain property.
- [Great agent experience starts with collaboration](#great-agent-experience-starts-with-collaboration) -
  Why it matters for AX.

## Upgrade now

To use the latest features, update your packages with the following command.

```bash
npx create-liveblocks-app@latest --upgrade
```

If you were previously on Liveblocks 2.15 or below, make sure to follow our
[upgrade guides](/docs/platform/upgrading) before updating.

## Formatting toolbars [#formatting-toolbars]

Adding a toolbar to your [text editor](/text-editor) is now easier than ever
with our new customizable components. Make a selection in your Tiptap and
Lexical editors to see floating options for formatting your text and adding
comments.

<Figure>
  <video autoPlay loop muted playsInline>
    <source
      src="/images/blog/whats-new-in-liveblocks-january-25/toolbar-floating.mp4"
      type="video/mp4"
    />
  </video>
</Figure>

In addition, we’re also enabling static toolbars. In the video below, you can
see an example of this, along with some custom functionality, such as
highlighting and text alignment, added to the toolbar.

<Figure>
  <video autoPlay loop muted playsInline>
    <source
      src="/images/blog/whats-new-in-liveblocks-january-25/toolbar-static.mp4"
      type="video/mp4"
    />
  </video>
</Figure>

To add these toolbars to your app, simply import our pre-built components into
your text editor. All styling will match the rest of your Liveblocks components.

```tsx
// Floats next to selected text
<FloatingToolbar editor={editor} />

// Always visible and static
<Toolbar editor={editor} />
```

Find more information under
[Tiptap components](/docs/api-reference/liveblocks-react-tiptap#Default-components)
and
[Lexical components](/docs/api-reference/liveblocks-react-lexical#Default-components).

### Custom options

Both `<FloatingToolbar>` and `<Toolbar>` can be extensively customized, allowing
you to add new options, remove parts you don’t need, or completely replace the
content. For example, below we’re adding an element to the start of the toolbar.

```tsx
<Toolbar editor={editor} after={<div>Formatting</div>} />
```

You can go further than this, picking and choosing the elements you require. The
toolbar below uses the default block selector (i.e. _Text_, _Heading 1_,
_Heading 2_, etc.), then adds a vertical separator and a button with fully
custom behavior.

```tsx
<Toolbar editor={editor}>
  <Toolbar.BlockSelector />
  <Toolbar.Separator />
  <Toolbar.Button
    name="Question"
    shortcut="CMD+Q"
    icon={<Icon.QuestionMark />}
    onClick={/* ... */}
  />
</Toolbar>
```

Custom buttons and toggles will be styled like the rest of the toolbar, with
tooltips and shortcut icons. Here’s a few different ways you can use them.

```tsx
// Simple text button
<Toolbar.Button name="Question" onClick={/* ... */} />

// Using an icon replaces the text in the button
<Toolbar.Button
  name="Question"
  shortcut="CMD+Q"
  icon={<Icon.QuestionMark />}
  onClick={/* ... */}
/>

// Toggle highlighted text in Tiptap
<Toolbar.Toggle
  name="Highlight"
  icon={<div>🖊️</div>}
  active={editor?.isActive("highlight") ?? false}
  onClick={() => editor?.chain().focus().toggleHighlight().run()}
  disabled={!editor?.can().chain().focus().toggleHighlight().run()}
/>
```

You can create a custom toolbar in
[Tiptap](/docs/api-reference/liveblocks-react-tiptap#creating-a-custom-toolbar)
and
[Lexical](/docs/api-reference/liveblocks-react-lexical#creating-a-custom-toolbar).

### Updated examples

We’ve updated a number of examples with the new toolbar, such as our
[Linear-like issue tracker](/examples/linear-like-issue-tracker).

<Figure>
  <video autoPlay loop muted playsInline>
    <source
      src="/images/blog/whats-new-in-liveblocks-january-25/toolbar-linear.mp4"
      type="video/mp4"
    />
  </video>
</Figure>

Here’s every other example we’ve updated:

- [Next.js Starter Kit](/nextjs-starter-kit).
- [Advanced collaborative text editor](/examples/collaborative-text-editor-advanced).
- [Collaborative text editor](/examples/collaborative-text-editor).
- [Text editor comments](/examples/text-editor-comments/nextjs-comments-tiptap).
- [Next.js Tiptap editor](/examples/collaborative-text-editor-advanced/nextjs-tiptap-advanced).

### Get started with the new toolbars

Get started now with the new toolbar functionality.

- [Tiptap components](/docs/api-reference/liveblocks-react-tiptap#Default-components).
- [Lexical components](/docs/api-reference/liveblocks-react-lexical#Default-components).

## AI toolbar [#ai-toolbar]

We’ve been working hard on the first stage of [AI Copilots](/ai-copilots) this
January; our AI toolbar for [Text Editor](/text-editor). This toolbar enables
customizable AI actions inside Tiptap, allowing users to make quick
modifications to their documents.

<Figure>
  <video autoPlay loop muted playsInline>
    <source
      src="/images/blog/whats-new-in-liveblocks-january-25/toolbar-ai.mp4"
      type="video/mp4"
    />
  </video>
</Figure>

The toolbar’s currently only available for those that have
[requested access](/ai-copilots), and we’re going to start rolling it out to
selected partners over the coming days. To add it to your app, simply import
[`AiToolbar`](/docs/api-reference/liveblocks-react-tiptap#AiToolbar).

```tsx
<AiToolbar editor={editor} />
```

You can also easily add fully custom prompts to the toolbar, for example below
is an option to add emoji to your text.

```tsx highlight="4-6"
<AiToolbar
  editor={editor}
  suggestions={
    <AiToolbar.Suggestion prompt="Add emoji" icon={<Icon.Emoji />}>
      Emojify
    </AiToolbar.Suggestion>
  }
/>
```

Learn more under
[`AiToolbar.Suggestion`](/docs/api-reference/liveblocks-react-tiptap#AiToolbar.Suggestion).

## Icon set

To aid with creating custom toolbars, we’re now exporting our custom-made
Liveblocks icon set, used in our components. This way you can re-use icons that
are part of products, such as [Comments](/comments) and
[Notifications](/notifications).

<div className="my-8 md:my-10">
  <Figure>
    <Image
      className="opacity-90"
      src="/images/blog/whats-new-in-liveblocks-january-25/icons.svg"
      alt="Selected icons"
      width={515}
      height={73}
      quality={100}
    />
  </Figure>
</div>

Get started by importing [`Icon`](/docs/api-reference/liveblocks-react-ui#Icon)
into your component. These icons perfectly fit into toolbar buttons, toggles,
and suggestions, but can also be used elsewhere.

```tsx highlight="1,6"
import { Icon } from "@liveblocks/react-ui";

<Toolbar.Button
  name="Question"
  shortcut="CMD+Q"
  icon={<Icon.QuestionMark />}
  onClick={/* ... */}
/>;
```

## New error types [#new-error-types]

We’ve added 15 new error types to Liveblocks, allowing you to gracefully handle
any problems in your app with
[`useErrorListener`](/docs/api-reference/liveblocks-react#useErrorListener).
Here’s an example of a new error, triggered if creating a thread doesn’t work.

```tsx highlight="3-6"
useErrorListener(({ context }) => {
  switch (context.type) {
    case "CREATE_THREAD_ERROR": {
      const { roomId, threadId, commentId, body, metadata } = context;
      break;
    }

    // A number of other errors
    // ...
  }
});
```

Above, we’re highlighting a `CREATE_THREAD_ERROR` error—one reason this may
occur is if you submit thread metadata that’s too large. Find more information
about each error under
[`useErrorListener`](/docs/api-reference/liveblocks-react#useErrorListener).

## Filter threads by absent metadata [#filter-threads-by-absent-metadata]

When selecting threads, you can now filter them by the _absence_ of a metadata
property. For example, you can now filter threads that do _not_ have a `color`
attribute set in their metadata by setting `color: null`.

```ts highlight="6"
useThreads({
  query: {
    // Filter any "pinned" threads that don't have a color set
    metadata: {
      pinned: true,
      color: null,
    },
  },
});
```

Learn more about fetching threads under
[`useThreads`](/docs/api-reference/liveblocks-react#useThreads).

## Great agent experience starts with collaboration [#agent-experience-starts-with-collaboration]

Our CEO, Steven Fabre, recently wrote
[Great agent experience starts with great collaboration](/blog/great-agent-experience-great-collaboration),
an article on how agent-compatible collaborative apps will have a huge
competitive advantage. In this post, he breaks down why great agent experience
(AX) starts with great collaborative products.

<UniversalLink href="/blog/great-agent-experience-great-collaboration">
  <Figure>
    <Image
      src="/images/blog/social-images/great-agent-experience-great-collaboration.jpg"
      alt="Liveblocks AX blog post"
      width={750}
      height={394}
      quality={95}
    />
  </Figure>
</UniversalLink>

## Minor changes

Here’s a list of other improvements in our [changelog](/changelog) this month.

- Add
  [`isTextFormatActive`](/docs/api-reference/liveblocks-react-lexical#isTextFormatActive)
  and
  [`isBlockNodeActive`](/docs/api-reference/liveblocks-react-lexical#isBlockNodeActive)
  utilities to Lexical.
- Automatically refresh Comments and Notifications data when the browser window
  regains focus.
- Improve error message if hooks are accidentally called server-side.
- Fix `useLayoutEffect` warnings when using React versions lower than `18.3.0`
  and SSR.
- Fix memory leak in some hooks.
- Fix bug where querying metadata with `useThreads()` would not always reuse the
  cache correctly.
- Fix download comments attachments action in text editor view from room detail
  page.
- Fix bug in `Zustand` typing in case the multi-argument form of `set()` is
  used.
- Dashboard: Allow rate limit configuration for webhook endpoints.
- Examples: Updated [Next.js Starter Kit](/nextjs-starter-kit) to use the new
  `FloatingToolbar`.
- Examples: Updated [text editor examples](/examples/browse/text-editor) to use
  the new `FloatingToolbar`.
- Docs:
  [`@liveblocks/node-lexical`](/docs/api-reference/liveblocks-node-lexical)
  documentation has been improved.
- Docs: Added missing `409` response to
  [Initialize Storage REST API](/docs/api-reference/rest-api-endpoints#post-rooms-roomId-storage).
- Docs: Better info on `initialStorage` values.
- Docs: Typo fixed in custom notification snippet.

## Upgrade

To use the latest features, update your packages with the following command.

```bash
npx create-liveblocks-app@latest --upgrade
```

If you were previously on Liveblocks 2.15 or below, make sure to follow our
[upgrade guides](/docs/platform/upgrading) before updating.

## Contributors

<Contributors
  gitHubUsernames={[
    "nvie",
    "ctnicholas",
    "marcbouchenoire",
    "jrowny",
    "sugardarius",
    "jltimm",
    "stevenfabre",
    "hans-lizihan",
  ]}
/>