Skip to main content

Widgets [Experimental]

Enable Widgets

Widgets are an experimental feature and need to be turned on before you can use them:

  1. Click your profile and go to Preferences.
  2. Open the Experimental features tab.
  3. Toggle Widget nodes on.

Widgets are interactive, declarative UI nodes you can drop onto the Fused canvas. Unlike UDFs that run Python, widgets are defined in JSON — no code required. They can be inputs (sliders, dropdowns, text inputs) that drive parameters across the canvas, or outputs (charts, maps, tables, big numbers) that display live data from any UDF. Every widget is shareable as a standalone URL, embeddable anywhere, and fully accessible to AI for generation and editing.

With Widgets, you can:

  • Drive UDFs with inputs — Sliders, dropdowns, and text inputs broadcast values to every UDF on the canvas.
  • Visualize UDF output — Output widgets render charts, tables, maps, and KPIs from any UDF.
  • Query with SQL — Widgets run SQL over UDF results in-browser via DuckDB WASM.
  • Dynamically generate interactive UI — Pass valid widget JSON via a URL parameter to update the UI on the fly (see Anatomy of a Widget URL).

Widgets overview

Changes in the input widget make all the downstream UDFs and output widgets re-run.

Structure of a Widget

Every widget is a small JSON object with two required keys: type and props.

{
"type": "<widget-type>",
"props": {
"sql": "SELECT ... FROM {{my_udf}} WHERE value > $my_param",
"title": "My Chart",
"param": "my_param"
}
}

See all the available widget types in the Available Widgets section below.

How to create and use a Widget

Step 1: Drop a Widget Node

In the Fused Workbench canvas, click the Widget icon in the toolbar to add one, then click it to open the JSON editor in the right panel.

{
"type": "<widget-type>",
"props": { ... }
}

Step 2: Connect it to a UDF

Show the orders_df UDF code
@fused.udf
def udf(selected_option: str = "orders"):
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

np.random.seed(42)
end_date = datetime.now()
dates = [end_date - timedelta(days=i) for i in range(29, -1, -1)]

df = pd.DataFrame({
"date": [d.strftime("%Y-%m-%d") for d in dates],
"orders": np.random.randint(50, 300, size=30),
"revenue": np.round(np.random.uniform(500, 5000, size=30), 2)
})

col = selected_option if selected_option in ("orders", "revenue") else "orders"
return df[["date", col]].rename(columns={col: "value"})

Two things to do:

  1. Write the widget SQL to query the UDF output table (here orders_df).
  2. Connect the orders_df UDF node to the widget node with an edge.

Connect a UDF to a widget

Example (bar chart) — Try it out:

{
"type": "bar-chart",
"props": {
"sql": "SELECT date AS label, value FROM {{orders_df}} ORDER BY label",
"title": "Metric Per Day",
"barColor": "#E8FF59",
"rotateLabels": true
}
}
tip

The UDF must be on the same canvas. The name inside {{ }} must match a UDF name present in the canvas exactly (case-sensitive).

Anatomy of a Widget URL

Every widget URL follows a predictable structure that makes it easy to share, embed, and programmatically manipulate:

Widget URL anatomy

URL structure breakdown:

  • share_token: A unique identifier that scopes access to your canvas and its UDFs.
  • ?widget={JSON}: An optional query parameter that overrides the entire widget definition — pass any valid widget JSON to render a different visualization, query, or layout on the same underlying data without touching the canvas. AI agents can use this to generate new widgets and return modified URLs instantly.
note

The base share URL (https://fused.io/share/<share_token>) can access all UDFs in the shared canvas.

Example: Line Chart

The ?widget= param makes the URL a lightweight experiment surface for both humans and AI. You can tweak the SQL in the URL, reload, and see the result instantly.

https://fused.io/share/fc_...?widget={
"type": "line-chart",
"props": {
"sql": "SELECT date AS label, value FROM {{orders_df}} ORDER BY label",
"title": "Time Series",
"showArea": true,
"curveType": "smooth"
}
}

The {{orders_df}} reference still resolves to the UDF on your canvas — the share token carries the canvas context. Only the widget rendering config changes.

Open this in a browser to get a live, interactive widget — the chart renders data from your Fused UDF. Try it out

