Skip to content

Python API Reference

panel_live

Accessible imports for the panel_live package.

__all__ = ['PanelLive', '__version__', 'content_hash', 'pre_render'] module-attribute

__version__ = importlib.metadata.version(__name__) module-attribute

PanelLive

Bases: JSComponent

Run Python code in the browser via Pyodide, with bidirectional server communication.

Wraps the <panel-live> web component as a Panel JSComponent. Code executes client-side in a Pyodide Web Worker; the value parameter provides a bidirectional data channel between server and browser.

The panel-live JS/CSS bundle is loaded automatically from CDN by default. To use local assets instead, call :meth:configure before creating any instances::

PanelLive.configure(js_url="./pl/panel-live.js")

Attributes:

Name Type Description
code str

Python code to execute in Pyodide.

requirements list[str]

Packages to install via micropip before execution.

mode str

Display mode:

  • "editor" — code editor + output (default)
  • "app" — output only
  • "playground" — editor + examples selector
  • "headless" — invisible (0px), pure background compute
  • "progress" — spinning Python icon, evaluate queue on hover
  • "debug" — stdout/stderr visible, for development
theme str

Color theme: "auto" (OS preference), "light", or "dark".

layout str

Editor/output arrangement: "vertical" or "horizontal".

auto_run bool

If True, execute code automatically on load.

code_visibility str

Code editor visibility: "visible", "collapsed", or "hidden".

code_position str

Code panel position relative to output: "first" (before) or "last" (after).

value object

Bidirectional value for server→client and client→server data. Supports JSON-serializable types (str, int, float, dict, list, bool, None).

input object

Server→client data channel. Setting input pushes data to the client's server.input param (reactive, no re-run).

output object

Client→server data channel (read-only from server perspective). Updated when client-side code sets server.output.

status str

Current execution status (read-only from user perspective).

error str

Last error message from execution.

stdout str

Captured stdout from last execution.

auto_run = param.Boolean(default=True, doc='Run code automatically on load') class-attribute instance-attribute

code = param.String(default='', doc='Python code to execute in Pyodide') class-attribute instance-attribute

code_position = param.Selector(default='first', objects=['first', 'last'], doc="Code panel position relative to output: 'first' (before) or 'last' (after).") class-attribute instance-attribute

code_visibility = param.Selector(default='visible', objects=['visible', 'collapsed', 'hidden']) class-attribute instance-attribute

error = param.String(default='', doc='Last error message') class-attribute instance-attribute

input = param.Parameter(doc="Server-to-client data. Setting this pushes data to the client's server.input param.") class-attribute instance-attribute

layout = param.Selector(default='vertical', objects=['vertical', 'horizontal']) class-attribute instance-attribute

mode = param.Selector(default='editor', objects=['app', 'editor', 'playground', 'headless', 'progress', 'debug'], doc="Display mode: 'editor' (code + output), 'app' (output only), 'playground' (editor + examples), 'headless' (invisible 0px), 'progress' (spinning Python icon, evaluate queue on hover), 'debug' (stdout/stderr visible).") class-attribute instance-attribute

output = param.Parameter(doc='Client-to-server data. Updated when Pyodide code sets server.output.') class-attribute instance-attribute

requirements = param.List(default=[], item_type=str, doc='Packages to install via micropip') class-attribute instance-attribute

status = param.Selector(default='idle', objects=['idle', 'loading', 'running', 'ready', 'error']) class-attribute instance-attribute

stdout = param.String(default='', doc='Captured stdout from last execution') class-attribute instance-attribute

theme = param.Selector(default='auto', objects=['auto', 'light', 'dark']) class-attribute instance-attribute

value = param.Parameter(doc='Bidirectional value. JSON-serializable types: str, int, float, dict, list, None.') class-attribute instance-attribute

configure(*, js_url=None, css_url=None) classmethod

Override the panel-live JS and/or CSS asset URLs.

Call before creating any PanelLive instances::

PanelLive.configure(js_url="./pl/panel-live.js", css_url="./pl/panel-live.css")

HTTP(S) URLs are loaded via __javascript__ / __css__. Relative URLs (for local --static-dirs serving) are injected via pn.config.js_files / pn.config.css_files.

evaluate(code, timeout=30.0, **kwargs) async

