BookbagBookbag

Code editor

Write and refine widgets by hand using the JSX-like markup language, a typed data schema, example data for the live preview, and interactive functions.

View as Markdown

The code editor gives you full control over a widget. You write the UI in a JSX-like markup language over Bookbag's component library, declare the data the widget expects, provide example data for the live preview, and define the functions that run when a customer interacts.

The four parts of a widget

PartWhat it does
CodeThe JSX-like markup — the layout and components the widget renders.
SchemaThe typed data the widget expects, as a Zod schema with a JSON mirror.
Example dataSample values used to render the preview and to fall back on at runtime.
Functions & statesBehavior for interactive elements, and conditional visibility (e.g. a "submitted" state).

The markup language

Widget markup looks like JSX but is not React — it is parsed and evaluated by a safe interpreter (no eval). You can reference data fields, use ternaries and .map() to repeat over arrays, and compose the built-in components. Here is a minimal lead form:

<Card size="md" asForm>
  <Col gap={4}>
    <Title value={title} size="lg" weight="medium" />
    <Text value={subtitle} size="sm" color="secondary" />
    <Divider />
    <Col gap={2}>
      <Label value="Work email" fieldName="email" />
      <Input name="email" inputType="email" placeholder="you@example.com" required />
    </Col>
    <Button label="Submit" submit variant="solid" color="primary" block
      onClickAction={{ functionName: "submitLead" }} />
  </Col>
</Card>
Repeat over a list with .map()

Render one component per item in an array — exactly like JSX — to build carousels, lists, and tables from data.

<ListView orientation="horizontal" gap={2}>
  {products.map((product) => (
    <ListViewItem key={product.id}>
      <Col gap={2} width={240}>
        <Image src={product.image} height={190} fit="cover" radius="md" />
        <Title value={product.name} size="xs" weight="semibold" />
        <Text value={product.price} size="md" weight="medium" />
        <Button label="Add to cart" block
          onClickAction={{ functionName: "addToCart",
            additionalInputs: { productId: product.id } }} />
      </Col>
    </ListViewItem>
  ))}
</ListView>

The data schema

The schema declares what data the widget needs. It is written as a Zod schema and mirrored as JSON, so the AI builder, your actions, and the agent all agree on the shape. The .describe() text helps the model populate fields correctly at runtime.

z.object({
  title: z.string().describe("Heading"),
  subtitle: z.string().describe("Subheading"),
  products: z.array(z.object({
    id: z.string(),
    name: z.string(),
    price: z.string(),
    image: z.string(),
  })).describe("Products to display"),
})

Functions: making it interactive

Buttons and inputs trigger functions. A function has a type that decides what happens when it runs:

Function typeWhat it does
messageSends a message back into the chat (e.g. a templated lead summary).
linkOpens a URL.
setVariablesUpdates the widget's own data — used with states to show/hide sections.
dismissCloses the widget.
server / clientCalls your API (server) or delegates to the host (client) for custom behavior.
{
  submitLead: {
    type: "message",
    messageTemplate: "New lead from {{name}} ({{email}})",
    onExecute: { action: "dismiss" }
  }
}

States: conditional UI

A state shows or hides part of the widget based on its data. A common pattern is a form that flips to a confirmation after submit: wrap the form in a FormView state and the thank-you in a Submitted state, each shown conditionally on a data field.

info

States are written as tag wrappers whose name matches a declared state (for example <Submitted>…</Submitted>). The renderer shows only the states whose conditions are met by the current data.

Live preview and example data

The editor renders a live preview from your example data, so you see the widget exactly as a customer would — themed light or dark to match the chat. Add named examples to preview specific states (for instance, an example with submitted: true to see the confirmation view).

Import, export, and duplicate

You can duplicate any widget to branch from a working version, export a widget to move it between agents or workspaces, and import one back in. Templates are just exported widgets you can duplicate and edit.

What's next