n8ncommunity nodetypescriptnpmautomacaointegracao

Como criar um community node do n8n do zero: do scaffold ao Creator Portal

Douglas Haruo 12 min 14/09/2026

Um community node do n8n, integração publicada por terceiros, é um pacote npm com três marcas de identificação. O nome começa com n8n-nodes-, a keyword é n8n-community-node-package, e um atributo n8n no package.json aponta para as classes compiladas de node e de credencial. Junto vão duas classes TypeScript que o n8n carrega em runtime. A doc oficial recomenda hoje este caminho (conferida em 25/08/2026): scaffold com o CLI n8n-node e teste local com n8n-node dev. Se a meta é a verificação, o passo seguinte é publicar no npm via GitHub Actions com provenance, o atestado assinado de quem construiu o pacote. A exigência vale para nodes verificados desde 01/05/2026.

Este guia percorre esse caminho inteiro usando um node real e público como mapa. É o n8n-nodes-tamperlens, que expõe a API do Tamperlens como três operações e está no npm desde 11/08/2026. As regras de verificação que mudam a arquitetura (zero dependências, multipart à mão, o scanner, a tarde que o OIDC custou) já têm post próprio. Aqui o assunto é o como: o que cada arquivo faz, o que cada bloco de código precisa conter, e em que ordem as coisas acontecem até a submissão.


O que tem dentro de um community node?

Tirando testes e CI, o pacote inteiro são sete arquivos de fonte. A árvore do node real:

n8n-nodes-tamperlens/
├── package.json                  ← o contrato com o n8n
├── credentials/
│   └── TamperlensApi.credentials.ts
└── nodes/Tamperlens/
    ├── Tamperlens.node.ts        ← a classe do node
    ├── GenericFunctions.ts       ← helpers puros, testáveis sem n8n
    ├── Tamperlens.node.json      ← o "codex": categorias e links de doc
    ├── tamperlens.svg            ← ícone (tema claro)
    └── tamperlens.dark.svg       ← ícone (tema escuro)

Quatro peças, quatro papéis:

1. O package.json é o contrato. É ele que faz de um pacote npm um community node, pelo atributo n8n:

{
  "name": "n8n-nodes-tamperlens",
  "keywords": ["n8n-community-node-package", "..."],
  "files": ["dist"],
  "n8n": {
    "n8nNodesApiVersion": 1,
    "credentials": ["dist/credentials/TamperlensApi.credentials.js"],
    "nodes": ["dist/nodes/Tamperlens/Tamperlens.node.js"]
  },
  "peerDependencies": { "n8n-workflow": "*" }
}

Repare que os caminhos apontam para dist/, o JavaScript compilado, não o TypeScript. Isso cria a primeira pegadinha prática do projeto: o tsc só emite .js, e os ícones SVG e o .node.json precisam estar em dist/ também. O script de build do node real resolve isso copiando os três arquivos estáticos depois do tsc. Se o seu esquecer, o pacote instala e o node aparece sem ícone, ou não aparece.

2. A classe do node (*.node.ts) implementa INodeType: um objeto description que declara a interface inteira (nome, ícone, operações, campos) e um método execute() que processa os itens. As duas seções seguintes abrem cada metade.

3. A classe da credencial (*.credentials.ts) implementa ICredentialType. Ela declara os campos que o usuário preenche, a regra de autenticação que o n8n aplica sozinho e um request de teste para o botão “Test”.

4. O codex (*.node.json) é metadado de catálogo: categorias e links para a documentação. É o que preenche o painel lateral quando o usuário encontra o node.

Declarativo ou programático: qual estilo usar?

O n8n tem dois estilos de node. A doc de escolha de estilo (conferida em 25/08/2026) recomenda o declarativo para a maioria dos casos: JSON descrevendo o roteamento das requisições, menos código, menos bug. Mas ela lista onde o programático é obrigatório: trigger nodes, APIs que não são REST, versionamento completo e “qualquer node que precise transformar os dados de entrada”. Esse último critério é o que decide para muita API de arquivo.