Evaluate Python code in the browser and return the result.

Executes code in the client-side Pyodide worker without rendering any UI. Returns the value of the last expression, like Python's built-in :func:eval.

Parameters:

Name Type Description Default
code str

Python code to evaluate in Pyodide.

required
timeout float

Maximum seconds to wait for a result (default 30).

30.0
**kwargs Any

JSON-serializable keyword arguments injected as globals in the Pyodide execution namespace.

{}

Returns:

Type Description
Any

The result of the last expression (must be JSON-serializable).

Raises:

Type Description
TimeoutError

If the evaluation does not complete within timeout seconds.

RuntimeError

If the client-side execution raises an error.

run(code=None, timeout=60.0) async

Trigger the full render pipeline (same as clicking "Run").

Parameters:

Name Type Description Default
code str or None

If provided, updates self.code and syncs to the client editor before running. If None, runs the editor's current content (which may differ from self.code if the user edited it in the browser).

None
timeout float

Maximum seconds to wait for the render to complete.

60.0

Raises:

Type Description
TimeoutError

If rendering does not complete within timeout seconds.

RuntimeError

If client-side execution raises an error.

send(data)

Send data from server to client-side Pyodide code.

The data must be JSON-serializable. On the client side, the data is available via the pl-server-data event on the <panel-live> element.

Parameters:

Name Type Description Default
data Any

JSON-serializable data to send to the client.

required

content_hash(code)

Return a SHA-256 hex digest for code.

Parameters:

Name Type Description Default
code str

The full source code string (including any prepended setup code).

required

Returns:

Type Description
str

64-character hexadecimal digest.

pre_render(code, cache_dir, *, setup_code='', timeout=120)

Pre-render code and return the embedded JSON string.

Uses a content-hash cache so repeated builds with unchanged code are nearly instant.

Parameters:

Name Type Description Default
code str

The user-visible source code.

required
cache_dir Path or str

Directory for the content-hash cache (e.g. ".panel-live").

required
setup_code str

Optional code prepended before code (e.g. pn.extension(...)). Included in the content hash.

''
timeout int

Maximum seconds to wait for the subprocess.

120

Returns:

Type Description
str or None

Bokeh JSON string on success, None on failure or empty output.

__main__

Allow running panel-live as python -m panel_live.

cli

Command-line interface for panel-live utilities.

Usage::

panel-live serve --port 5008
panel-live pre-render CODE
panel-live pre-render --file script.py
panel-live pre-render CODE --cache-dir .cache --setup-code "import panel as pn" --timeout 60
panel-live mcp
panel-live mcp --transport streamable-http --port 5002
panel-live --version

The serve command starts a Panel server with the showcase example app, demonstrating all PanelLive display modes.

The pre-render command executes Panel code and prints the resulting Bokeh JSON to stdout. Exit code 0 on success, 1 on failure.

The mcp command starts the MCP server for use with VS Code Copilot Chat, Claude Desktop, or other MCP-compatible clients.

main(argv=None)

Entry point for python -m panel_live and panel-live CLI.

component

PanelLive server component.

A JSComponent that wraps the <panel-live> web component, enabling Panel server applications to run Python code in the browser via Pyodide with bidirectional data exchange.

PanelLive

Bases: JSComponent

Run Python code in the browser via Pyodide, with bidirectional server communication.

Wraps the <panel-live> web component as a Panel JSComponent. Code executes client-side in a Pyodide Web Worker; the value parameter provides a bidirectional data channel between server and browser.

The panel-live JS/CSS bundle is loaded automatically from CDN by default. To use local assets instead, call :meth:configure before creating any instances::

PanelLive.configure(js_url="./pl/panel-live.js")

Attributes:

Name Type Description
code str

Python code to execute in Pyodide.

requirements list[str]

Packages to install via micropip before execution.

mode str

Display mode:

  • "editor" — code editor + output (default)
  • "app" — output only
  • "playground" — editor + examples selector
  • "headless" — invisible (0px), pure background compute
  • "progress" — spinning Python icon, evaluate queue on hover
  • "debug" — stdout/stderr visible, for development
theme str

Color theme: "auto" (OS preference), "light", or "dark".

layout str

Editor/output arrangement: "vertical" or "horizontal".

auto_run bool

