Ext Ops Panel
Abrir o painel

Início/Docs/Métricas personalizadas

Contrato de métricas personalizadas

Versão do contrato 2.1.0 · payload schema_version 2.0

As métricas personalizadas permitem que o próprio serviço da sua extensão exponha os números que importam para ela (conversões, receita, saúde do backend, funis) no mesmo painel que já acompanha as instalações e a saúde na Chrome Web Store. O contrato é orientado por metadados e tem superfície dupla: cada métrica declara onde e como aparece tanto no painel quanto no status do Telegram, de modo que o painel continua sendo um renderizador genérico, sem código específico de produto.

↓ Baixar OpenAPI 3.1 (YAML) /custom-metrics-openapi.yaml · info.version 2.1.0

1. Como funciona (superfície dupla)

Seu serviço expõe um endpoint de métricas. O Ext Ops Panel é o cliente: ele consulta esse endpoint (nunca o contrário) e renderiza a resposta. A resposta alimenta duas superfícies ao mesmo tempo:

O painel mescla suas métricas com as métricas básicas do próprio painel (instalações / desinstalações / reinstalações / feedback / Chrome Web Store / saúde), que ficam na aba reservada base e na linha do Telegram de mesmo nome.

2. O endpoint e a autenticação

O painel chama o seu endpoint com GET e espera o payload v2 como resposta 200. Os dois modos de pull têm o mesmo formato:

A autenticação usa uma das opções nenhum, bearer ou api_key_header. Os segredos ficam criptografados em repouso no backend do painel e nunca são entregues à extensão do painel. Se um pull falhar, o painel se degrada de forma controlada (último snapshot ou bloco oculto) sem quebrar as métricas básicas.

3. Estrutura do payload

O payload de nível superior traz o layout (abas, seções, linhas do Telegram) e o array de métricas.

CampoTipoNotas
schema_versionstringObrigatório. Deve ser "2.0".
generated_atstring (date-time)Quando o payload foi composto (detecção de atualidade / inatividade).
tabsTab[]Abas do painel. Consulte a seção 4.
sectionsSection[]Grupos de métricas dentro de uma aba (tab, id, title, order).
telegramTelegramMetaTemplate header + lines[]. Consulte a seção 7.
metricsMetric[]As próprias métricas. Consulte as seções 5 e 6.

4. Abas e ícones de abas

Uma aba é { id, title, order, icon? }. A aba reservada base pertence ao próprio painel (icon="layout-dashboard"); o seu serviço adiciona as demais. Quando há muitas abas abertas, a aba ativa mostra ícone + título e as outras se recolhem para apenas o ícone (o título fica no tooltip e no aria-label).

Dois ids de aba são reservados ao painel: base (a aba Basic) e monitoring (a aba Monitoring integrada que os relatórios de heartbeat preenchem, veja o contrato de monitoramento). Não os declare em tabs: uma declaração é ignorada e o painel define título, ícone e ordem. As métricas continuam apontando para eles via dashboard.tab.

O valor icon é uma de duas coisas:

O painel traz um catálogo selecionado de 60 nomes Lucide (leve para o pacote MV3). O mesmo catálogo serve para ícones de abas e ícones de metas (seção 5.3):

5. Métrica: valor + dois blocos de posicionamento

Uma métrica traz um valor tipado mais dois blocos independentes: dashboard (onde e como na extensão) e telegram (onde e como na mensagem). Qualquer um dos dois pode ser omitido ou ocultado com show:false, então uma métrica pode existir em uma única superfície.

CampoNotas
idId de métrica estável, único no payload.
kindTipo de dado (seção 5.1). Define quais campos de valor se aplicam.
labelRótulo canônico legível para pessoas.
value e campos do tipoOs campos do payload dependem de kind (seção 5.1).
severityUm de ok, info, warn, crit (define a cor / o emoji).
shareableBooleano opcional, padrão true. Quando false, a métrica é exclusiva do dono: em um projeto compartilhado como somente leitura, ela é removida no servidor e nunca chega a um observador / visualização pública (use para métricas financeiras). Controla o acesso, não o posicionamento.
dashboard{ show, tab, section, order, name, render, options } (seção 5.2).
telegram{ show, line, pos, format, sep, when } (seções 6 e 8).