O node do Tamperlens é programático por essa razão. Ele não repassa JSON: monta o corpo da requisição a partir do binário do item, incluindo um multipart/form-data construído à mão para a operação de comparação. A história do porquê está no post da verificação. Se a sua API recebe JSON e devolve JSON, comece declarativo. Se ela recebe arquivos, você vai acabar no execute().

Como começar: o scaffold do CLI n8n-node

O jeito atual de começar do zero, pela doc do CLI (conferida em 25/08/2026):

npm create @n8n/node@latest
# ou, instalado global:
npm install --global @n8n/node-cli
n8n-node new

O scaffold, o gerador de estrutura inicial, pergunta o nome do projeto, o tipo de node (HTTP API, que é o declarativo, ou programático) e um template. Depois gera a estrutura completa. Dali em diante, dois comandos carregam o loop de desenvolvimento:

  • n8n-node dev compila o projeto e sobe um n8n local em localhost:5678 com o seu node já carregado. Você adiciona o node num workflow de verdade e testa contra a API real. É o substituto moderno do ritual de npm link para dentro do ~/.n8n/custom que os tutoriais antigos ensinam.
  • n8n-node lint (com --fix para o que for automático) roda as regras de qualidade que a verificação vai cobrar depois.

Transparência do caso real: o n8n-nodes-tamperlens nasceu antes de o CLI virar o caminho recomendado. O pacote foi montado à mão sobre a estrutura do n8n-nodes-starter. Hoje a doc de building pede que submissões novas comecem do scaffold do CLI. Não há razão para não obedecer: a estrutura gerada já sai no formato que a revisão espera, workflow de publicação incluído.

Como o node declara a interface?

A metade description da classe é declarativa mesmo num node programático. Quatro detalhes dela valem apontar, porque nenhum é óbvio no primeiro node:

export class Tamperlens implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Tamperlens',
    name: 'tamperlens',
    icon: { light: 'file:tamperlens.svg', dark: 'file:tamperlens.dark.svg' },
    group: ['transform'],
    version: 1,
    subtitle: '={{$parameter["operation"]}}',
    usableAsTool: true,
    inputs: [NodeConnectionTypes.Main],
    outputs: [NodeConnectionTypes.Main],
    credentials: [{ name: 'tamperlensApi', required: true }],
    properties: [ /* Operation + campos por operação */ ],
  };
  • O ícone é um par claro/escuro. icon aceita { light, dark }, e a verificação cobra as duas variantes. No caso real, a dark só troca a cor do traço do mesmo SVG.
  • subtitle é uma expressão. ={{$parameter["operation"]}} faz o node mostrar no canvas qual operação aquela instância executa, de graça, sem código.
  • usableAsTool: true é uma linha com consequência grande: marca o node como utilizável como ferramenta pelo AI Agent do n8n. Para uma API empacotada, é a diferença entre “existe num workflow” e “um agente pode decidir chamá-la”.
  • Campos aparecem por operação via displayOptions. Cada property pode declarar displayOptions: { show: { operation: ['inspect'] } }. O campo só é renderizado quando a operação selecionada bate. É assim que três operações dividem um formulário sem virarem três nodes.

E uma decisão de vocabulário que a revisão olha: cada opção de Operation carrega action (“Inspect a document for fraud signals”) além de name e description. O action é o texto que aparece na busca de nodes, escrito como verbo.

Como o execute() processa itens sem derrubar o workflow?

O esqueleto do execute() é um loop por item com um contrato de erro específico. Esse contrato é a parte que os tutoriais resumem e a revisão cobra por extenso:

async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
  const items = this.getInputData();
  const returnData: INodeExecutionData[] = [];

  for (let i = 0; i < items.length; i++) {
    try {
      // ... monta a requisição do item i e chama a API ...
      returnData.push({ json, pairedItem: { item: i } });
    } catch (error) {
      if (this.continueOnFail()) {
        returnData.push({
          json: { error: error instanceof Error ? error.message : String(error) },
          pairedItem: { item: i },
        });
        continue;
      }
      throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });
    }
  }
  return [returnData];
}