If True, execute code automatically on load.

code_visibility str

Code editor visibility: "visible", "collapsed", or "hidden".

code_position str

Code panel position relative to output: "first" (before) or "last" (after).

value object

Bidirectional value for server→client and client→server data. Supports JSON-serializable types (str, int, float, dict, list, bool, None).

input object

Server→client data channel. Setting input pushes data to the client's server.input param (reactive, no re-run).

output object

Client→server data channel (read-only from server perspective). Updated when client-side code sets server.output.

status str

Current execution status (read-only from user perspective).

error str

Last error message from execution.

stdout str

Captured stdout from last execution.

auto_run = param.Boolean(default=True, doc='Run code automatically on load') class-attribute instance-attribute
code = param.String(default='', doc='Python code to execute in Pyodide') class-attribute instance-attribute
code_position = param.Selector(default='first', objects=['first', 'last'], doc="Code panel position relative to output: 'first' (before) or 'last' (after).") class-attribute instance-attribute
code_visibility = param.Selector(default='visible', objects=['visible', 'collapsed', 'hidden']) class-attribute instance-attribute
error = param.String(default='', doc='Last error message') class-attribute instance-attribute
input = param.Parameter(doc="Server-to-client data. Setting this pushes data to the client's server.input param.") class-attribute instance-attribute
layout = param.Selector(default='vertical', objects=['vertical', 'horizontal']) class-attribute instance-attribute
mode = param.Selector(default='editor', objects=['app', 'editor', 'playground', 'headless', 'progress', 'debug'], doc="Display mode: 'editor' (code + output), 'app' (output only), 'playground' (editor + examples), 'headless' (invisible 0px), 'progress' (spinning Python icon, evaluate queue on hover), 'debug' (stdout/stderr visible).") class-attribute instance-attribute
output = param.Parameter(doc='Client-to-server data. Updated when Pyodide code sets server.output.') class-attribute instance-attribute
requirements = param.List(default=[], item_type=str, doc='Packages to install via micropip') class-attribute instance-attribute
status = param.Selector(default='idle', objects=['idle', 'loading', 'running', 'ready', 'error']) class-attribute instance-attribute
stdout = param.String(default='', doc='Captured stdout from last execution') class-attribute instance-attribute
theme = param.Selector(default='auto', objects=['auto', 'light', 'dark']) class-attribute instance-attribute
value = param.Parameter(doc='Bidirectional value. JSON-serializable types: str, int, float, dict, list, None.') class-attribute instance-attribute
configure(*, js_url=None, css_url=None) classmethod

Override the panel-live JS and/or CSS asset URLs.

Call before creating any PanelLive instances::

PanelLive.configure(js_url="./pl/panel-live.js", css_url="./pl/panel-live.css")

HTTP(S) URLs are loaded via __javascript__ / __css__. Relative URLs (for local --static-dirs serving) are injected via pn.config.js_files / pn.config.css_files.

evaluate(code, timeout=30.0, **kwargs) async

Evaluate Python code in the browser and return the result.

Executes code in the client-side Pyodide worker without rendering any UI. Returns the value of the last expression, like Python's built-in :func:eval.

Parameters:

Name Type Description Default
code str

Python code to evaluate in Pyodide.

required
timeout float

Maximum seconds to wait for a result (default 30).

30.0
**kwargs Any

JSON-serializable keyword arguments injected as globals in the Pyodide execution namespace.

{}

Returns:

Type Description
Any

The result of the last expression (must be JSON-serializable).

Raises:

Type Description
TimeoutError

If the evaluation does not complete within timeout seconds.

RuntimeError

If the client-side execution raises an error.

run(code=None, timeout=60.0) async

Trigger the full render pipeline (same as clicking "Run").

Parameters:

Name Type Description Default
code str or None

If provided, updates self.code and syncs to the client editor before running. If None, runs the editor's current content (which may differ from self.code if the user edited it in the browser).

None
timeout float

Maximum seconds to wait for the render to complete.

60.0

Raises:

Type Description
TimeoutError

If rendering does not complete within timeout seconds.

RuntimeError

If client-side execution raises an error.

send(data)

Send data from server to client-side Pyodide code.

The data must be JSON-serializable. On the client side, the data is available via the pl-server-data event on the <panel-live> element.