5.1. Tipos (tipo de dado + payload)

Os tipos formam um conjunto aberto: um tipo desconhecido é ignorado pelo renderizador (compatível com versões futuras).

kindCampos do payloadExemplo de uso
numbervalue (+ opcional delta, unit)contagens de instalações / feedback
deltavalue, delta (renderiza 1200 (+72))total (+hoje)
percentvalue (0 a 100)taxa de sucesso do motor
durationseconds ou minutesp50/p95/média, atualidade
bytesbytes ou kbtamanho da saída (~522KB)
ratingvalue, count5.0★ (1)
moneyvalue, currency, opcional deltaMRR, gastos
statusstate + severitysemáforo de saúde
seriespoints:[{date,count}], opcionalmente vários series:[{name,points}], empilhadas com stack: trueinstalações diárias, usuários ativos
intradayseries:[{name,intraday:[{minute,value}]}], opcional step_min (padrão 15), tzhoje em intervalos de 15 minutos
hour_histogram{ weekdays:[...], start, end, tz }conversões por hora
funnelsteps:[{key,label,value}]da tabela de preços ao checkout
breakdownitems:[{label,value,unit?,sub?/money?,country?,parts?}], opcional ranges:{today,week,month,all}assinaturas por plano, por motor, por superfície; visitas por origem ou país
listitems:[{text,count?,severity?}] no nível superior da métrica, não dentro de valueerros recentes
tableitems:[{…}], um objeto por linha; colunas de dashboard.options.columns, senão as chaves da primeira linhafilas, envios recentes
textvalue (string)texto livre

Uma métrica table:

{ "id": "queues", "kind": "table", "label": "Mail queues",
  "items": [ { "queue": "outbound", "size": 3, "oldest": "2m" },
             { "queue": "deferred", "size": 0, "oldest": "-" } ],
  "dashboard": { "show": true, "tab": "backend", "section": "engines", "order": 5, "render": "table",
                 "options": { "columns": ["queue", "size", "oldest"] } } }

5.2. Renderização (como exibir no painel)

dashboard.render escolhe o visual (geralmente coincide com kind, mas pode diferir, por exemplo, exibir um percent como badge). Também é um conjunto aberto.

renderAparênciaOpções (dashboard.options)
kpiCartão: nome + valor grande + delta; com stack, as partes em uma fileira abaixo do valortone, stack
badgePílula de status / pictograma por severidade–
chartGráfico de linhas (Mês/Todo o período, média móvel de 7 dias, hover); com stack: true, colunas empilhadas por dia com uma linha Totalseries, defaultMode, stack
intradayHoje por intervalos (96 por dia UTC): barras empilhadas ou linhas suavizadas, eixo de tempo HH:MM, leitura ao passar o mouse em todas as sériesmode (bars | lines, padrão lines)
hour_histogramBarras por hora, agrupadas em Dias úteis / Fim de semana / Por dia da semanaseries
funnelEtapas com a conversão entre elas–
breakdownBarras categóricas horizontais com valor + participação no total. Com ranges, um seletor hoje / 7 dias / 30 dias / tudo; com map: countries, um mapa-múndi acima da lista; com stack, barras divididas em partes alternáveisitems, defaultRange, map, stack
tableTabela (por exemplo, feedback)columns
textParágrafo / inline–
statusEstado de semáforo por severidade–
listLista de itens com contagem / severidade opcional–

intraday vs. chart. O eixo do chart é diário (points[].date = YYYY-MM-DD), e a visualização Mês dele mostra só os últimos 30 pontos, então os 96 intervalos de hoje não cabem nela. intraday é o gráfico Hoje do próprio painel, exposto como tipo do contrato: a mesma convenção em que minute conta os minutos desde o início do dia UTC (0, 15, …, 1425), mas as séries e os dados são declarados pela fonte. Envie apenas os intervalos não vazios; o painel preenche o resto.