Quatro regras embutidas aí:

  1. try/catch por item, não por execução. Um lote de vinte documentos em que o décimo é corrompido não pode perder os outros dezenove.
  2. continueOnFail() decide o destino do erro. Ligado (é o usuário quem liga, na aba de settings do node), o erro vira um item de saída { json: { error } } e o workflow segue; desligado, a execução para com erro.
  3. Erro sempre embrulhado em NodeOperationError, com itemIndex. Nunca um throw error cru: o scanner da verificação, o validador automático de pacotes, reprova o throw cru sintaticamente, até quando ele parece defensável. O itemIndex é o que permite à UI apontar qual item falhou.
  4. pairedItem nos dois caminhos. Sucesso e erro carregam pairedItem: { item: i }. É o que deixa o n8n rastrear de qual item de entrada cada saída veio. Também é o que faz expressões como $('NóAnterior').item funcionarem rio abaixo.

Como a credencial injeta a chave sem o node vê-la?

A credencial é uma classe separada, e o desenho importa: o código do node nunca toca na API key. A classe declara os campos e uma regra genérica de autenticação:

export class TamperlensApi implements ICredentialType {
  name = 'tamperlensApi';
  properties = [
    { displayName: 'API Key', name: 'apiKey', type: 'string',
      typeOptions: { password: true }, required: true, default: '' },
    { displayName: 'Base URL', name: 'baseUrl', type: 'string',
      default: 'https://tamperlens.com/api/v1' },
  ];
  authenticate: IAuthenticateGeneric = {
    type: 'generic',
    properties: { headers: { Authorization: '=Bearer {{$credentials.apiKey}}' } },
  };
  test: ICredentialTestRequest = {
    request: { baseURL: '={{$credentials.baseUrl}}', url: '/receipt/verify',
      method: 'POST', body: { report: {}, receipt: {} } },
  };
}

Três decisões nesse bloco:

  • typeOptions: { password: true } faz o campo renderizar mascarado. A revisão espera isso em qualquer segredo.
  • authenticate é declarativo: a expressão =Bearer {{$credentials.apiKey}} diz ao n8n como montar o header. No node, a chamada é this.helpers.httpRequestWithAuthentication.call(this, 'tamperlensApi', options). O runtime injeta o header na hora da requisição, e a chave nunca passa pelo código que você escreveu. É também o que faz a credencial funcionar igual em qualquer operação futura.
  • O bloco test alimenta o botão “Test” da credencial. Qual endpoint apontar ali é uma decisão de produto com consequência de cobrança. O argumento completo (aponte para algo autenticado e não medido) está no post da verificação.

Como entra o arquivo? Binário do item, nunca caminho

As diretrizes de verificação (conferidas em 25/08/2026) são diretas: o código “não deve interagir com variáveis de ambiente nem tentar ler/escrever arquivos”. Tudo que o node precisa chega por parâmetro. Para um node que processa documentos, a consequência prática é o par:

const binary = this.helpers.assertBinaryData(i, binaryPropertyName);
const buffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);

assertBinaryData valida que o campo binário existe no item (com erro legível se não existir) e entrega os metadados: fileName, mimeType. getBinaryDataBuffer entrega os bytes. O mimeType do item vira o Content-Type da requisição, com fallback para application/octet-stream. O campo que o usuário configura no node não é um caminho de arquivo. É o nome do campo binário do item (data por padrão), que um node anterior já carregou: IMAP, webhook, HTTP Request.

O efeito colateral é o argumento de venda. Um node que só lê binário do item roda idêntico no n8n autogerenciado e no n8n Cloud. Lá, “caminho no disco” nem é um conceito que o usuário alcança.

Como publicar no npm do jeito que a verificação exige?

Desde 01/05/2026, a doc de building (conferida em 25/08/2026) exige que node verificado seja publicado via GitHub Actions com provenance statement. Publish da máquina local não atende. O scaffold novo do CLI já vem com o workflow de publicação pronto. Para pacote existente, a doc manda adotar o publish.yml do n8n-nodes-starter.

Provenance, no npm, é o atestado assinado de que o pacote foi construído daquele repositório público, por aquele workflow. É a resposta do ecossistema a pacote de automação virando vetor de ataque.

O workflow real do node, na variante token-less (npm Trusted Publishing via OIDC, sem token guardado no repositório):

on:
  push:
    tags: ["v*"]
