Grafana Dashboard Validator · Observability
Encuentra las trampas de importación antes de publicar el dashboard.
22 reglas sobre un parse real de tu JSON: la variable de plantilla que nada declara, el placeholder ${DS_PROMETHEUS} que el provisioning nunca resolverá, el panel AngularJS que Grafana 12 eliminó, el panel sin type que dibuja una caja vacía. Cada hallazgo nombra el path JSON donde está — y nada de lo que pegues sale de la pestaña.
Se ejecuta en tu navegador: nada de lo que pegas sale de esta página. Cómo lo demostramos
Playground del Grafana Dashboard Validator
The uid is the stable identifier you choose and keep in git; the id is a row number belonging to one Grafana database, and it should be null in any file you commit. schemaVersion records which of Grafana's own dashboard-format migrations have already been applied to the JSON.
Rules pinned to grafana-12 / schemaVersion 41 — every version-sensitive finding is phrased as a range, never as one exact release.
Results update as you type — press Enter to run now.
Press Esc to release keyboard focus from the editor; ⌘/Ctrl + Enter lints and leaves the editor. Dashboards are too large for a shareable link, so use Save snapshot instead — it stays in this browser. Nothing you paste is uploaded.
Paste a Grafana dashboard JSON above — or tap an example — to see every finding here, with the JSON path it lives at and the fix.
El hueco
Un dashboard que importa no es un dashboard que funciona.
Grafana es indulgente de la peor manera posible. Una variable no declarada no es un error: la query simplemente corre con $env dentro, así que el panel queda vacío, o coincide con muchas más series de las que querías y queda lleno y equivocado. Un panel sin type dibuja una caja en blanco. Un repeat sobre una variable que no existe renderiza exactamente un panel y parece terminado. Nada en la interfaz objeta.
Y un dashboard es el único artefacto que nadie puede revisar. Un diff JSON de cuatro mil líneas donde los valores de gridPos se desplazaron, los ids se renumeraron y la interfaz reescribió fieldConfig no es un diff que un humano lea: es un diff que un humano aprueba. Los defectos que sobreviven a la revisión son los que se parecen a cualquier otra línea del archivo.
Pídele un dashboard a un asistente y heredas la misma clase de bug con más confianza encima. Los dashboards generados son fluidos: títulos de panel verosímiles, PromQL verosímil, un bloque templating — y queries que referencian variables que ese bloque nunca declara, una schemaVersion sacada del recuerdo de algún Grafana más antiguo, y tipos de panel eliminados hace dos releases mayores. Verificar la afirmación lleva segundos. Saber qué verificar es la parte difícil, y esa parte la hace esta herramienta: cada regla está en esta página, y también todo lo que se niega a señalar.
¿También revisas las queries? PromQL Explainer desmonta una query en lenguaje claro, y Alertmanager Route Tester demuestra dónde acaban de verdad las alertas que vigilan esos paneles.
El pipeline
Cómo funciona.
Cuatro pasos, todos dentro de tu pestaña, repetidos mientras escribes.
-
Parsear, y decir qué costó.
Primero JSON estricto. Después una marca de orden de bytes, `// comentarios`, comas finales, un wrapper de API `{ dashboard: … }` o un dashboard guardado como string escapado — cada caso recuperado y cada caso reportado, porque la API de Grafana no es así de indulgente.
-
Aplanar todos los layouts de panel.
`panels` de primer nivel, los hijos que guarda una fila colapsada, los hermanos que posee una fila desplegada, y `rows[]` de antes de schemaVersion 16 — todo a una sola lista donde cada panel recuerda el path JSON del que vino.
-
Indexar las variables.
Cada string del documento se recorre buscando `$var`, `${var}`, `${var:formato}` y `[[var]]`, y se compara con lo que declara `templating.list` más los built-ins de Grafana. Usadas, sin usar y sin resolver salen del mismo índice.
-
Ejecutar 22 reglas y reportar un path y una corrección.
Cada regla corre en su propio try/catch, así que una que tropiece cuesta una sola nota en lugar del resto de hallazgos. Cada diagnóstico nombra el path, el motivo y el cambio a hacer: listo para pegar en una revisión.
Referencia
Variables, versiones de esquema y las 22 reglas.
El conjunto de reglas está fijado a grafana-12 / schemaVersion 41, y cada hallazgo sensible a la versión se expresa como rango en lugar de un release exacto. 7 reglas son errores, 11 son advertencias y 4 son notas.
Las cuatro sintaxis de variable
Las cuatro resuelven hoy, y dos cosas que parecen variables no lo son. El linter lee todas ellas en cada string del documento: queries, títulos, formatos de leyenda, enlaces de panel, queries de anotaciones y las queries de otras variables.
| Forma | Desde | Qué hay que saber |
|---|---|---|
| $env | Siempre | Termina en el primer carácter que no sea letra, dígito o guion bajo, así que «$env-prod» es la variable env seguida del texto literal «-prod». |
| ${env} | Grafana 6 | La forma actual. Inequívoca junto al texto que la rodea, y la única que puede llevar un formato. |
| ${env:regex} | Grafana 6 | Una interpolación con formato — regex, csv, json, pipe, glob y otros — que es lo que hace segura una variable multivalor dentro de una query. |
| [[env]] | Antes de Grafana 6 | Obsoleta. Sigue resolviendo, no puede llevar formato. Se reporta como legacy-var-syntax. |
| $__rate_interval | Grafana 7.2 | Un built-in, no una de las tuyas. Todo nombre que empiece por dos guiones bajos se trata como built-in y nunca se reporta. |
| ${DS_PROMETHEUS} | — | No es una variable de plantilla en absoluto: es un placeholder de importación de __inputs. Se reporta como unresolved-ds-input. |
Hitos de schemaVersion
La columna de Grafana indica el rango de releases en el que llegó cada migración, no una correspondencia uno a uno: Grafana sube schemaVersion también dentro de releases menores, así que trátalos como puntos de referencia y no como tabla de consulta.
| schemaVersion | Grafana | Qué cambió |
|---|---|---|
| 16 | 5.x | Los paneles salieron de «rows» a un array «panels» de primer nivel, y gridPos reemplazó a span. Por debajo de aquí, todo lo relativo al layout se guarda de otra manera. |
| 36 | 8.3–9.x | La «datasource» de un panel pasó a ser una referencia { type, uid } en lugar de un nombre. Esta migración está detrás de la mayoría de importaciones «funciona en mi instancia». |
| 39 | 11.x | El esquema vigente durante la línea Grafana 11, donde los paneles Angular quedaron desactivados por defecto. |
| 41 | 12.x | El esquema más nuevo que conoce este linter. Cualquier valor por encima de 41 se reporta como nota, nunca como error. |
El catálogo de reglas
Un error significa que Grafana hace algo distinto de lo que dice el JSON; una advertencia significa que carga y está mal o no es portable; una nota conviene saberla. Cada hallazgo del playground enlaza a su regla aquí.
Dale un uid al dashboard
Sin uid, cada importación crea un dashboard NUEVO en lugar de actualizar el que ya está ahí — así que el mismo archivo importado dos veces deja dos copias, y cualquier enlace que alguien guardó apunta al que ahora está obsoleto. Grafana acepta hasta 40 caracteres entre letras, dígitos, guiones y guiones bajos.
Fix "uid": "api-slo"
id debe ser null en un archivo commiteado
id es un número de fila dentro de una única base de datos de Grafana. Llevado a otra instancia, o falla la importación o cae sobre un dashboard que no tiene nada que ver con el tuyo. Grafana asigna el suyo, siempre.
Fix "id": null
Ponle título al dashboard
Un title ausente o vacío aparece como «New dashboard»: imposible de encontrar en la búsqueda e indistinguible de cualquier otro tablero que alguien creó por accidente.
Fix "title": "API SLO"
Los ids de panel deben ser únicos
Grafana direcciona los enlaces a paneles, las URLs de «View panel» y los repeats por el id del panel. Dos paneles con el mismo id rompen las tres cosas, y la interfaz no dice nada: el segundo panel simplemente deja de ser direccionable.
Fix Renumera uno de los dos; los ids solo tienen que ser únicos dentro de este dashboard
Los nombres de variable deben ser únicos
Dos entradas en templating.list con el mismo name: Grafana conserva la última y descarta la primera en silencio, así que el tipo, la query y el valor por defecto de la variable son los que la segunda declaración fijó por casualidad.
Fix Renombra o elimina una de las dos
Reexporta una schemaVersion antigua
schemaVersion registra por cuáles de las migraciones de formato de Grafana ya pasó el JSON. Por debajo de 36, la datasource de un panel sigue siendo un nombre; por debajo de 16, los paneles siguen viviendo dentro de rows. Grafana migra al cargar, pero el archivo de tu repositorio no se migra solo, así que revisar ese archivo es revisar algo que Grafana nunca va a renderizar.
Fix Ábrelo en Grafana 9 o posterior y exporta de nuevo
Una schemaVersion que este linter no conoce
Más nueva que la 41 fijada, ausente por completo, o escrita como string en lugar de número. Se reporta como nota y nunca como error: un esquema desconocido es un límite de este linter, no un defecto de tu dashboard.
Fix Nada que cambiar — la nota marca los hallazgos de abajo como orientativos
Toda $variable tiene que estar declarada
Una referencia que nada declara se queda en la query como texto literal, así que la consulta corre con «$env» dentro y el panel no devuelve nada — o, peor, devuelve algo verosímil. Los built-ins de Grafana ($__rate_interval, $__from, $__range y los demás) se conocen y nunca se reportan, y tampoco $1, que es una retrorreferencia de regex.
Fix Añádela en templating.list o corrige la escritura
Una variable que nadie lee
Declarada pero nunca referenciada por ningún panel, query, título, enlace o anotación. Inofensiva en sí — salvo que una variable de tipo «query» que nadie lee ejecuta su consulta en cada carga del dashboard.
Fix Elimínala, o úsala
[[var]] es la forma anterior a Grafana 6
Sigue resolviendo y sigue estando obsoleta. Además es la única forma que no puede llevar un formato, así que un valor que necesita ser una alternación de regex o una lista CSV hay que reescribirlo antes de poder formatearlo.
Fix "${env}", o "${env:regex}" cuando la query necesita un patrón
Referencia datasources por uid, no por nombre
Un nombre solo resuelve si en la instancia destino existe una datasource con exactamente ese nombre. Esa única diferencia separa un dashboard que importa de uno que muestra «Datasource not found» en el Grafana de un compañero. La forma { type, uid } es lo que Grafana escribe desde schemaVersion 36.
Fix "datasource": { "type": "prometheus", "uid": "P1809F7CD0C75ACF3" }
${DS_…} necesita el diálogo de importación
«Export for sharing externally» reemplaza cada datasource por un placeholder de __inputs que solo Dashboards → Import rellena. Si en cambio provisionas ese mismo archivo, Grafana reporta «Datasource ${DS_PROMETHEUS} not found» — y una referencia ${DS_…} sin ningún bloque __inputs falla igual incluso pasando por el diálogo.
Fix Importa por el diálogo, o sustituye antes la { type, uid } real
Un panel sin query
Sin targets en absoluto, así que el panel se renderiza vacío. Los tipos de panel que nunca consultan — row, text, dashlist, news, alertlist, annolist — no se reportan, y tampoco los library panels, cuyas queries viven en la librería y no en este archivo.
Fix Añade un target, o borra el panel
graph, singlestat y table-old
Grafana 9–12 los migra cuando el dashboard carga, y precisamente por eso vale la pena señalarlos: lo que revisas en el JSON no es lo que nadie va a ver en pantalla. Volver a guardar desde Grafana 9 o posterior escribe la migración en el archivo, y entonces la revisión y el render por fin coinciden.
Fix "type": "timeseries" / "stat" / "table"
Plugins de panel AngularJS
El soporte de Angular quedó obsoleto en Grafana 9 y se eliminó a lo largo de Grafana 11–12. A diferencia de los tipos del núcleo de arriba, un plugin no tiene migración automática: el panel no se degrada, no renderiza nada.
Fix piechart para grafana-piechart-panel, geomap para grafana-worldmap-panel
Un repeat sobre nada
Un repeat que nombra una variable que no existe produce un solo panel y ningún mensaje de error. Una fila que debía desplegarse por cada clúster muestra en silencio uno, y el dashboard parece terminado.
Fix Declara la variable, o quita «repeat»
Un rango por defecto que nadie quiere
Rangos de más de un año, rangos que van al revés, rangos de longitud cero y expresiones que Grafana no puede parsear. Cada panel ejecuta el rango por defecto en el momento en que se abre el dashboard, lo que hace de esto el error de rendimiento más barato de arreglar del archivo.
Fix "time": { "from": "now-6h", "to": "now" }
Un refresh por debajo de diez segundos
Por debajo de diez segundos las queries se encolan más rápido de lo que terminan, en cada pestaña que tenga el dashboard abierto, y la datasource paga por todas. El propio min_refresh_interval de Grafana puede sobrescribirte igualmente, y entonces el JSON dice una cosa y la instancia hace otra.
Fix "refresh": "1m"
Un override que no aplica nada
Un matcher byName sin valor no coincide con ningún campo; un override con un array properties vacío no fija nada. Grafana conserva ambos en el JSON y no aplica ninguno, así que se leen como configuración que está haciendo algo.
Fix Dale algo con lo que coincidir y algo que fijar, o bórralo
Una fila sin paneles
Una fila colapsada con el array panels vacío es invisible hasta que alguien la despliega y no encuentra nada dentro. Una fila DESPLEGADA no se reporta cuando sus paneles la siguen como hermanos: así es como Grafana los guarda de verdad, y reportarlo dispararía en cualquier dashboard moderno.
Fix Borra la fila, o mueve paneles dentro
Un panel sin type
Nada le dice a Grafana qué renderizar, así que dibuja una caja vacía donde debería estar el panel. Un panel sin type suele haberse editado a mano, mergeado mal o generado por un script. Los paneles de biblioteca son la única excepción legítima: Grafana los guarda como {id, title, gridPos, libraryPanel} y esta regla los omite.
Fix "type": "timeseries"
Un panel que no ocupa espacio
Un gridPos con ancho o alto cero es invisible. Un gridPos ausente hace que Grafana recurra a una posición por defecto, donde los paneles pueden acabar unos encima de otros. La rejilla tiene 24 columnas de ancho.
Fix "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 }
La valla
Lo que no señala a propósito.
Cada uno de estos puntos se consideró y se descartó, y la misma lista está en un comentario al inicio del motor. Un silencio que puedes leer vale más que una regla que aprendes a ignorar.
Validación completa del esquema
Esto es un lint estructural, no una comprobación de esquema. El esquema de dashboards de Grafana es grande, versionado y sigue moviéndose; una comprobación parcial presentada como comprobación de esquema sería una mentira.
PromQL, LogQL y SQL dentro de targets
Un lenguaje de consulta merece su propia herramienta. El PromQL Explainer hace ese trabajo bien, en lugar de que esta herramienta parsee expresiones a medias.
Todo lo que necesita tu Grafana
Si un uid está ocupado, si un plugin está instalado, si una carpeta existe. Aquí no hay red, así que adivinar sería inventar.
Erratas dentro de nombres $__
Todo lo que empieza por dos guiones bajos se trata como built-in de Grafana, porque Grafana no deja de añadirlos. Llamar «no definido» al built-in del año que viene sería peor que dejar pasar una errata.
Paneles sin título
Los paneles de texto y las tarjetas de un solo dato están legítimamente sin título. Solo el título del DASHBOARD es obligatorio.
Solapamientos de gridPos y geometría del layout
Grafana recoloca la rejilla al cargar el dashboard, así que un solapamiento en el JSON no es un solapamiento en pantalla.
Tamaño de paneles en rows[] heredadas
Los layouts anteriores a schemaVersion 16 usaban span, no gridPos. Reportar ahí un gridPos ausente dispararía en cada panel de un dashboard cuyo problema real ya es un error.
Un refresh que este linter no puede parsear
Adivinar a qué equivale «1m30s» es adivinar, y el hallazgo hablaría de la suposición y no del dashboard.
Campos obsoletos dentro de las opciones de panel
Son distintos por plugin, cambian en cada release y Grafana los migra al cargar. Una regla sobre ellos quedaría desfasada en una versión menor.
Límites, dichos en lugar de escondidos: el linter lee hasta 5.000.000 caracteres, conserva como máximo 50 hallazgos por regla y 400 en total, y te dice el número real cada vez que se aplica un límite.
Siguiente paso
Pega el informe en la revisión.
«Copy report» te da la ejecución completa como texto plano: una línea por hallazgo, con el path JSON, el id de la regla y la corrección, y la versión fijada de las reglas arriba para que nadie tenga que adivinar qué lo comprobó. Y luego sigue: lee las queries que ejecutan esos paneles y demuestra que las alertas que vigilan llegan a alguien.
Grafana Dashboard Validator — 7 errors, 12 warnings, 3 notes
— variables: 2 defined, 2 unresolved, schemaVersion 27
Rules: grafana-12 / schemaVersion 41
ERRORS (7)
error duplicate-panel-id (panels[1].id): Panel id 1 is
already used by "Requests" (panels[0]).
error undefined-variable (panels[2].targets[0].expr):
"$cluster" is used here, but no template variable named
"cluster" is defined and it is not a Grafana built-in. FAQ
Tus preguntas, respondidas.
Toca una pregunta para desplegar la respuesta.
¿Qué comprueba el Grafana Dashboard Validator?
22 reglas sobre un parse real del dashboard: 7 errores, 11 advertencias y 4 notas. Los errores son los que cambian lo que hace Grafana — una variable de plantilla que nada declara, un placeholder ${DS_…} sin bloque __inputs, un panel AngularJS que no renderiza nada en Grafana 11–12, dos paneles con el mismo id, un repeat sobre una variable que no existe, un panel sin type, y una schemaVersion tan antigua que el JSON y el render son dos dashboards distintos. Las advertencias son los problemas de portabilidad y de revisión: un uid ausente, un id de base de datos dejado en un archivo commiteado, una datasource referenciada por nombre, un panel graph o singlestat obsoleto, un panel invisible de ancho cero, un rango de tiempo por defecto de más de un año, un refresh por debajo de diez segundos. Cada regla tiene su propia subsección en esta página, y los chips de regla del playground enlazan directamente ahí.
¿Mi dashboard sale alguna vez de mi navegador?
No. El parser y todas las reglas son JavaScript ejecutándose en tu pestaña — no hay servidor, ni llamada a una API, ni registro, así que se suben 0 bytes. Aquí importa más que en la mayoría de las herramientas: el JSON de un dashboard es un mapa de tu infraestructura interna. Nombres de métricas, uids de datasources, hostnames en formatos de leyenda, nombres de servicio en queries de variables, a veces una URL interna en un enlace de panel. Es exactamente el archivo que no deberías pegar en un formateador online cualquiera.
¿Esto es validación de esquema?
No, y lo dice en lugar de insinuar lo contrario. El esquema de dashboards de Grafana es grande, versionado y sigue moviéndose, así que una comprobación parcial presentada como comprobación de esquema sería peor que no comprobar nada. Lo que hace es un lint estructural: lee las formas que rompen importaciones y revisiones — variables, referencias a datasources, tipos de panel, ids, layout, tiempo y refresh — y nombra el path JSON de cada hallazgo. No parsea PromQL, no conoce tus plugins y no habla con ningún Grafana. La lista completa de lo que calla a propósito está arriba, en «Lo que no señala a propósito».
¿Por qué mi importación falla con «Datasource ${DS_PROMETHEUS} not found»?
Porque el archivo lo produjo «Export for sharing externally». Esa exportación reemplaza cada referencia a datasource por un placeholder — ${DS_PROMETHEUS} — y añade un bloque «__inputs» que describe qué necesita cada uno. Solo el diálogo Dashboards → Import lee ese bloque y te pide elegir una datasource real. Provisionar el mismo archivo, hacerle POST a la API o dejarlo en una carpeta sincronizada con Git salta el diálogo por completo: el placeholder sobrevive al dashboard guardado y todos los paneles fallan al resolverlo. O lo importas por el diálogo, o sustituyes la { type, uid } real antes de provisionarlo. Este linter reporta el placeholder como error en ambos casos, porque el archivo no se puede provisionar tal cual.
¿Cuál es la diferencia entre uid e id?
El uid es el identificador que eliges tú. Forma parte de la URL del dashboard, es lo que direccionan el provisioning y la API, y debe vivir en control de versiones junto al JSON. El id es un número de fila en la base de datos de una instancia concreta de Grafana: fuera de ella no significa nada. Un archivo con uid actualiza el mismo dashboard en cada importación; uno sin él crea una copia nueva cada vez. Un archivo con un id obsoleto o falla al importar o apunta a un dashboard que no tiene nada que ver con el tuyo, y por eso una exportación debería llevar «id»: null.
Mi dashboard usa paneles graph. ¿Seguirá funcionando en Grafana 12?
Los paneles del núcleo graph, singlestat y table-old se migran automáticamente al cargar el dashboard, así que siguen funcionando — pero el JSON de tu repositorio no se migra, lo que significa que el archivo que revisas y el panel que Grafana renderiza son dos cosas distintas. Vuelve a guardar el dashboard desde Grafana 9 o posterior para escribir la migración en el archivo. Los PLUGINS de panel AngularJS son otra historia: grafana-piechart-panel, grafana-worldmap-panel y los demás no tienen migración automática, y el soporte de Angular quedó obsoleto en Grafana 9 y se eliminó a lo largo de Grafana 11–12, así que esos paneles no renderizan nada y hay que reemplazarlos a mano.
¿A qué versión de Grafana está fijado, y si la mía es más nueva?
El conjunto de reglas está fijado a grafana-12 / schemaVersion 41, impreso en esta página y en cada informe copiado para que nunca haya ambigüedad sobre qué lo comprobó. Las reglas sensibles a la versión se expresan como rangos — «Grafana 9–12», «eliminado a lo largo de Grafana 11–12» — en lugar de como un release exacto, porque tu instancia está cerca de este punto y no exactamente en él. Si la schemaVersion de tu dashboard es mayor que 41, recibes una NOTA que lo dice y los hallazgos específicos del esquema quedan marcados como orientativos. Un esquema desconocido es un límite de este linter, no un defecto de tu dashboard, así que nunca se reporta como error.
¿Por qué una variable no declarada es un error y no una advertencia?
Porque Grafana no falla: no interpola nada y ejecuta la query con el texto literal dentro. Un selector PromQL {env="$env"} no coincide con ninguna serie, así que el panel queda vacío; un selector con coincidencia por regex puede coincidir con MUCHAS más series de las que querías, así que el panel queda lleno y equivocado. Ninguno de los dos casos produce un mensaje en ninguna parte de la interfaz. La única excepción que hace este linter es una referencia dentro de un string que parece una expresión regular, donde «$» también es un ancla de fin de línea: ahí el hallazgo baja a advertencia, porque ese «$env» que escribiste pudo ser realmente un ancla seguida de texto.
¿De qué tamaño puede ser el dashboard?
Hasta 5.000.000 caracteres, lo que cubre un dashboard generado de varios miles de paneles; uno de 500 paneles se analiza en unos pocos milisegundos. Por encima de ese límite se niega con un mensaje en lugar de congelarte la pestaña, porque una entrada tan grande es un log o un archivo comprimido, no un dashboard. Los hallazgos también están limitados: 50 por regla y 400 en total, y el panel indica el límite y el número real cada vez que uno se aplica — una lista truncada que no dijera que está truncada sería exactamente el tipo de error silencioso que esta herramienta existe para cazar.
More free, private DevOps tools.
El Grafana Dashboard Validator es una de las herramientas de OpsCanopy — un dosel creciente de validadores, conversores y testers que corren en el navegador y nunca tocan un servidor.
More in Observability
39 free tools, every one offline-capable — opscanopy.com works with no signup and nothing uploaded.
Relacionado: PromQL Explainer para las queries dentro de los paneles, Alertmanager Route Tester para saber dónde acaban las alertas de al lado, el Prometheus Relabel Tester para las labels por las que filtran tus variables, y el Conversor JSON ↔ YAML cuando hay que reformar el archivo de provisioning que rodea al dashboard — o explora el directorio completo de tools.
Sin afiliación con Grafana Labs, ni respaldo ni patrocinio suyo. «Grafana» es una marca de Raintank, Inc. dba Grafana Labs, usada aquí solo para describir qué lee esta herramienta. Se ofrece tal cual; confirma siempre un dashboard contra el Grafana que lo va a renderizar. OpsCanopy es gratis y abierto.