Um intervalo vazio é null, não 0. Para uma média (por exemplo, o tempo médio de conversão), um zero seria uma mentira, então a linha se interrompe em vez de despencar para zero. Um intervalo ausente e value: null são equivalentes: a linha se interrompe, nenhuma barra é desenhada e a leitura ao passar o mouse mostra um traço.

Empilhado: o total e suas partes em um gráfico e uma lista. Quando uma métrica é composta de partes (por exemplo, as visualizações de página de dois sites), envie o total e as partes como UMA métrica: o painel desenha as partes umas sobre as outras e a legenda liga e desliga cada uma, sem abas extras nem filtros suspensos. As duas extensões são aditivas: um renderizador que não as conhece desenha a mesma métrica de antes.

Gráfico: kind: "series" com dashboard.options.stack: true. As séries se empilham na ordem do array: a primeira é a camada de baixo. O painel desenha uma coluna por dia (em todo o período, por janela de 7 dias) com um segmento por série visível, então o topo de uma coluna é a soma exata das séries visíveis; um dia em que todas são zero não tem coluna. Clicar em um item da legenda oculta uma camada (a última visível continua ativa); o resto é redesenhado a partir de zero e o eixo de valores acompanha o total visível. Ao passar o mouse sobre uma coluna, ou chegar a ela com as setas, a leitura mostra cada camada visível e uma linha Total. Todo o período empilha a média móvel de 7 dias de cada série, marcada com ~. Qualquer outro valor de stack mantém as linhas sobrepostas.

Lista: kind: "breakdown" com dashboard.options.stack definido como lista ordenada de partes [{key, label}], e parts: {"key": number} em cada item, em items e em cada janela de ranges. value deve ser igual à soma de todas as partes. O painel mostra um chip por parte que a liga ou desliga para o cartão inteiro, em todas as janelas. Uma linha mostra a soma de suas partes visíveis, linhas com zero ficam ocultas, a lista é reordenada por essa soma e a barra é dividida em um segmento por parte visível. Com map: "countries", os círculos acompanham as somas visíveis. Um item sem parts conta inteiro, não importa o que esteja desligado.

{ "id": "site_sources", "kind": "breakdown", "label": "Sources",
  "items": [ { "label": "Search engines", "value": 812, "parts": { "makcards": 790, "journalingapp": 22 } } ],
  "ranges": { "today": [ { "label": "Search engines", "value": 30, "parts": { "makcards": 29, "journalingapp": 1 } } ],
              "month": [ { "label": "Search engines", "value": 812, "parts": { "makcards": 790, "journalingapp": 22 } } ] },
  "dashboard": { "show": true, "tab": "site", "section": "site_sources", "render": "breakdown",
                 "options": { "defaultRange": "month",
                              "stack": [ { "key": "makcards", "label": "makcards.online" },
                                         { "key": "journalingapp", "label": "journalingapp.app" } ] } } }

Bloco: um bloco kpi do tipo number ou delta aceita a mesma lista dashboard.options.stack e parts: {"key": number} na própria métrica. O valor grande continua sendo value, que deve ser igual à soma de todas as partes. Abaixo dele, o painel mostra as partes em uma fileira na ordem da pilha (num bloco estreito a fileira quebra), com a cor que o mesmo nome tem em um gráfico empilhado da aba; uma parte zerada aparece esmaecida. O bloco não tem interruptores. Os cabeçalhos da lista de projetos e o Telegram continuam lendo value.

{ "id": "site_views_today", "kind": "number", "label": "Page views", "value": 42,
  "parts": { "portal": 30, "cabinet": 4, "backoffice": 8 },
  "dashboard": { "show": true, "tab": "site", "section": "site_today", "render": "kpi",
                 "options": { "stack": [ { "key": "portal", "label": "zakazindia.ru" },
                                         { "key": "cabinet", "label": "Client cabinet" },
                                         { "key": "backoffice", "label": "Backoffice" } ] } } }

5.3. A lista de projetos: usuários e metas da aba Básica

A lista de projetos mostra alguns números de um serviço web sem abri-lo. O painel os lê apenas da aba base e apenas quando a métrica tem um value numérico. A primeira correspondência vence.

