---
title: "Dynamic Filter Payload Contract"
slug: "dynamic-filter-payload-contract"
description: "Contrato operacional do payload do filtro dinamico, cobrindo eventos do PraxisFilter, envio ao backend e normalizacao aplicada pelo metadata-starter."
doc_type: "reference"
document_kind: "host-guide"
component: "table"
category: "integration"
audience:
  - "host"
  - "frontend"
  - "backend"
  - "architect"
level: "advanced"
status: "active"
owner: "praxis-ui"
tags:
  - "dynamic-filter"
  - "payload"
  - "GenericFilterDTO"
  - "metadata-starter"
  - "http"
order: 27
icon: "send"
toc: true
sidebar: true
search_boost: 1.2
reading_time: 18
estimated_setup_time: 40
version: "1.0"
related_docs:
  - "dynamic-filter-architecture-overview"
  - "dynamic-filter-range-filters-guide"
  - "dynamic-fields-inline-components-guide"
  - "dynamic-fields-inline-filter-runtime-contract"
  - "table-overview"
keywords:
  - "submit"
  - "change"
  - "GenericFilterDTO"
  - "POST /filter"
source_of_truth:
  - "projects/praxis-table/src/lib/components/praxis-filter/praxis-filter.component.ts"
  - "projects/praxis-table/src/lib/components/praxis-filter/praxis-filter.component.spec.ts"
  - "../praxis-metadata-starter/src/main/java/org/praxisplatform/uischema/controller/base/AbstractResourceQueryController.java"
  - "../praxis-metadata-starter/src/main/java/org/praxisplatform/uischema/filter/web/FilterRequestBodyAdvice.java"
  - "../praxis-metadata-starter/docs/examples/filter-dto.md"
  - "../praxis-metadata-starter/docs/guides/FILTROS-E-PAGINACAO.md"
last_updated: "2026-03-07"
---

# Dynamic Filter Payload Contract

## Objetivo

Formalizar como o payload do filtro dinâmico nasce na UI, como deve ser enviado pelo host e como o `praxis-metadata-starter` o trata antes de construir a consulta.

Nota de trilha:

- `table` documenta o payload da feature
- `dynamic-fields` documenta o shape que cada inline coloca no formulario antes do envio

## Pré-requisitos

- Conhecimento básico do `PraxisFilter` e dos endpoints resource-oriented de filtro.
- DTO de filtro backend implementando `GenericFilterDTO`.
- Entendimento mínimo de `controlType`, `@Filterable` e operações de range.

## Quando usar

Use este documento para:

- implementar integração host -> backend sem adivinhação;
- revisar shape do payload emitido por `change` e `submit`;
- alinhar frontend e backend sobre o que é aceito como filtro válido;
- depurar `400 FILTER_PAYLOAD_INVALID`.

## Contrato funcional

O runtime do filtro expõe dois eventos principais:

- `change`: mudanças incrementais do formulário;
- `submit`: snapshot que normalmente deve ser enviado ao backend.

Além disso, existem eventos auxiliares como `clear`, `tagsChange`, `selectedFieldIdsChange`, `metaChanged` e `schemaStatusChange`, mas eles não substituem o payload principal do filtro.

O fluxo oficial do payload é o seguinte.

```mermaid
sequenceDiagram
  participant UI as PraxisFilter
  participant Host
  participant Controller as AbstractResourceQueryController
  participant Advice as FilterRequestBodyAdvice
  participant Normalizer as RangePayloadNormalizer
  participant DTO as GenericFilterDTO
  participant Builder as GenericSpecificationsBuilder

  UI->>Host: submit or change payload
  Host->>Controller: POST /filter
  Controller->>Advice: intercept request body
  Advice->>Normalizer: normalize ranges and aliases
  Normalizer->>DTO: canonical payload
  DTO->>Builder: typed filter ready for specification
  Builder-->>Controller: query/specification pronta ou erro
  Controller-->>Host: result payload or 400 FILTER_PAYLOAD_INVALID
```