AI driven Widget

This section uses the render widget type — a wrapper widget that takes any other widget definition as input and displays it on the canvas. Because it wraps other widgets, it can layer on an AI panel and a live JSON editor, letting you iterate on the inner widget without leaving the canvas.

With the render widget, you can:

  • Wrap any widget — pass an inner { type, props } definition and render it at runtime.
  • Edit live with AI — prompt the built-in AI panel to rewrite the inner definition in place.
  • Tweak JSON inline — open the optional JSON editor to change SQL, chart type, or styling without leaving the canvas.

Structure of a Render Widget

A render widget is always a two-layer schema:

  • The outer shell is { "type": "render", "props": { ... } }.
  • The inner definition is the widget you want to display, stored under props.defaultValue as { "type": "…", "props": { … } }.
{
"type": "render",
"props": {
"defaultValue": {
"type": "<inner-widget-type>",
"props": { }
},
"showEditor": true,
"aiBuilderMode": "enabled",
"aiPanel": "right"
}
}

How to create and use a Render Widget

Click the widget icon to create a new widget node, then paste in the JSON below. Once you connect the widget to the my_udf UDF, the render widget will display a bar chart with an AI panel and code editor.

Show the my_udf UDF code
@fused.udf
def udf(path: str = "s3://fused-sample/demo_data/airbnb_listings_sf.parquet"):
import pandas as pd

@fused.cache
def load_data(file_path):
return pd.read_parquet(file_path)

df = load_data(path)
return df
Show the render widget JSON
{
"type": "render",
"props": {
"defaultValue": {
"type": "bar-chart",
"props": {
"sql": "SELECT neighbourhood_cleansed as label, ROUND(AVG(price_in_dollar), 2) as value FROM {{my_udf}} GROUP BY neighbourhood_cleansed ORDER BY value DESC LIMIT 10",
"title": "Top 10 Most Expensive Neighbourhoods"
}
},
"showEditor": true,
"editorCollapsed": false,
"aiBuilderMode": "enabled",
"aiPanel": "right"
}
}

Editing with AI

With aiBuilderMode: "enabled", you can describe what you want in plain English (for example, "switch this to a line chart" or "add a filter for price > 100"), and the AI rewrites the inner { type, props } definition for you. Use aiPanel to place the panel ("right", "left", "top", or "bottom").

AI panel rewriting render widget config from a prompt

Prompting the AI panel to change the chart type to a line chart — the widget updates in real time. Try it out →

note

The AI edits the inner defaultValue definition only — the outer render shell stays the same. This means you can iterate on chart type, SQL, or styling without touching the widget container itself.

Available props

PropTypeDefaultWhat it does
defaultValueobject or string(required)The widget definition to render, or a "$param" reference
showEditorbooleanfalseShow/hide the JSON editor panel
editorCollapsedbooleantrueWhether the editor starts collapsed
editorHeightnumberautoFixed editor height in pixels
aiBuilderMode"enabled" / "disabled""disabled"Toggle the AI assistant
aiPanel"top" / "bottom" / "left" / "right""bottom"AI panel position
allowedWidgetTypesstring"all"Comma-separated list of permitted widget types
stylestringInline CSS for the outer container

For the full list of widgets you can nest inside render, see the available widgets.

Sharing a Widget from Canvas

When you share a specific widget from a canvas, you will get a URL like:

https://fused.io/share/<share_token>/<widget_name>?...

You can still override the widget by appending a widget query parameter and passing widget JSON.

info

When the base URL includes a specific widget path (for example, /orders_widget), the ?widget= override is scoped. It can only query UDFs that are connected to that widget node in the canvas. It does not have access to every UDF in the shared canvas.

When you share a widget URL, Fused automatically appends all canvas parameters as query parameters so the shared view can be reproduced exactly. Try it out →

Available Widgets

See a live example →