MétricaComo o painel a encontraOnde é exibida
DAU, WAU, MAUO id, o label ou o nome no dashboard é exatamente dau, wau ou mau (sem diferenciar maiúsculas).WAU e MAU na coluna Usuários, DAU em Hoje.
Metas 1 a 3O id é exatamente goal_1, goal_2 ou goal_3.Hoje, na mesma linha que DAU: um ícone e um número.

Uma meta é uma ação-alvo dos seus usuários: cadastros, compras, formulários enviados. Você escolhe a janela do número e o painel o mostra como está. Recomendamos contar o dia de hoje e nomear a janela no label (por exemplo, Cadastros hoje). O label é o tooltip, até 80 caracteres. O campo icon aceita um nome do catálogo da seção 4; emoji não são aceitos para metas. Sem ícone, ou com um nome desconhecido, o painel desenha target. Um zero aparece como um traço atenuado.

{ "id": "goal_1", "kind": "number", "label": "Sign-ups today", "value": 12, "icon": "user-plus",
  "dashboard": { "show": true, "tab": "base", "section": "goals", "render": "kpi" } }

O painel guarda apenas os valores mais recentes, como no DAU. Cada consulta bem-sucedida os substitui, e uma meta que não está mais na resposta desaparece da lista.

Várias superfícies

Uma fonte de métricas personalizadas pertence a uma superfície do seu serviço, e o painel guarda os números por superfície. A lista de projetos mostra a soma; a página do projeto mostra cada superfície separadamente.

Extensões do Chrome

Uma extensão segue a mesma convenção. Quando o seu feed envia dau e metas na aba base, a lista de projetos mostra-os numa linha a mais abaixo dos contadores de instalações, desinstalações, feedback e reinstalações, no mesmo formato da linha Hoje de um serviço web.

5.4. Limites (warn e crit)

Uma métrica numérica pode trazer thresholds. Nesse caso, o painel calcula sozinho a severidade da métrica, a cada consulta e a cada recebimento de heartbeat, e mostra os limites ao lado do valor: 41 (threshold 20/100), amarelo entre warn e crit, vermelho a partir de crit. A severidade que o seu serviço envia para essa métrica é substituída pela calculada.

CampoSignificado
directionabove: pior quando mais alto (fila, CPU). below: pior quando mais baixo (disco livre, dias restantes).
warn, critNúmeros. Com above, um valor igual ou acima de warn é amarelo e igual ou acima de crit é vermelho; below faz o inverso. Com above, warn não pode passar de crit; com below, não pode ficar abaixo.
unitRótulo opcional de até 16 bytes para a legenda: threshold 80/95%.
recoverHisterese opcional: uma métrica que estava em warn ou crit só volta para ok quando o valor passa de recover; entre os dois ela continua em warn.
{
  "id": "queue",
  "kind": "number",
  "label": "Mail queue",
  "value": 41,
  "thresholds": { "direction": "above", "warn": 20, "crit": 100, "recover": 15 },
  "dashboard": { "show": true, "tab": "backend", "section": "mail", "order": 1, "render": "kpi" },
  "telegram": { "show": true, "line": "mail", "pos": 10, "format": "queue {value}", "when": "severity>=warn" }
}

Consulta agendada. Em um serviço web, você pode ativar a consulta agendada de uma fonte nas configurações dela. O painel passa a consultar o endpoint em um intervalo que vai do mínimo do seu plano (5 minutos no Trial e no Indie, 1 minuto no Studio e no Fleet) até 24 horas, com a mesma requisição de qualquer consulta: um User-Agent honesto e no máximo uma requisição por minuto. Ela ocupa uma unidade de monitoramento, por isso não está disponível no Free. Um limite ultrapassado abre um incidente para essa métrica e avisa você; uma nova ultrapassagem não avisa de novo. O incidente se fecha depois de uma consulta sem ultrapassagem se o intervalo for de 1 hora ou mais, e depois de duas consultas seguidas sem ultrapassagem se for menor. Uma consulta com falha não muda nada e mostra o motivo como último erro. Até 20 métricas com limites por fonte geram incidentes.