## O que o host realmente envia

O host deve serializar o DTO produzido pela UI como JSON e enviá-lo para o controller correspondente.

Superfícies padrão do starter:

- `POST /filter`
- `POST /filter/cursor`
- `POST /locate`
- `POST /options/filter`

O shape exato depende do seu `FilterDTO`, mas o princípio é o mesmo: cada propriedade enviada deve corresponder a um campo filtrável do backend.

## Pipeline do payload

### 1. Runtime Angular

O `PraxisFilter` agrega o estado do formulário e emite objetos do tipo `Record<string, any>`.

Aspectos relevantes do runtime:

- ranges vazios não devem sobreviver à serialização;
- estado avançado e preferências visuais podem ser persistidos localmente;
- aliases inline e metadata overrides influenciam a UI, mas não devem gerar um contrato HTTP arbitrário.

### 2. Envio HTTP

O host é responsável por:

- observar `submit` ou `change`;
- aplicar debounce/estratégia de busca adequada;
- enviar o JSON ao endpoint correto;
- não inventar transformações fora do contrato do backend.

### 3. Interceptação no backend

No `praxis-metadata-starter`, o request body passa por `FilterRequestBodyAdvice` antes da desserialização final.

Esse ponto é central para a doc:

- o payload não é consumido “como veio”;
- ranges podem ser canonicalizados;
- payload inválido já pode falhar nessa etapa.

### 4. Normalização de ranges

`RangePayloadNormalizer` converte payloads canônicos para o formato de lista esperado pelos DTOs tipados.

Isso inclui:

- arrays canônicos;
- objetos canônicos;
- nomes de bound reconhecidos pela plataforma para domínios como data, numérico e monetário.

### 5. Construção da consulta

Depois da normalização, `GenericSpecificationsBuilder` monta a predicate com base em `@Filterable`.

Para o frontend, a consequência prática é simples:

- a UI pode ser rica;
- o contrato final precisa continuar mapeável para `GenericFilterDTO`.

## Exemplo de payload enviado pela UI

```json
{
  "name": "Alice",
  "departmentId": 10,
  "salaryRange": {
    "minPrice": 6500,
    "maxPrice": 15000,
    "currency": "BRL"
  },
  "admissionPeriod": {
    "startDate": "2026-03-01",
    "endDate": "2026-03-31"
  },
  "status": [
    "ACTIVE",
    "PENDING"
  ]
}
```

## Entity Lookup em coleções

Quando um filtro usa `entityLookup` com `multiple=true`, o `PraxisFilter` não deve persistir nem emitir a coleção rica do runtime como contrato HTTP final. A tabela agora reaproveita o helper canônico de `@praxisui/core` e respeita o mesmo `payloadMode` já usado pelo `dynamic-form`.

Regras:

- `multiple=true` sem `payloadMode` explícito envia `ids`
- `multiple=true` com `payloadMode: "entityRefs"` envia `[{ id, type }]`
- o `entityType` padrão vem de `optionSource.entityKey` quando a linha ainda não trouxer `type`

Exemplo com `ids`:

```json
{
  "supplierIds": ["sup_1", "sup_2"]
}
```

Exemplo com `entityRefs`:

```json
{
  "suppliers": [
    { "id": "sup_1", "type": "supplier" },
    { "id": "sup_2", "type": "legacy-supplier" }
  ]
}
```

Diretriz:

- hosts não devem criar um serializer paralelo para filtros de entidade em tabela;
- `PraxisFilter` já deve emitir o payload canônico pronto para envio ao backend.

## Como o starter trata esse payload

Para campos simples:

- valores seguem diretamente para o `FilterDTO`.

Para ranges:

- o backend pode converter o objeto em lista canônica;
- chaves auxiliares consumidas pelo normalizador são removidas do payload final desserializado;
- listas com limites inválidos ou conflitantes retornam erro.