permissions:
  contents: read
  id-token: write   # OIDC para o npm Trusted Publishing + provenance
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4   # sem registry-url — deliberado
        with:
          node-version: 22
      - run: npm install -g npm@11    # pinado: nem 10, nem 12.0.x
      - run: npm ci
      - run: npm test
      - run: npm publish --access public

O fluxo de release vira: versão no package.json, tag v0.1.2, push da tag. O CI builda, testa e publica. Dois cintos de segurança ficam fora do YAML. O primeiro é prepublishOnly: npm test no package.json, para nenhum publish sair sem a suíte verde, nem por engano. O segundo é o script de teste buildando antes de testar, para a suíte rodar sobre o dist/ que vai para o npm.

As três linhas comentadas no YAML acima (o registry-url ausente, o npm pinado em 11) são cicatrizes de uma tarde de debugging que tem seção própria no post da verificação. Copie o estado final e leia a história se algo der ENEEDAUTH.

Como funciona a verificação da n8n — e quanto tempo leva?

Com o pacote no npm, a submissão é feita no Creator Portal. O que as diretrizes e a página de submissão cobram (ambas conferidas em 25/08/2026):

  • Licença MIT e repositório público, com a URL do repositório no npm batendo com o GitHub.
  • Interface e documentação em inglês.
  • Um serviço por node, e não pode ser um serviço que o n8n já integra.
  • Zero dependências externas e nada de filesystem ou variáveis de ambiente: as duas regras que moldaram as seções anteriores.
  • Passar no scanner: npx @n8n/scan-community-package n8n-nodes-SEUPACOTE. Rode antes do primeiro publish: cada achado dele depois de publicado é uma versão nova no npm.
  • Publicação por GitHub Actions com provenance, da seção anterior.
  • E uma ressalva que vale ler antes de investir o esforço: a n8n se reserva o direito de recusar nodes que concorram com funcionalidades pagas da plataforma.

O que a verificação compra, nas palavras da doc: usuários “descobrem e instalam nodes verificados direto do painel de nodes do n8n”. Sem ela, o usuário precisa achar o pacote no npm e instalar pelo nome nas configurações.

Os prazos do caso real, para calibrar expectativa: o node foi ao npm em 11/08/2026, em duas versões no mesmo dia, a segunda corrigindo os achados do scanner. A submissão ao Creator Portal foi em 14/08/2026, e a confirmação indicou janela de revisão de até quatro semanas. A doc pública não fixa prazo nenhum (conferida em 25/08/2026). Este post foi escrito com a revisão ainda aberta: sem resultado para relatar, e nenhum a prometer. A regra da casa enquanto isso: repositório congelado até a revisão terminar.

O checklist do zero ao submetido

  1. Scaffold: npm create @n8n/node@latest; escolha declarativo se a API é JSON puro, programático se há arquivo ou transformação.
  2. Contrato: nome n8n-nodes-*, keyword n8n-community-node-package, atributo n8n apontando para dist/, build copiando SVGs e codex para dist/.
  3. Interface: ícone claro/escuro, subtitle por expressão, action como verbo em cada operação, campos por operação via displayOptions, usableAsTool se fizer sentido como ferramenta de agente.
  4. execute(): loop por item, try/catch por item, continueOnFail() respeitado, NodeOperationError com itemIndex, pairedItem nos dois caminhos.
  5. Credencial: segredo com password: true, authenticate declarativo, test apontando para endpoint autenticado e barato. O node nunca toca na chave.
  6. Arquivo = binário do item: assertBinaryData + getBinaryDataBuffer; nenhum fs, nenhum process.env.
  7. Teste local: n8n-node dev, workflow de verdade, API de verdade; n8n-node lint e o scanner antes do primeiro publish.
  8. Publicação e submissão: tag → GitHub Actions → npm com provenance; depois creators.n8n.io/nodes. O repo congela até a resposta.

Empacotar uma API para dentro de um ecossistema de automação é um padrão que se repete. A mesma decisão já apareceu aqui na versão MCP. E o n8n que roda o atendimento da casa é o mesmo para o qual este node existe. O custo de fazer do jeito verificável é quase todo pago no primeiro dia. A partir daí, um node de zero dependências é o tipo de software que não acorda ningué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 →