Skip to main content
The Charm Store Universal Renderer maps structured agent output to React components — bar/line/pie charts, data grids, metric cards — without you writing frontend code. For full payload schemas, see the UI Protocol Reference.

Built-in render types (no store changes)

Return a JSON object with _charm_render_type set to one of:
TypeUse case
bar_chartCategorical comparisons
line_chartTrends over time
pie_chartPart-to-whole
data_gridTabular results
metric_cardKPI-style metrics
These work for any published agent — no store deploy required.

Custom Python agents

def agent(inputs):
    return {
        "_charm_render_type": "bar_chart",
        "title": "Quarterly Sales",
        "data": [
            {"name": "Q1", "value": 15000},
            {"name": "Q2", "value": 22000},
        ],
    }
With the built-in custom adapter, return the dict directly from your entry point. With a third-party adapter, return {"status": "success", "output": {...}} or ensure your adapter forwards structured output.

Framework adapters (LangChain, CrewAI, OpenClaw)

Configure the model to emit JSON matching the schema. Example for OpenClaw:
runtime:
  adapter:
    type: openclaw
  config:
    system_prompt: |
      Return your final answer as JSON only:
      {
        "_charm_render_type": "metric_card",
        "title": "Analysis Results",
        "metrics": [
          {"label": "Confidence", "value": "98.5", "unit": "%"}
        ]
      }
Adapters parse JSON final messages and forward structure to the Store.

Metric card example

return {
    "_charm_render_type": "metric_card",
    "title": "Performance",
    "metrics": [
        {"label": "Active Users", "value": 1204, "change": 12, "unit": "users"},
        {"label": "Error Rate", "value": "0.3", "unit": "%"},
    ],
}

Markdown and HTML fallback

Return a plain string for standard Markdown rendering. HTML and SVG snippets are sanitized (scripts stripped):
def agent(inputs):
    return "### Status\n\nAll systems operational."

Streaming text + rich final

Use CharmEmitter for intermediate markdown, then return structured JSON as the final result:
from charm.core.io import CharmEmitter

def agent(inputs):
    CharmEmitter.emit_delta("Analyzing dataset...\n")
    return {
        "_charm_render_type": "metric_card",
        "title": "Results",
        "metrics": [{"label": "Score", "value": 98, "unit": "%"}],
    }

Sharing Custom Output Renderers with the Community

You can now build and share your own custom UI components (like a specialized stock chart, a unique data grid, or a 3D molecule viewer) without waiting for a store deployment! Charm supports dynamic ESM module loading for community output renderers. To share a custom renderer:
  1. Build a React Component: Write a React component that takes a payload prop and export it as your default export.
  2. Publish to NPM: Package your component and publish it to NPM. Ensure your package is ESM compatible.
  3. Register in the Community Plugin: Submit a Pull Request to the charm-community-plugin repository. Add your renderer’s details (including the id which corresponds to your _charm_render_type, and the npm_package name) into renderers/registry.json.
Once your PR is merged, the Charm Store will dynamically fetch your component from esm.sh directly in the browser whenever an agent emits your _charm_render_type! You can also browse all community renderers on the Plugins page in the Charm Store.