Exemplo de canonicalização:

```json
{
  "salaryRange": [6500, 15000],
  "admissionPeriod": ["2026-03-01", "2026-03-31"]
}
```

## Regras práticas para o frontend

1. Envie apenas propriedades semanticamente preenchidas.
   Não envie objetos vazios, listas com três posições ou placeholders neutros.

2. Prefira objeto canônico ou lista canônica para ranges.
   Não envie payload escalar para operações Java de range.

3. Não serialize metadata de UI junto com o DTO.
   `label`, `tooltip`, `clearButton` e `inlineAutoSize` são contrato de renderização, não de busca.

4. Use nomes canônicos nos contratos novos.
   Para numérico genérico, prefira `{ min, max }`; para monetário, `{ minPrice, maxPrice }`; para datas, `{ startDate, endDate }`.

5. Considere `change` e `submit` como contrato de integração, não como detalhe interno.

## Query Context na Orquestração de Página

Quando o filtro dinâmico participar de um `praxis-dynamic-page`, o contrato recomendado entre widgets não é mais gravar diretamente `filterCriteria` em cada consumidor.

O caminho canônico passa a ser:

- `praxis-filter` emite `requestSearch` com o DTO filtrado
- a página escreve esse payload em `page.state`
- a página ou o builder propagam um `queryContext` para widgets de dados

Para narrativas, badges e surfaces, use o output separado `contextChange`. Ele
publica `PraxisFilterViewContext`, com o DTO canônico em `filter` e as projeções
`labels`, `fieldLabels` e `fields`. Os rótulos são resolvidos a partir da mesma
metadata/opção usada pelos campos; não crie mapas paralelos no host.

```json
{
  "schemaVersion": "praxis.filter-view-context.v1",
  "filter": { "departmentId": 10, "status": "ACTIVE" },
  "labels": { "departmentId": "Engenharia", "status": "Ativo" },
  "fieldLabels": { "departmentId": "Departamento", "status": "Status" },
  "fields": []
}
```

`filter` é a única parte executável como consulta. O objeto enriquecido completo é
um `view-context` e não deve ser enviado a `/filter`, stats ou option sources.

Exemplo recomendado:

```json
{
  "queryContext": {
    "filters": {
      "departmentId": 10,
      "status": "ACTIVE"
    },
    "sort": ["nome,asc"],
    "page": {
      "index": 0,
      "size": 25
    }
  }
}
```

Diretriz de compatibilidade:

- `filterCriteria` continua válido em componentes que ainda publicam esse contrato
- para novo authoring, recipes e bindings, prefira `queryContext`
- a semântica mínima garantida hoje é `queryContext.filters`; `sort`, `limit` e `page` devem ser consumidos apenas por widgets que já declararam suporte a esses campos
- `queryContext.filters` continua sendo um DTO plano com semântica de conjunção simples; filtros compostos com OR ou grupos aninhados devem evoluir pelo contrato canônico proposto em `projects/praxis-core/docs/rfc-query-context-filter-expression.md`, não por aliases ou payloads ad hoc do frontend

## Payloads que devem ser evitados

Exemplos inválidos ou indesejáveis:

```json
{ "salaryRange": 1500 }
```

```json
{ "salaryRange": [10, 20, 30] }
```

```json
{ "admissionPeriod": { "startDate": null, "endDate": null } }
```

```json
{ "salaryRange": { "from": 10, "minPrice": 20 } }
```

O último caso é especialmente perigoso porque pode configurar fonte conflitante para o mesmo bound.

## Como documentar esta feature daqui para frente

Toda documentação de filtro dinâmico precisa citar explicitamente:

- qual evento gera o payload;
- qual endpoint o recebe;
- como o `FilterRequestBodyAdvice` o intercepta;
- como `RangePayloadNormalizer` o canonicaliza;
- quais falhas devolvem `400`.

Sem isso, a doc tende a ficar “bonita na UI” e incompleta na integração real.
