View Plugin — Python

A complete example of a remote view plugin in Python that adds a "Hello World" tab to the workspace dashboard.

Project Setup

mkdir hello-view-plugin && cd hello-view-plugin
python -m venv venv && source venv/bin/activate
pip install grpcio grpcio-tools

Generate Python code from the proto files:

python -m grpc_tools.protoc \
  --proto_path=. \
  --python_out=. \
  --grpc_python_out=. \
  proto/plugin.proto proto/view.proto

Full Source — main.py

import os
from concurrent import futures

import grpc

import plugin_pb2
import plugin_pb2_grpc
import view_pb2
import view_pb2_grpc


MANIFEST_HCL = """plugin "hello-view" {
  version     = "1.0.0"
  description = "A minimal view plugin that displays Hello World."
  author      = "yourorg"
  icon        = "eye"
  type        = "remote"
}
"""


class HelloViewPlugin(
    plugin_pb2_grpc.PluginServicer,
    view_pb2_grpc.ViewProviderServicer,
):
    """Implements both Plugin and ViewProvider services."""

    # --- Plugin service (required) ---

    def Register(self, request, context):
        return plugin_pb2.RegisterResponse(
            name="hello-view",
            capabilities=[plugin_pb2.VIEW_PROVIDER],
            metadata={"version": "1.0.0"},
        )

    def Health(self, request, context):
        return plugin_pb2.HealthResponse(status=plugin_pb2.SERVING)

    def GetManifest(self, request, context):
        return plugin_pb2.GetManifestResponse(manifest_hcl=MANIFEST_HCL)

    # --- ViewProvider service ---

    def ListViews(self, request, context):
        return view_pb2.ListViewsResponse(
            views=[
                view_pb2.ViewDefinition(
                    name="hello",
                    label="Hello World",
                    icon="sparkles",
                    scope="workspace",
                )
            ]
        )

    def RenderView(self, request, context):
        if request.view_name != "hello":
            return view_pb2.RenderViewResponse()

        ws_id = request.workspace_id

        hcl = f"""
component "greeting" {{
  type    = "text"
  content = "Hello, World!"
  style   = "heading"
}}

component "info" {{
  type    = "text"
  content = "This view is rendered by the hello-view plugin for workspace {ws_id}."
  style   = "muted"
}}

component "status" {{
  type = "stat-group"

  stat "uptime" {{
    label = "Status"
    value = "Online"
  }}

  stat "version" {{
    label = "Version"
    value = "1.0.0"
  }}
}}

component "details" {{
  type  = "section"
  title = "About This Plugin"

  component "description" {{
    type    = "text"
    content = "This is a minimal example of a VIEW_PROVIDER plugin. It uses the platform's HCL component library to render UI without writing any HTML or JavaScript."
  }}

  component "tip" {{
    type    = "alert"
    message = "View plugins can compose any combination of text, stats, tables, forms, code blocks, and chat components."
    level   = "info"
  }}
}}
"""
        return view_pb2.RenderViewResponse(component_hcl=hcl)

    def HandleRoute(self, request, context):
        return view_pb2.RouteResponse(
            status_code=404,
            body=b'{"error":"not found"}',
        )

    def HandleStream(self, request_iterator, context):
        context.abort(grpc.StatusCode.UNIMPLEMENTED, "streaming not supported")


def serve():
    port = os.environ.get("PORT", "9011")
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    servicer = HelloViewPlugin()
    plugin_pb2_grpc.add_PluginServicer_to_server(servicer, server)
    view_pb2_grpc.add_ViewProviderServicer_to_server(servicer, server)
    server.add_insecure_port(f"[::]:{port}")
    server.start()
    print(f"hello-view plugin listening on :{port}")
    server.wait_for_termination()


if __name__ == "__main__":
    serve()

Run It

python main.py
# hello-view plugin listening on :9011

Register in Workspace

workspace "my-workspace" {
  plugin "hello-view" {
    source  = "remote://localhost:9011"
    version = "1.0.0"
  }
}

Start the workspace and navigate to its page. You'll see a "Hello World" tab. Click it to see the rendered view.

How It Works

  1. Platform calls ListViews — discovers the "hello" view with scope "workspace"
  2. Platform adds a tab labeled "Hello World" to the workspace page
  3. When the user clicks the tab, platform calls RenderView(view_name="hello")
  4. Your plugin returns HCL component definitions
  5. Platform parses the HCL and renders it to HTML using its component library
  6. The rendered HTML is injected into the tab content area

Adding Interactivity

View plugins can go far beyond static content. See the HCL Component Reference for: