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

# Blocks

> Add structured blocks and inline blocks to FrogBot's rich text editor.

The `BlocksFeature` embeds reusable [Block configurations](https://docs.frogbot.ai/fields/blocks.md) in rich text. Blocks support the same fields, hooks, validation, access control, and conditional logic as a Blocks field, but their data is stored inside the rich text JSON.

## Basic setup

Define blocks separately so their schemas can be shared with regular Blocks fields:

```ts theme={"theme":"tokyo-night"}
import { BlocksFeature, lexicalEditor } from '@frogbotai/richtext-lexical'
import type { Block, RichTextField } from 'frogbot'

export const BannerBlock: Block = {
  slug: 'banner',
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'style',
      type: 'select',
      defaultValue: 'info',
      options: ['info', 'warning', 'error', 'success'],
    },
    {
      name: 'content',
      type: 'textarea',
      required: true,
    },
  ],
}

export const CallToActionBlock: Block = {
  slug: 'callToAction',
  fields: [
    {
      name: 'heading',
      type: 'text',
      required: true,
    },
    {
      name: 'link',
      type: 'text',
    },
  ],
}

export const contentField: RichTextField = {
  name: 'content',
  type: 'richText',
  editor: lexicalEditor({
    features: ({ defaultFeatures }) => [
      ...defaultFeatures,
      BlocksFeature({
        blocks: [BannerBlock, CallToActionBlock],
      }),
    ],
  }),
}
```

See [Blocks fields](https://docs.frogbot.ai/fields/blocks.md) for every block configuration option.

## BlocksFeature options

| Option | Type | Default | Description |
| - | - | - | - |
| `blocks` | `(Block \| string)[]` | `[]` | Block-level blocks editors can insert. A string references a block by slug from the root `blocks` array in your FrogBot config; an unknown slug throws when the config loads. |
| `inlineBlocks` | `(Block \| string)[]` | `[]` | Inline blocks editors can insert within text. Strings reference root `blocks` by slug, as for `blocks`. |

## Blocks and inline blocks

Regular blocks are block-level elements. They occupy their own line and work well for call-to-action sections, galleries, code, and embedded content.

Inline blocks flow within text. Use them for mentions, badges, variables, icons, and footnote references.

```ts theme={"theme":"tokyo-night"}
import { BlocksFeature } from '@frogbotai/richtext-lexical'
import type { Block } from 'frogbot'

const CalloutBlock: Block = {
  slug: 'callout',
  fields: [{ name: 'content', type: 'textarea' }],
}

const MentionBlock: Block = {
  slug: 'mention',
  fields: [
    {
      name: 'username',
      type: 'text',
      required: true,
    },
  ],
}

export const blocksFeature = BlocksFeature({
  blocks: [CalloutBlock],
  inlineBlocks: [MentionBlock],
})
```

If a block contains another rich text field, configure that nested field with an explicit `editor`. This keeps its feature set deliberate and avoids recursively inheriting the root editor's block configuration.

## Stored data

Each block node contains a `fields` object with the block's field values. FrogBot generates `id`; `blockType` identifies the configured block; and editors can set the optional `blockName` label.

```json theme={"theme":"tokyo-night"}
{
  "type": "block",
  "version": 2,
  "fields": {
    "id": "65298b13db4ef8c744a7faaa",
    "blockType": "banner",
    "blockName": "Important notice",
    "style": "warning",
    "content": "This is the block content."
  }
}
```

Inline blocks use the same shape with `"type": "inlineBlock"`.

## Custom block components

Set `admin.components.Block` to replace a block's editor UI. Component paths resolve from the configured import-map base directory and can include a named export. See [Component paths](https://docs.frogbot.ai/custom-components/overview.md#component-paths).

```ts theme={"theme":"tokyo-night"}
import type { Block } from 'frogbot'

export const BannerBlock: Block = {
  slug: 'banner',
  admin: {
    components: {
      Block: '/components/BannerBlock#BannerBlock',
    },
  },
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'style',
      type: 'select',
      options: ['primary', 'secondary'],
    },
    {
      name: 'content',
      type: 'textarea',
    },
  ],
}
```

### Client block components

Client components can compose the standard editor controls. `BlockCollapsible` provides the field drawer and default controls; disable its remove button when rendering `BlockRemoveButton` yourself.

```tsx theme={"theme":"tokyo-night"}
'use client'

import type { LexicalBlockClientProps } from '@frogbotai/richtext-lexical'
import {
  BlockCollapsible,
  BlockEditButton,
  BlockRemoveButton,
} from '@frogbotai/richtext-lexical/client'
import { useFormFields } from '@frogbotai/ui/admin'

export function BannerBlock(_props: LexicalBlockClientProps) {
  const content = useFormFields(([fields]) => fields.content?.value)
  const style = useFormFields(([fields]) => fields.style?.value)

  return (
    <BlockCollapsible removeButton={false}>
      <div
        style={{
          background: 'var(--color-bg-secondary)',
          border: '1px solid var(--color-border)',
          borderRadius: 8,
          color: 'var(--color-text)',
          padding: 16,
        }}
      >
        <strong>{String(style ?? 'info')} banner</strong>
        <p>{String(content ?? 'No content')}</p>
        <div style={{ display: 'flex', gap: 8 }}>
          <BlockEditButton />
          <BlockRemoveButton />
        </div>
      </div>
    </BlockCollapsible>
  )
}
```

You can omit `BlockCollapsible` and render a completely custom layout while retaining the edit and remove controls.

### Server block components

Server component props carry a `FrogBotRequest`. Use `req.frogbot` for server operations:

```tsx theme={"theme":"tokyo-night"}
import type { LexicalBlockServerProps } from '@frogbotai/richtext-lexical'

export async function BannerBlock({ path, req }: LexicalBlockServerProps) {
  const currentUser = req.user
  const files = await req.frogbot.find({
    collection: 'files',
    overrideAccess: true,
    limit: 1,
    req,
  })

  return (
    <section>
      <strong>{currentUser?.email ?? 'Editor'}</strong>
      <span>{files.totalDocs} files available</span>
      <span>Editing {path}</span>
    </section>
  )
}
```

The same client/server choice is available for regular blocks, inline blocks, and their labels.

## Inline block components

Inline block primitives provide the default container, label, edit button, and remove button:

```tsx theme={"theme":"tokyo-night"}
'use client'

import type { LexicalInlineBlockClientProps } from '@frogbotai/richtext-lexical'
import {
  InlineBlockContainer,
  InlineBlockEditButton,
  InlineBlockLabel,
  InlineBlockRemoveButton,
} from '@frogbotai/richtext-lexical/client'

export function MentionBlock(_props: LexicalInlineBlockClientProps) {
  return (
    <InlineBlockContainer>
      <InlineBlockLabel />
      <InlineBlockEditButton />
      <InlineBlockRemoveButton />
    </InlineBlockContainer>
  )
}
```

Compose only the controls you need when rendering a custom inline layout:

```tsx theme={"theme":"tokyo-night"}
'use client'

import type { LexicalInlineBlockClientProps } from '@frogbotai/richtext-lexical'
import {
  InlineBlockEditButton,
  InlineBlockRemoveButton,
} from '@frogbotai/richtext-lexical/client'
import { useFormFields } from '@frogbotai/ui/admin'

export function MentionBadge(_props: LexicalInlineBlockClientProps) {
  const username = useFormFields(([fields]) => fields.username?.value)

  return (
    <span
      style={{
        alignItems: 'center',
        background: 'var(--color-bg-tertiary)',
        borderRadius: 12,
        color: 'var(--color-text)',
        display: 'inline-flex',
        gap: 4,
        padding: '2px 8px',
      }}
    >
      @{String(username ?? 'username')}
      <InlineBlockEditButton />
      <InlineBlockRemoveButton />
    </span>
  )
}
```

## Label components

Set `admin.components.Label` to display block values in the block header.

```tsx theme={"theme":"tokyo-night"}
'use client'

import type {
  LexicalBlockLabelClientProps,
  LexicalInlineBlockLabelClientProps,
} from '@frogbotai/richtext-lexical'
import { useFormFields } from '@frogbotai/ui/admin'

export function BannerLabel(_props: LexicalBlockLabelClientProps) {
  const title = useFormFields(([fields]) => fields.title?.value)

  return <span>Banner: {String(title ?? 'Untitled')}</span>
}

export function MentionLabel(_props: LexicalInlineBlockLabelClientProps) {
  const username = useFormFields(([fields]) => fields.username?.value)

  return <span>Mention: @{String(username ?? 'username')}</span>
}
```

Configure these exports under `admin.components.Label`. See [custom block components](https://docs.frogbot.ai/fields/blocks.md#custom-components) for the complete block configuration shape.

## Component prop types

Import all component prop types from the package root:

```ts theme={"theme":"tokyo-night"}
import type {
  LexicalBlockClientProps,
  LexicalBlockLabelClientProps,
  LexicalBlockLabelServerProps,
  LexicalBlockServerProps,
  LexicalInlineBlockClientProps,
  LexicalInlineBlockLabelClientProps,
  LexicalInlineBlockLabelServerProps,
  LexicalInlineBlockServerProps,
} from '@frogbotai/richtext-lexical'
```

| Type | Use |
| - | - |
| `LexicalBlockClientProps` | Client `admin.components.Block` for a regular block |
| `LexicalBlockServerProps` | Server `admin.components.Block` for a regular block |
| `LexicalBlockLabelClientProps` | Client `admin.components.Label` for a regular block |
| `LexicalBlockLabelServerProps` | Server `admin.components.Label` for a regular block |
| `LexicalInlineBlockClientProps` | Client `admin.components.Block` for an inline block |
| `LexicalInlineBlockServerProps` | Server `admin.components.Block` for an inline block |
| `LexicalInlineBlockLabelClientProps` | Client `admin.components.Label` for an inline block |
| `LexicalInlineBlockLabelServerProps` | Server `admin.components.Label` for an inline block |

The editor renders block and label components as the custom component of an internal UI field that sits alongside the block's fields. Their `field` and `path` props describe that internal field, not the block. Read block values with `useFormFields` in a Client Component, or from `data` in a Server Component.

The four client types share these props:

| Prop | Type | Default | Description |
| - | - | - | - |
| `field` | `ClientField` | Always provided | Serializable configuration of the internal UI field. |
| `path` | `string` | Always provided | Form path of the internal UI field within the block form. |
| `schemaPath` | `string` | May be `undefined` | Schema path of the internal UI field. |
| `readOnly` | `boolean` | May be `undefined` | Whether the editor is read-only for the current user or document state. |
| `permissions` | `SanitizedFieldPermissions` | May be `undefined` | Field permissions for the current user. |
| `forceRender` | `boolean` | May be `undefined` | Renders nested fields immediately instead of waiting until they scroll into view. |
| `renderedBlocks` | `RenderedField[]` | May be `undefined` | Pre-rendered nested block components supplied by the form. |

The four server types share these props:

| Prop | Type | Default | Description |
| - | - | - | - |
| `req` | `FrogBotRequest` | Always provided | Current request. Use `req.frogbot` for server operations and `req.user` for access checks. |
| `data` | `Record<string, unknown>` | Always provided | The block's field values. |
| `siblingData` | `Record<string, unknown>` | Always provided | Values of the fields next to the internal UI field, which are the block's field values. |
| `value` | `unknown` | May be `undefined` | Value of the internal UI field. UI fields store no data, so this is normally `undefined`. |
| `field` | `UIField` | Always provided | Authored configuration of the internal UI field. |
| `clientField` | `ClientField` | Always provided | Serializable configuration of the internal UI field, for passing to a Client Component. |
| `path` | `string` | Always provided | Form path of the internal UI field within the block form. |
| `schemaPath` | `string` | May be `undefined` | Schema path of the internal UI field. |
| `id` | `string \| number` | May be `undefined` | ID of the document being edited. |
| `collectionSlug` | `string` | Always provided | Slug of the collection that owns the document. |
| `operation` | `'create' \| 'read' \| 'update' \| 'delete'` | Always provided | Form operation. Block forms use `'update'`. |
| `user` | `User` | Always provided | Authenticated user with field read access applied. Use it for values sent to the browser and `req.user` for access checks. |
| `i18n` | `I18nClient` | Always provided | Admin Panel translations for the current language. |
| `permissions` | `SanitizedFieldPermissions` | Always provided | Field permissions for the current user. |
| `preferences` | `DocumentPreferences` | Always provided | Saved Admin Panel preferences for the document. |
| `formState` | `FormState` | Always provided | Form state of the block's fields. |
| `fieldSchemaMap` | `FieldSchemaMap` | Always provided | Map from schema paths to authored field configurations. |
| `clientFieldSchemaMap` | `ClientFieldSchemaMap` | Always provided | Map from schema paths to serializable field configurations. |
| `customComponents` | `{ AfterInput?, BeforeInput?, Description?, Error?, Field?, Label? }` | May be `undefined` | Rendered custom components of the internal UI field. |
| `readOnly` | `boolean` | May be `undefined` | Whether the editor is read-only for the current user or document state. |
| `forceRender` | `boolean` | May be `undefined` | Renders nested fields immediately instead of waiting until they scroll into view. |
| `renderedBlocks` | `RenderedField[]` | May be `undefined` | Pre-rendered nested block components supplied by the form. |

## Rendering blocks

Frontend converters map each block slug to its rendered output:

* [JSX converters](https://docs.frogbot.ai/rich-text/converting-jsx.md#lexical-blocks) for React and Next.js
* [HTML converters](https://docs.frogbot.ai/rich-text/converting-html.md#blocks-to-html) for HTML output
* [Markdown converters](https://docs.frogbot.ai/rich-text/converting-markdown.md#defining-a-custom-block) for Markdown output

## CodeBlock

`CodeBlock` is a ready-made block with syntax highlighting, language selection, and optional TypeScript diagnostics. Import it from the package root:

```ts theme={"theme":"tokyo-night"}
import { BlocksFeature, CodeBlock } from '@frogbotai/richtext-lexical'

export const codeFeature = BlocksFeature({
  blocks: [
    CodeBlock({
      defaultLanguage: 'ts',
      languages: {
        plaintext: 'Plain text',
        js: 'JavaScript',
        ts: 'TypeScript',
        tsx: 'TSX',
        jsx: 'JSX',
      },
    }),
  ],
})
```

### CodeBlock options

| Option | Type | Default | Description |
| - | - | - | - |
| `slug` | `string` | `'Code'` | Block slug. |
| `languages` | `Record<string, string>` | Built-in list of about 80 languages | Maps language keys to labels shown in the language selector. The selected key is passed to the code editor as the block's language. |
| `defaultLanguage` | `string` | First key in `languages` | Language selected for a new block. |
| `typescript` | `object` | None | TypeScript definitions and diagnostics for the code editor. See [TypeScript support](#typescript-support). |
| `fieldOverrides` | `Partial<Block>` | None | Properties shallow-merged over the generated block configuration. A key you set, such as `admin` or `fields`, replaces the generated value entirely. |

### TypeScript support

`fetchTypes` downloads declaration files into the editor's virtual file system. Every imported declaration dependency must also be loaded. Pin versions so a later package release cannot change the editor environment unexpectedly.

This example loads React's declarations plus its `csstype` dependency:

```ts theme={"theme":"tokyo-night"}
import { CodeBlock } from '@frogbotai/richtext-lexical'

export const TypedCodeBlock = CodeBlock({
  languages: {
    ts: 'TypeScript',
    tsx: 'TSX',
  },
  typescript: {
    fetchTypes: [
      {
        url: 'https://unpkg.com/@types/react@19.1.11/index.d.ts',
        filePath: 'file:///node_modules/@types/react/index.d.ts',
      },
      {
        url: 'https://unpkg.com/@types/react@19.1.11/global.d.ts',
        filePath: 'file:///node_modules/@types/react/global.d.ts',
      },
      {
        url: 'https://unpkg.com/csstype@3.1.3/index.d.ts',
        filePath: 'file:///node_modules/csstype/index.d.ts',
      },
    ],
    paths: {
      csstype: ['file:///node_modules/csstype/index.d.ts'],
      react: ['file:///node_modules/@types/react/index.d.ts'],
    },
    typeRoots: ['node_modules/@types'],
    target: 'ES2022',
    enableSemanticValidation: true,
  },
})
```

FrogBot publishes normal package declarations at `dist/index.d.ts`; it does not publish a single bundled declaration file. To offer FrogBot APIs in the editor, pin and load that declaration plus every declaration it imports, or publish a self-contained declaration bundle from your application. Do not point `fetchTypes` at an unverified `index.bundled.d.ts` URL.

| Option | Type | Default | Description |
| - | - | - | - |
| `fetchTypes` | `{ url: string; filePath: string }[]` | None | Declaration files downloaded when the editor mounts. Each `url` is fetched and added to the editor's virtual file system at `filePath`. |
| `paths` | `Record<string, string[]>` | None | Module mappings used to resolve imports, as in a `tsconfig.json` `paths` setting. |
| `typeRoots` | `string[]` | `['node_modules/@types']` | Virtual directories searched for type packages. |
| `target` | `string` | `'ESNext'` | TypeScript compilation target, such as `'ES2022'`. |
| `enableSemanticValidation` | `boolean` | `false` | Enables type checking in addition to syntax validation. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.