kibana-dashboard
Create Kibana dashboards from descriptions
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Create Kibana dashboards from descriptions
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Debug and analyze LLM eval runs โ view traces, compare runs, investigate failures, track costs. Use when debugging @kbn/evals failures, comparing eval runs, or analyzing LLM performance.
Start your security session with a personalized briefing โ attacks, alerts, cases, rules, threat intel. Use as the first thing when starting security work.
Guide users from zero to a working Elastic cluster โ Cloud or on-prem, connection config, first queries, and next steps.
Interactive guide for creating an APM service overview dashboard โ discovers service data, presents metrics, and creates a tailored dashboard.
Interactive guide for creating SLOs from discovered APM and metric data โ identifies candidates, lets user configure targets, and creates SLOs.
Create, configure, and manage Elasticsearch indices โ mappings, settings, templates, data streams, and lifecycle policies.
| name | kibana-dashboard |
| description | Create Kibana dashboards from descriptions |
Use when the user wants to create or customize Kibana dashboards programmatically via the kibana_create_dashboard tool.
list_indices and get_mappings to understand available fields.esql_query or search to sample data and verify field names.kibana_list_data_views and kibana_list_dashboards to check existing resources."aggs": { "ts": { "stats": { "field": "@timestamp" } } } to confirm when data exists. Dashboards with now-1h won't show data older than 1 hour.Call kibana_create_dashboard with a title, panels array, and time range.
The tool uses a simplified panel format โ it translates panels into the raw Kibana as-code API format (POST /api/dashboards with Elastic-Api-Version: 1). The tool also handles:
type: "lens" + config.attributes for the raw APIlabel properties from ES|QL XY y-axis columnslens.apiFormat, dashboardAgent.enabled, lens.enable_esql) dynamicallyKibana 9.4+ needs these in kibana.yml or as Docker env vars:
feature_flags.overrides:
dashboardAgent.enabled: true
lens.apiFormat: true
lens.enable_esql: true # Required for ES|QL datasets
Also set coreApp.allowDynamicConfigOverrides: true and server.restrictInternalApis: false to allow the tool to enable flags at runtime.
w:48, half = w:24, third = w:16, quarter = w:12.h:6, charts h:12โ14, gauge h:10โ12, tables h:10, markdown headers h:3โ4.KPI row (top of dashboard): 6 metric panels across (w:8 each) with h:6. Use ROUND() and readable column aliases so subtitles show "Tank Level (%)" not "avg_val".
Section headers: Use markdown panels (h:3) with ### ๐ญ Section Name to group related charts. Organize by domain (e.g. Chemical Systems, Water Systems, Sanitation).
Chart grouping: 3 charts per row at w:16 each. Use consistent chart types within a section (line for precision, area for flow/continuous, bar_stacked for volume).
Summary tables: Place at the bottom. Split into side-by-side tables (w:24 each) โ e.g. "Metrics by Site" + "Metrics by Device Type" โ rather than one huge table.
Time range: Always check actual data timestamps before setting time_from. For demo data that spans minutes, use now-1h; for live data, now-24h is typical. Match BUCKET() interval to data density (30s for 2-min data, 5min for hourly data).
Markdown panel:
{ "type": "DASHBOARD_MARKDOWN", "content": "# Title\nBody text", "grid": { "x": 0, "w": 48, "h": 4 } }
Metric panel (ES|QL):
dataset at panel level: { "type": "esql", "query": "..." }metrics is an array (not a single object): [{ "type": "primary", "operation": "value", "column": "col_name" }]"type": "secondary"breakdown_by: { "operation": "value", "column": "col_name" }{
"type": "metric",
"title": "Avg Dosing Rate",
"dataset": { "type": "esql", "query": "FROM my-index | STATS `L/min` = AVG(field)" },
"metrics": [{ "type": "primary", "operation": "value", "column": "L/min" }]
}
XY panel (ES|QL):
breakdown_by (NOT breakdown โ the wrong name silently causes validation failure).y columns accept ONLY operation + column โ no label property (causes hard validation error).dosing not avg_val).line, area, area_stacked, area_percentage, bar, bar_stacked, bar_horizontal, bar_horizontal_stacked, bar_percentage, bar_horizontal_percentage{
"type": "xy",
"title": "Trend Over Time by Category",
"layers": [{
"dataset": { "type": "esql", "query": "FROM my-index | WHERE field IS NOT NULL | STATS val = AVG(field) BY @timestamp = BUCKET(@timestamp, 5 minute), category = attributes.category" },
"type": "line",
"x": { "operation": "value", "column": "@timestamp" },
"y": [{ "operation": "value", "column": "val" }],
"breakdown_by": { "operation": "value", "column": "category" }
}]
}
Gauge panel (ES|QL):
dataset at panel levelmetric is a single object (not an array): { "operation": "value", "column": "col" }{
"type": "gauge",
"title": "Water pH (target 6.5โ8.5)",
"dataset": { "type": "esql", "query": "FROM my-index | STATS pH = AVG(metrics.water.ph)" },
"metric": { "operation": "value", "column": "pH" }
}
Datatable panel (ES|QL):
dataset at panel levelrows + metrics arrays (NOT columns โ that key is rejected){ "operation": "value", "column": "col" }{
"type": "datatable",
"title": "Summary Table",
"dataset": { "type": "esql", "query": "FROM my-index | STATS `Count` = COUNT(*), `Avg Value` = AVG(f) BY `Category` = cat_field | SORT `Category`" },
"rows": [{ "operation": "value", "column": "Category" }],
"metrics": [{ "operation": "value", "column": "Count" }, { "operation": "value", "column": "Avg Value" }]
}
\L/min`or`Avg pH`in STATS, not generic names likeavg_val`.BUCKET(@timestamp, 10 second). If data spans hours, use 5 minute. Too-large buckets collapse everything into one point.site = attributes.site.name instead of raw attributes.site.name.WHERE field IS NOT NULL to avoid empty data points.counter_double) reject all aggs (AVG, SUM, MAX, MIN). Cast first: EVAL c = TO_DOUBLE(counter_field) | STATS avg_c = AVG(c). This applies to fields with time_series_metric: "counter" (e.g. metrics.sanitation.cycle_count).water.ph for WaterSystem only), aggregating across all device types shows null for rows without that metric. This is expected.| Mistake | Symptom | Fix |
|---|---|---|
dataset at XY panel root | Validation error on all layers | Move dataset into each layer |
breakdown instead of breakdown_by | Validation error | Use breakdown_by |
label on ES | QL XY y-axis columns | Validation error |
Single metric for metric panels | Validation error | Use metrics array with type: "primary" |
columns array on datatable | Validation error | Use rows + metrics arrays |
time_range: "now-1h" on old data | Empty panels | Check actual data timestamps, set absolute range |
BUCKET(@timestamp, 5 minute) on 1-min data | Single flat point per series | Use smaller bucket (e.g. 10 second) |
SUM/AVG/MAX on OTel counter metrics | ES | QL verification_exception |
| Raw field names as ES | QL aliases | Ugly subtitles like avg_val |
Missing lens.enable_esql feature flag | esql dataset rejected with "expected dataView or index" | Add flag to kibana.yml or Docker env |
For non-ES|QL panels, use { "type": "dataView", "id": "data-view-id" } or { "type": "index", "index": "my-index-*" }. Operations use aggregation names: "count", "average", "sum", "unique_count", "last_value", "percentile", "terms", "date_histogram" with a field property.
If calling POST /api/dashboards directly (without kibana_create_dashboard), the panel structure differs:
{
"title": "Dashboard Title",
"panels": [
{
"type": "lens",
"uid": "unique_id",
"grid": { "x": 0, "y": 0, "w": 24, "h": 10 },
"config": {
"attributes": {
"type": "metric",
"title": "Panel Title",
"dataset": { "type": "esql", "query": "..." },
"metrics": [{ "type": "primary", "operation": "value", "column": "col" }]
}
}
}
]
}
Note: Lens panels are wrapped in type: "lens" with chart config inside config.attributes. Markdown panels use type: "DASHBOARD_MARKDOWN" with config.content. The kibana_create_dashboard tool handles this translation automatically.