Um n8n community node verificado: as regras que o docs não enfatiza
Empacotar uma API como n8n community node, um plugin da comunidade publicado no npm, é um projeto de um dia. A superfície é pequena: uma classe de node, uma credential (onde o usuário cola a chave da API), um SVG. O que não é um projeto de um dia é fazer isso do jeito que a verificação da n8n exige. As exigências mudam a arquitetura do pacote, e três delas quase não aparecem nos tutoriais.
Esta é a nota de campo do n8n-nodes-tamperlens, o node que expõe a API do Tamperlens como três operações (Inspect, Metadata, Compare). Ele foi ao npm em 2026-08-11, em duas versões no mesmo dia (já explico por quê), e foi submetido ao Creator Portal três dias depois. O que segue são as regras que valem para qualquer API que você queira empacotar. E também a tarde que o trusted publishing do npm custou, com os erros na ordem em que aconteceram.
As três regras que mudam o design
Um node verificado é o que passou pela revisão oficial da n8n. Desde 2026-05-01, ele precisa cumprir três condições que não são cosméticas:
- Publicação via GitHub Actions com provenance, a prova de origem do pacote. Publish de máquina local é rejeitado. Isso não é uma preferência de processo: é o que garante que o pacote no npm foi construído do repositório público, por um workflow auditável.
- O node não pode tocar arquivos nem variáveis de ambiente. Nada de
fs.readFilenum caminho que o usuário digitou. A consequência prática: a entrada é dado binário do item, nunca um caminho de arquivo. - Passar no
npx @n8n/scan-community-package. É um scanner estático que reprova padrões específicos, e vale rodar antes de publicar, não depois.
Há uma quarta regra que o ecossistema assume sem escrever em negrito: zero dependências de runtime. O package.json do node tem devDependencies e um peerDependencies: { "n8n-workflow": "*" }, e nenhum dependencies. Isso reverbera pelo código inteiro, como fica claro já na primeira operação que precisa de upload.
Zero deps na prática: multipart à mão
Duas das operações (inspect, metadata) mandam o buffer cru como body. O Content-Type vem do mimeType do binário, com fallback para application/octet-stream. Simples.
A terceira (compare) manda dois arquivos, e dois arquivos pedem multipart/form-data, o formato que empacota vários arquivos num envio só. O reflexo é instalar o pacote form-data. Com zero deps, não pode. Então o multipart é montado à mão, em cerca de trinta linhas: um buildMultipart() que concatena boundary, headers de parte e buffers.
Montar multipart à mão tem duas armadilhas conhecidas, e as duas viraram teste:
- Boundary previsível. O boundary é gerado com 24 caracteres base-36, e o gerador aceita um RNG injetável, um gerador de números aleatórios trocável. Não é por segurança: é para o teste poder fixá-lo e afirmar o corpo byte a byte.
- Filename como vetor de header injection. O nome do arquivo vem do item do workflow, ou seja, de fora. Um
escapeQuotes()trata aspas e contrabarras, e o teste correspondente afirma que um filename malicioso não escapa do header da parte.
O detalhe de arquitetura que fez esses testes serem baratos: os helpers (buildMultipart, joinUrl, escapeQuotes) moram num módulo sem nenhum tipo do n8n. Testam com node --test puro, sem harness, sem mock do runtime do n8n. A classe do node fica fina. A lógica testável fica fora dela.
Binário em vez de caminho: a regra que protege o usuário
A regra “não toque em arquivos” parece burocracia. Pense no que um node malicioso faria com um campo de caminho de arquivo: ler ~/.ssh/id_rsa e mandar para um endpoint. Por isso a entrada é assertBinaryData + getBinaryDataBuffer: o node só enxerga o que o workflow já carregou como binário do item.
Para quem empacota uma API, isso define o contrato: seu node recebe buffer e seu endpoint recebe buffer. Qualquer história de “aponte para o arquivo no disco” morre no design. O efeito colateral é bom: o mesmo node funciona igual no n8n Cloud, onde disco nem existe do jeito que o usuário imagina.
O credential test que não custa nada
Toda credential do n8n pode declarar um endpoint de teste: o botãozinho “Test” que o usuário aperta depois de colar a API key. A escolha ingênua é apontar para a operação principal. Só que se a API é medida por uso, cada teste de credencial queima quota.
A solução foi apontar o teste para um endpoint autenticado mas não medido. No caso, o de verificação de recibo: com chave errada devolve 401, com chave válida devolve 200 sem consumir nada da franquia. Se a sua API não tem um endpoint assim, vale criar um GET /auth/check só para isso. O teste de credencial é a primeira interação do usuário com a sua API, e “testar me cobrou” é uma péssima primeira interação.
O scanner reprova coisas que parecem certas
O @n8n/scan-community-package rodou sobre a 0.1.1 e apontou três coisas. Daí a 0.1.2 no mesmo dia:
- Credential sem
icon. A credential precisa da própria referência de ícone, com variantes light/dark. - Ícone do node sem variante dark. O SVG precisa do par
tamperlens.svg/tamperlens.dark.svg. No caso, a variante troca só a cor do traço. throw errorcru dentro docatch. O código tinha um rethrow condicional (if (error instanceof NodeOperationError) throw error;) antes de embrulhar o resto emNodeOperationError. O scanner conta o rethrow como “raw throw” e reprova. A correção foi embrulhar sempre, comitemIndexno contexto.
O terceiro item é o instrutivo: o padrão reprovado é defensável em código de aplicação. Num node verificado, a regra é sintática e não-negociável. Então rode o scanner antes do primeiro publish, porque cada rodada dele depois de publicado é uma versão nova no npm.
O tratamento de erro que sobrou é o idiomático do n8n e vale copiar: try/catch por item. Com continueOnFail() ligado, o erro vira { json: { error } } e o workflow segue. Desligado, vira NodeOperationError com o índice do item. Nos dois caminhos a saída carrega pairedItem, para o n8n saber de qual item de entrada cada saída veio.
A tarde do OIDC: quatro erros até o token-less funcionar
A parte que mais rende por hora de sofrimento. Publicar com provenance via trusted publishing (sem token no repositório) é o caminho certo. O caminho até ele, em 2026-08-11, levou cinco commits em quarenta minutos:
- npm 10 não faz a troca OIDC, que autentica o CI sem token. O Node 22 do
setup-nodetraz npm 10, e essa versão não implementa a troca token-less. O sintoma não é uma mensagem sobre OIDC: é o registry devolvendo 404 no PUT não autenticado. registry-urlnosetup-nodesabota o OIDC. Comregistry-urlconfigurado, osetup-nodeescreve um.npmrccom o placeholder${NODE_AUTH_TOKEN}. O npm apresenta esse placeholder como se fosse um token real e preempta a troca OIDC (issueactions/setup-node#1551). A correção é contra-intuitiva: remover oregistry-url.--provenanceexplícito quebra o trusted publishing. Com trusted publishing, a provenance é automática. Passar a flag na mão roteia o publish pelo caminho de assinatura por token, que não existe, e dáENEEDAUTH.- npm 12.0.x regrediu a troca. Mesmo com tudo certo, o npm 12.0.x da época devolvia
ENEEDAUTHcom configuração válida.
O estado final, que funciona e está pinado no workflow: setup-node sem registry-url, npm install -g npm@11 (nem 10, nem 12), npm publish --access public sem flag e sem NODE_AUTH_TOKEN. No job, só permissions: id-token: write. E dois cintos de segurança fora do YAML: prepublishOnly: npm test no package.json, e o script de teste que builda antes de testar. Assim o publish nunca sai sem a suíte verde sobre o dist/ real.
Se você for montar o mesmo pipeline: copie o estado final, mas guarde a lista de sintomas. 404 no PUT, ENEEDAUTH e “funciona local, falha no CI” são três disfarces do mesmo problema, e nenhum deles menciona OIDC no texto do erro.
Canal de distribuição? Medindo antes de afirmar
A tese que justificou o node era “marketplace como canal de distribuição”. A leitura honesta, registrada nos docs internos antes deste post: o ecossistema tinha 5.834 community nodes indexados em janeiro de 2026 e cerca de 25 verificados. Um node não verificado é uma agulha num palheiro sem busca boa. A verificação é o que muda a prateleira.
Por isso a conclusão operacional foi outra: o node é higiene, não canal. Ele precisa existir para o produto ser levado a sério por quem vive dentro do n8n. O custo de mantê-lo é quase zero (zero deps ajuda de novo: não há dependabot acordando o repo toda semana). Mas a aposta de distribuição de verdade é a verificação, submetida ao Creator Portal em 2026-08-14, com janela de revisão de até quatro semanas. Junto veio uma regra auto-imposta que vale adotar: não tocar no repositório enquanto a revisão está aberta, porque cada versão nova reinicia a fila.
O checklist, para a sua API
Se você for empacotar uma API como community node com pretensão de verificação:
- Zero
dependencies. Multipart à mão se precisar; helpers puros num módulo sem tipos do n8n, testados comnode --test. - Binário, nunca caminho de arquivo.
assertBinaryData+getBinaryDataBuffer; o mimeType do item vira oContent-Type. - Credential test em endpoint autenticado e não-medido. Se não existe, crie um.
- Rode
npx @n8n/scan-community-packageantes do primeiro publish. Ícones com variante dark (node e credential), nenhum throw cru, erro comitemIndexepairedItemnos dois caminhos. - Trusted publishing token-less: sem
registry-url, npm pinado numa versão que faz a troca OIDC, sem--provenancemanual,id-token: write,prepublishOnlyrodando a suíte. - Trate o node como higiene. A verificação é a aposta de distribuição, e o node em si é a condição de entrada. Congele o repo enquanto a revisão estiver aberta.
Nada disso é difícil na segunda vez. O objetivo deste post é ser a segunda vez de alguém.
Precisa de um projeto técnico sob medida?
Arquitetura, TypeScript, APIs e automação, do protótipo à produção. Quem responde o seu e-mail é quem escreve o código, e o prazo que eu prometo é o prazo que eu consigo cumprir.
Me manda uma mensagem →