CategoryWidgetDescriptionKey Features / Notes
InputsliderNumeric range controlmin, max, step, defaultValue, binds to param
InputinputFree-text input fieldSyncs with canvas param
InputdropdownSelect menuStatic options OR dynamic via DuckDB SQL (label, value), supports search
InputbuttonClickable triggerIncrements a canvas param on click
Inputcode-editorMulti-language editorSupports Python, SQL, JSON, Markdown; syncs content to a param
InputformInput containerWraps inputs and submits as one object
Inputmap-boundsInteractive map viewportEmits [west, south, east, north] bounds
Inputmap-h3H3-based map inputEmits H3 hex cell; supports resolution, k-ring, multi-coordinates
Outputbar-chartBar chart via SQLlabel + value, sorting, animation, layout options
Outputline-chartTime-series chartSupports multi-series (series column), zoom, curves
Outputbig-numberKPI displayPrefix/suffix, compact format
Outputsql-tableData tableSortable, filterable
OutputtextStatic/dynamic textSupports markdown + variants (h1, muted, etc.)
OutputimageImage displayURL or base64
Outputfused-mapFull interactive mapSupports multiple layer types, tooltips, legends, basemaps
OutputrenderMeta widgetRenders widgets dynamically from JSON

Multi-Component Widgets

A single widget node can hold multiple widgets — sliders, charts, tables, text, maps — stacked or laid out together. You wrap them in a div with children.

Use a div as the root container type and list each widget inside its children array — the structure is just a wrapper with standard CSS for layout.

{
"type": "div",
"props": {
"style": "display: flex; flex-direction: column; gap: 16px; padding: 16px;"
},
"children": [
{ "type": "slider", "props": { ... } },
{ "type": "bar-chart", "props": { ... } },
{ "type": "sql-table", "props": { ... } }
]
}
  • type: "div" — a container, not a visual widget. It just groups children.
  • props.style — standard CSS. Use flex-direction: column for a vertical stack, row for side-by-side.
  • children — an ordered array of widget definitions { type, props }. Each child is a full widget. For the full list of supported widgets, see the available widgets.
tip

You can nest a div inside another div for more complex layouts.

Combining input & output components

Multiple components can also work together inside a single widget:

  • An input component (like a slider) publishes a value through param
  • An output component (like a chart) can read that value with $param

This lets you build interactive widgets with both inputs and outputs in a single widget.

Example

When the slider changes, every widget using $param (here $top_n) updates instantly — the bar chart and SQL table re-render in sync with the slider value. Try it out →

Show the my_udf UDF code
@fused.udf
def udf(path: str = "s3://fused-sample/demo_data/airbnb_listings_sf.parquet"):
import pandas as pd

@fused.cache
def load_data(file_path):
return pd.read_parquet(file_path)

df = load_data(path)
return df
Show the multi-widget JSON
{
"type": "div",
"props": {
"style": "display: flex; flex-direction: column; gap: 16px; padding: 16px;"
},
"children": [
{
"type": "slider",
"props": {
"label": "Top N most expensive locations",
"param": "top_n",
"min": 1,
"max": 20,
"step": 1,
"defaultValue": 15
}
},
{
"type": "bar-chart",
"props": {
"title": "Top N Most Expensive Locations (Avg Price)",
"sql": "SELECT neighbourhood_cleansed as label, ROUND(AVG(price_in_dollar), 2) as value FROM {{my_udf}} GROUP BY neighbourhood_cleansed ORDER BY value DESC LIMIT $top_n",
"barColor": "#E8FF59",
"barOpacity": 1,
"barRadius": 4,
"hoverColor": "#ffffff",
"showGrid": true,
"rotateLabels": true,
"horizontal": false,
"showValues": true,
"sort": "none",
"xAxisFontSize": 11,
"yAxisFontSize": 11,
"beginAtZero": true,
"animationMs": 300
}
},
{
"type": "sql-table",
"props": {
"title": "Number of Reviews for Top N Locations",
"sql": "SELECT neighbourhood_cleansed AS Neighbourhood, ROUND(AVG(price_in_dollar), 2) AS Avg_Price, CAST(SUM(number_of_reviews) AS INTEGER) AS Total_Reviews FROM {{my_udf}} WHERE neighbourhood_cleansed IN (SELECT neighbourhood_cleansed FROM {{my_udf}} GROUP BY neighbourhood_cleansed ORDER BY AVG(price_in_dollar) DESC LIMIT $top_n) GROUP BY neighbourhood_cleansed ORDER BY Avg_Price DESC"
}
}
]
}

Multi-widget slider updating chart and table