Parameters:

Name Type Description Default
data Any

JSON-serializable data to send to the client.

required

examples

Example Panel apps using the PanelLive component.

panes

panel-live in HTML/Markdown panes — demonstrates embedding inside pn.pane.HTML, pn.pane.Markdown, and pn.chat.ChatInterface.

Run with::

panel-live serve --port 5008
# then open http://localhost:5008/panes

Or directly::

pixi run panel serve src/panel_live/examples/panes.py \
    --static-dirs pl=dist docs=docs \
    --port 5008
CDN = 'https://panel-extensions.github.io/panel-live/assets' module-attribute
DOCS_BASE = 'https://panel-extensions.github.io/panel-live' module-attribute
HEADER_MD = pn.pane.Markdown(f'# panel-live in HTML & Markdown PanesEmbed `<panel-live>` directly inside `pn.pane.HTML`, `pn.pane.Markdown`, and`pn.chat.ChatInterface` no `PanelLive` JSComponent required.<div style="display:flex; align-items:center; gap:18px; justify-content:center; padding:8px 0;"><a href="https://panel.holoviz.org" target="_blank" title="Panel"><img src="https://panel.holoviz.org/_static/logo_stacked.svg" alt="Panel" style="height:48px;"></a><span style="font-size:28px; color:#aaa;">+</span><a href="https://pyodide.org" target="_blank" title="Pyodide"><img src="https://raw.githubusercontent.com/pyodide/pyodide-artwork/refs/heads/main/logo-quadratic.svg" alt="Pyodide" style="height:48px;"></a></div>Bokeh renders Panel panes inside a Shadow DOM `<panel-live>` detects thisautomatically and patches `document.getElementById` so Bokeh's renderingpipeline can find the output container.[How-to Guide]({DOCS_BASE}/how-to/panel-panes/) ·[PanelLive Component]({DOCS_BASE}/how-to/panel-component/) ·[GitHub](https://github.com/panel-extensions/panel-live)', width=WIDTH) module-attribute
SIZING = {'sizing_mode': 'stretch_width'} module-attribute
WIDTH = 800 module-attribute
accordion = pn.Accordion(('1. HTML Pane', pn.Column('Bokeh renders Panel panes inside Shadow DOM — `<panel-live>` detects this automatically.', html_pane)), ('2. Markdown Pane', pn.Column('`pn.pane.Markdown` passes raw HTML blocks through unchanged. Shadow DOM detection is automatic.', md_pane)), ('3. ChatInterface', pn.Column(f'Type {_KEYWORDS} — the callback returns a `pn.pane.HTML` wrapping `<panel-live>`. Shadow DOM detection is automatic.', pn.pane.Markdown('Type **slider**, **plot**, or **table** to see a live app.'), chat_interface)), width=WIDTH, active=[0]) module-attribute
chat_interface = pn.chat.ChatInterface(callback=_chat_respond, show_send=True, placeholder_text='Type: slider, plot, or table') module-attribute
html_pane = pn.pane.HTML(f'<panel-live mode="editor" style="width:100%">{_HTML_CODE}</panel-live>', **SIZING) module-attribute
md_pane = pn.pane.Markdown(f'<panel-live mode="app" auto-run="true" style="width:100%">{_MD_CODE}</panel-live>', **SIZING) module-attribute

showcase

PanelLive Showcase — demonstrates all modes and communication patterns.

Run with::

panel-live serve --port 5008

Or directly::

pixi run panel serve src/panel_live/examples/showcase.py         --static-dirs pl=quarto/_extensions/panel-live docs=docs         --port 5008
DOCS_BASE = 'https://panel-extensions.github.io/panel-live' module-attribute
EVAL_CASES = [('10 / 2', 'division'), ('def divide(a, b):\n return a / b\ndivide(1, 0)', 'ZeroDivisionError'), ("int('hello')", 'ValueError'), ('sum(range(100))', 'sum'), ('import nonexistent', 'ModuleNotFoundError')] module-attribute
HEADER_MD = pn.pane.Markdown(f'# PanelLive ShowcaseRun **Python in the browser** with [Pyodide](https://pyodide.org) no server required for execution.<div style="display:flex; align-items:center; gap:18px; justify-content:center; padding:8px 0;"><a href="https://panel.holoviz.org" target="_blank" title="Panel"><img src="https://panel.holoviz.org/_static/logo_stacked.svg" alt="Panel" style="height:48px;"></a><span style="font-size:28px; color:#aaa;">+</span><a href="https://pyodide.org" target="_blank" title="Pyodide"><img src="https://raw.githubusercontent.com/pyodide/pyodide-artwork/refs/heads/main/logo-quadratic.svg" alt="Pyodide" style="height:48px;"></a></div>This demo shows all six display modes, server-side RPC, and bidirectional data exchangebetween a Panel server and client-side Pyodide code.[Getting Started]({DOCS_BASE}/tutorials/getting-started-panel/) ·[How-to Guide]({DOCS_BASE}/how-to/panel-component/) ·[Architecture]({DOCS_BASE}/explanation/panel-component/) ·[GitHub](https://github.com/panel-extensions/panel-live)', width=800) module-attribute
SIZING = {'sizing_mode': 'stretch_width'} module-attribute
WIDTH = 800 module-attribute
accordion = pn.Accordion(('1. Editor Mode', pn.Column('Interactive code editor with live Pyodide output.', editor)), ('2. App Mode', pn.Column('Output only — no code editor visible.', app_mode)), ('3. Progress Mode', pn.Column('Sends 5 concurrent `evaluate()` calls (each sleeps 1s in the browser). The Python icon spins while active — **hover over it** to see the queue depth. Results arrive one by one as each completes.', pn.Row(btn_progress, progress_target), eval_results)), ('4. Progress Mode — Error Handling', pn.Column('Sends 5 evaluations — 2 succeed, 3 raise exceptions. Errors propagate as `RuntimeError` on the server without crashing the batch.', pn.Row(btn_mixed, error_target), error_results)), ('5. Debug Mode', pn.Column('Shows stdout/stderr — useful during development.', debug)), ('6. Playground Mode', pn.Column('Editor with examples selector — ideal for interactive exploration.', playground)), ('7. Headless Mode', pn.Column('Invisible (0px) — pure background compute. The element below is present but hidden:', headless)), ('8. Server RPC', pn.Column('Test `evaluate()` and `run()` — server-side methods that execute code in the client Pyodide worker.', pn.Row(btn_evaluate, btn_run), rpc_status, rpc_target)), ('9. Server→Client Reactive Push', pn.Column('Server sets `input` param — client reacts via `@pn.depends(server.param.input)`, no re-run needed.', pn.Row(btn_reactive_send), reactive_status, reactive_target)), ('10. Server→Client Periodic Push', pn.Column('Server pushes slider value via `input` param. Client displays datetime + `server.input` every 200ms.', slider, periodic_target)), ('11. Client→Server Data', pn.Column("Client sets `server.output` to push data back to the server's `output` param.", output_target, output_display)), width=WIDTH, active=[0, 2, 9]) module-attribute
app_mode = PanelLive(code='import panel as pn\npn.pane.Markdown("## App Mode\\n\\nNo editor visible — output only.").servable()\n', mode='app', auto_run=True, **SIZING) module-attribute
btn_evaluate = pn.widgets.Button(name='Test evaluate()', button_type='primary') module-attribute
btn_mixed = pn.widgets.Button(name='Send 5 mixed evaluations', button_type='primary') module-attribute
btn_progress = pn.widgets.Button(name='Send 5 evaluations', button_type='primary') module-attribute
btn_reactive_send = pn.widgets.Button(name='Send Data to Client', button_type='primary') module-attribute
btn_run = pn.widgets.Button(name='Test run()', button_type='success') module-attribute
debug = PanelLive(code='print("stdout: debug mode active")\nresult = sum(range(100))\nprint(f"Computed sum(range(100)) = {result}")\nprint(f"Python version: {__import__(\'sys\').version}")\n', mode='debug', auto_run=True, **SIZING) module-attribute
editor = PanelLive(code='import panel as pn\n\nslider = pn.widgets.IntSlider(name="Pick a number", start=1, end=100, value=42)\n\npn.Column(\n slider,\n pn.bind(lambda v: f"### You picked **{v}**", slider),\n).servable()\n', mode='editor', auto_run=True, **SIZING) module-attribute
error_results = pn.pane.Str('', sizing_mode='stretch_width') module-attribute
error_target = PanelLive(code='pass', mode='progress', auto_run=True, **SIZING) module-attribute
eval_counter = 0 module-attribute
eval_results = pn.pane.Str('', sizing_mode='stretch_width') module-attribute
headless = PanelLive(code='print("headless: invisible execution")', mode='headless', auto_run=True, **SIZING) module-attribute
output_display = pn.pane.JSON({}, name='Received Output', depth=2) module-attribute
output_target = PanelLive(code='import panel as pn\n\nbtn = pn.widgets.Button(name="Send to Server", button_type="primary")\ncount = 0\n\ndef on_click(event):\n global count\n count += 1\n server.output = {"count": count, "source": "browser"}\n\nbtn.on_click(on_click)\npn.Column(btn, "Click to send data to the server\'s `output` param").servable()\n', mode='editor', auto_run=True, **SIZING) module-attribute
periodic_target = PanelLive(code='import panel as pn\nimport datetime\n\ntime_pane = pn.pane.Str("Waiting...")\ndata_pane = pn.pane.Str("No server data yet")\n\ndef update():\n time_pane.object = f"Time: {datetime.datetime.now().strftime(\'%H:%M:%S.%f\')[:-3]}"\n data_pane.object = f"Server data: {server.input}"\n\npn.state.add_periodic_callback(update, period=200)\npn.Column(time_pane, data_pane).servable()\n', mode='editor', auto_run=True, **SIZING) module-attribute
playground = PanelLive(code='import panel as pn\n\nname = pn.widgets.TextInput(name="Your name", value="World")\n\npn.Column(\n name,\n pn.bind(lambda n: f"### Hello, **{n}**!", name),\n).servable()\n', mode='playground', auto_run=True, **SIZING) module-attribute
progress_target = PanelLive(code='pass', mode='progress', auto_run=True, **SIZING) module-attribute
reactive_counter = 0 module-attribute
reactive_status = pn.widgets.TextInput(name='Status', value='Ready', disabled=True) module-attribute
reactive_target = PanelLive(code='import panel as pn\n\n@pn.depends(server.param.input)\ndef message(value):\n return value or {"message": "No data received yet"}\n\npn.pane.JSON(message, name="Server Data", depth=2).servable()\n', mode='editor', auto_run=True, **SIZING) module-attribute
rpc_status = pn.widgets.TextInput(name='Status', value='Ready', disabled=True) module-attribute
rpc_target = PanelLive(code='import panel as pn\npn.pane.Markdown("Waiting for server command...").servable()', mode='editor', auto_run=True, **SIZING) module-attribute
run_counter = 0 module-attribute
slider = pn.widgets.IntSlider(name='Server value', start=0, end=100, value=42) module-attribute

fences

Custom pymdownx.superfences fence for panel-live code blocks in MkDocs.

Allows writing interactive Panel apps in markdown using fenced code blocks:

```panel
import panel as pn
pn.panel("Hello").servable()
```

or with attributes:

```{.panel mode="editor" theme="dark" height="500px"}
import panel as pn
pn.panel("Hello").servable()
```

Pre-rendering can be enabled via :func:configure so that Panel code is executed at MkDocs build time and the output is embedded as static HTML.

log = logging.getLogger('panel-live') module-attribute

configure(*, pre_render=False, cache_dir='.panel-live', setup_code='', timeout=120, docs_dir='docs')

Configure pre-rendering for the MkDocs fence formatter.

Call this from a MkDocs hook (on_startup or on_config) to enable build-time pre-rendering of panel fenced code blocks.

Parameters:

Name Type Description Default
pre_render bool

Enable pre-rendering (default False).

False
cache_dir str

Directory for the content-hash cache (default ".panel-live").

'.panel-live'
setup_code str

Python code prepended before every fence's code.

''
timeout int

Maximum seconds to wait for each subprocess (default 120).

120
docs_dir str

Path to the MkDocs docs directory (default "docs"). Used to resolve src attribute paths for pre-rendering.

'docs'

formatter(source, language, css_class, options, md, **kwargs)

Wrap source in a <panel-live> HTML element.

Called by pymdownx.superfences after validator has run. Builds the <panel-live> tag with attributes from options and the fenced source code as escaped inner text.

If mode="org" is set, delegates to the standard pymdownx.superfences.fence_code_format to render a plain syntax-highlighted code block instead of a <panel-live> element.

Parameters:

Name Type Description Default
source str

The raw content inside the fenced code block.

required
language str

The fence language identifier (e.g. "panel").

required
css_class str

CSS class assigned by pymdownx.superfences (unused).

required
options dict

Validated options produced by validator.

required
md Markdown

The Markdown processor instance.

required
**kwargs

Additional keyword arguments from pymdownx.superfences.

{}

Returns:

Type Description
str

An HTML string containing a <panel-live> element, or a standard <pre><code> block when mode="org".

prerender_formatter(source, language, css_class, options, md, **kwargs)

Wrap source in a <panel-live> element with pre-rendering forced on.

Drop-in replacement for :func:formatter that always pre-renders, regardless of the global configure() setting. Use this in your superfences configuration when you want every fence to be pre-rendered without needing a MkDocs hook::

custom_fences = [
    {
        "name": "panel",
        "class": "panel-live",
        "validator": "panel_live.fences.validator",
        "format": "panel_live.fences.prerender_formatter",
    }
]

Parameters:

Name Type Description Default
source

Same as :func:formatter.

required
language

Same as :func:formatter.

required
css_class

Same as :func:formatter.

required
options

Same as :func:formatter.

required
md

Same as :func:formatter.

required
**kwargs

Same as :func:formatter.

required

Returns:

Type Description
str

An HTML string containing a <panel-live> element with pre-rendered output embedded.

validator(language, inputs, options, attrs, md)

Parse fence attributes into options for the formatter.

Called by pymdownx.superfences when a panel fence is encountered. Known attributes (see _KNOWN_ATTRS) are popped from inputs and stored in options so that formatter can read them later.

Parameters:

Name Type Description Default
language str

The fence language identifier (e.g. "panel").

required
inputs dict

Raw key-value pairs parsed from the fence opening line. Known attributes are popped and moved into options.

required
options dict

Mutable dict that carries validated options to the formatter.

required
attrs dict

Additional attributes from pymdownx.superfences.

required
md Markdown

The Markdown processor instance.

required

Returns:

Type Description
bool

True if the fence is valid and should be processed.

mcp

MCP server for panel-live.

Provides the show_panel_live tool for rendering interactive Panel apps in MCP Apps-capable clients (VS Code Copilot Chat, Claude.ai, etc.).

Start the server via CLI::

panel-live mcp                       # stdio (default)
panel-live mcp --transport http      # SSE for testing

Or run directly with FastMCP::

fastmcp run panel_live.mcp:mcp

.. note::

This module intentionally avoids importing panel, bokeh, or panel_live.__init__ to keep MCP server startup fast (~0.5 s instead of 5-10 s).

RESOURCE_URI = 'ui://panel-live/show.html' module-attribute

TEMPLATE_PATH = Path(__file__).parent / 'templates' / 'show.html' module-attribute

mcp = create_mcp_server() module-attribute

create_mcp_server()

Create and return the panel-live MCP server.

Returns:

Type Description
FastMCP

A configured FastMCP server instance with the show_panel_live tool and the ui://panel-live/show.html MCP App resource.

prerender

Shared pre-rendering utilities for panel-live.

Executes Panel code at build time and produces Bokeh JSON output that can be embedded inside <panel-live> elements for instant display before Pyodide loads. Used by the Sphinx extension, the MkDocs fence formatter, and the CLI.

The pipeline is:

  1. Hash the code (including any setup code) with SHA-256.
  2. Check a content-hash cache on disk.
  3. On cache miss, spawn a subprocess that runs the code via panel.io.mime_render.exec_with_return() and serializes the result to Bokeh JSON via standalone_docs_json_and_render_items().
  4. Store the JSON in the cache and return it.
  5. The caller wraps the JSON in a <script> tag for embedding.

log = logging.getLogger('panel-live') module-attribute

content_hash(code)

Return a SHA-256 hex digest for code.

Parameters:

Name Type Description Default
code str

The full source code string (including any prepended setup code).

required

Returns:

Type Description
str

64-character hexadecimal digest.

embed_script_tag(json_str)

Wrap json_str in a <script> tag for embedding inside <panel-live>.

Parameters:

Name Type Description Default
json_str str

The Bokeh JSON string returned by :func:pre_render.

required

Returns:

Type Description
str

An HTML <script type="application/json" class="panel-live-prerender"> tag.

execution_process(code, conn)

Run code in a subprocess and send back Bokeh JSON via conn.

This function is the target of a multiprocessing.Process spawned with get_context('spawn').

Parameters:

Name Type Description Default
code str

Python source code to execute.

required
conn Connection

The child end of a Pipe(). A dict with error and output keys is sent before the connection is closed.

required

model_json(obj)

Serialize a Bokeh/Panel object to JSON for embedding.

Mirrors the pattern used by nbsite.pyodide._model_json().

Parameters:

Name Type Description Default
obj object

A Panel Viewable, Bokeh Model, or any object that pn.panel() can wrap.

required

Returns:

Type Description
str or None

A JSON string containing docs_json, render_items, and ext_resources keys, or None if serialization fails.

pre_render(code, cache_dir, *, setup_code='', timeout=120)

Pre-render code and return the embedded JSON string.

Uses a content-hash cache so repeated builds with unchanged code are nearly instant.

Parameters:

Name Type Description Default
code str

The user-visible source code.

required
cache_dir Path or str

Directory for the content-hash cache (e.g. ".panel-live").

required
setup_code str

Optional code prepended before code (e.g. pn.extension(...)). Included in the content hash.

''
timeout int

Maximum seconds to wait for the subprocess.

120

Returns:

Type Description
str or None

Bokeh JSON string on success, None on failure or empty output.

sphinx

Sphinx extension for embedding interactive Panel apps via <panel-live>.

Registers a configurable RST directive (default panel-live, switchable to pyodide or python) that transforms directive content into <panel-live> HTML elements. Injects the panel-live JS/CSS and a window.PANEL_LIVE_CONFIG script into every page that uses the directive.

Optionally pre-renders Panel output at build time using a subprocess, aligned with the pattern in nbsite.pyodide.

Configuration

In conf.py::

extensions = ["panel_live.sphinx"]

panel_live_conf = {
    "directive_name": "panel-live",      # or "pyodide", "python"
    "panel_live_js": "https://cdn.jsdelivr.net/npm/@panel-extensions/panel-live@latest/dist/panel-live.js",
    "panel_live_css": "https://cdn.jsdelivr.net/npm/@panel-extensions/panel-live@latest/dist/panel-live.css",
    "mini_coi": True,                    # inject mini-coi.js for SharedArrayBuffer
    "pyodide_version": "v0.28.2",
    "panel_version": "1.8.7",
    "bokeh_version": "3.8.2",
    "panel_cdn": "https://cdn.holoviz.org/panel/",
    "bokeh_cdn": "https://cdn.bokeh.org/bokeh/release/",
    "requirements": ["panel"],
    "requires": {},
    "setup_code": "",
    "pre_render": True,
    "default_mode": "editor",
}
Asset loading

panel_live_js and panel_live_css accept either absolute URLs (https://...) or Sphinx-relative _static/ paths (e.g. _static/panel-live.js). For local development, copy the built JS/CSS into your project's _static/ directory and use local paths.

mini_coi (default True) copies a bundled mini-coi.js service worker to the build root for COOP/COEP headers needed by Pyodide.

PanelLiveDirective

Bases: Directive

RST directive that produces <panel-live> HTML elements.

Options map to HTML attributes on the <panel-live> element.

has_content = True class-attribute instance-attribute
option_spec = {'mode': directives.unchanged, 'theme': directives.unchanged, 'height': directives.unchanged, 'layout': directives.unchanged, 'auto-run': directives.unchanged, 'label': directives.unchanged, 'code-visibility': directives.unchanged, 'code-position': directives.unchanged, 'requirements': directives.unchanged, 'pre-render': directives.unchanged, 'preview': directives.unchanged} class-attribute
optional_arguments = 0 class-attribute instance-attribute
required_arguments = 0 class-attribute instance-attribute
run()

Parse directive options and return a raw HTML node.

setup(app)

Register the panel-live Sphinx extension.

Parameters:

Name Type Description Default
app Sphinx

The Sphinx application instance.

required

Returns:

Type Description
dict

Extension metadata.