6. A paleta pertence ao painel

As cores de gráficos e séries são atribuídas pelo painel a partir dos tokens da marca, não pela fonte. options.series[] carrega apenas a identidade ({key,label,order}); um campo color no payload é apenas indicativo e ignorado. Chaves semânticas são mapeadas para tokens fixos (paid para destaque, free para neutro suave, failed/error para perigo, ready/success para sucesso); as demais chaves recebem a paleta categórica da marca por posição. Preenchimentos e linhas das séries mantêm contraste de pelo menos 3:1 nos dois temas (WCAG 1.4.11), e texto e rótulos de pelo menos 4,5:1. Isso protege a marca de cores hex externas arbitrárias.

7. Templates do Telegram

telegram.header é o cabeçalho da mensagem; telegram.lines[] são linhas lógicas ({ id, order, prefix, when }). O compositor reúne todas as métricas com um dado telegram.line, filtra por when, ordena por pos, renderiza cada uma pelo template format e as une com o sep de cada métrica. O prefix da linha é impresso primeiro. Tudo é texto simples.

PlaceholderSaída
{value}Conforme o tipo (número 1,200; percentual 99%)
{value:k}1.2k / 3.0M (compacto)
{delta:+}+72 / -3
{value:pct}99%
{seconds:dur}28s / 5m 47s
{minutes:age}45m / 2h 30m / never
{bytes:size}~522KB / 1.4MB
{value}★ ({count})5.0★ (1)
{sev}Emoji de severidade: 🟢 ok, 🟡 warn, 🔴 crit, ⚪ idle
{threshold}O limite da métrica: threshold 20/100. Sem ele no formato, uma métrica com limites recebe (threshold 20/100) no final.
{trend}↗ / ↘ (±5pp vs 7d)

8. Condições de visibilidade (when)

when controla se um campo de métrica (ou uma linha inteira do Telegram) é exibido. Valores:

whenMostra o campo
alwaysSempre (padrão).
nonzeroSomente quando o valor não for 0/vazio.
severity>=warnSomente quando a severidade é warn ou crit.
changedSomente quando mudou desde o snapshot anterior.

Valores desconhecidos de when se comportam como always (compatível com versões futuras).

9. Exemplo de payload

{
  "schema_version": "2.0",
  "generated_at": "2026-07-02T23:17:30Z",
  "tabs": [
    { "id": "conv", "title": "Conversions", "order": 1, "icon": "trending-up" }
  ],
  "sections": [
    { "tab": "conv", "id": "by_hour", "title": "By hour", "order": 0 }
  ],
  "telegram": {
    "header": "{name} • {cws.users} • {cws.rating}★ ({cws.rating_count})",
    "lines": [
      { "id": "base", "order": 0 },
      { "id": "conv", "order": 1, "prefix": "{sev} Conv " }
    ]
  },
  "metrics": [
    {
      "id": "conv_today",
      "kind": "number",
      "label": "Conversions today",
      "value": 254,
      "severity": "ok",
      "shareable": true,
      "dashboard": {
        "show": true,
        "tab": "conv", "section": "by_hour", "order": 10,
        "name": "Today", "render": "kpi", "options": {}
      },
      "telegram": {
        "show": true,
        "line": "conv", "pos": 20,
        "format": "today {value}", "sep": " • ", "when": "always"
      }
    }
  ]
}
Somente v2 nativo é compatível. Seu serviço retorna as métricas junto com um layout pronto, conforme este contrato; não há conectores para formatos legados. Os tipos e os tipos de renderização vêm dos conjuntos declarados acima; os desconhecidos são ignorados, para que uma fonte mais nova nunca quebre uma versão mais antiga do painel.

10. O contrato legível por máquina

A descrição formal e importável deste endpoint é o arquivo OpenAPI 3.1. Ele traz a mesma versão de contrato (2.1.0) e é o único artefato canônico para máquinas, compartilhado entre os idiomas.

↓ Baixar OpenAPI 3.1 (YAML) /custom-metrics-openapi.yaml