Skip to content
Guides

n8n Custom Node Lint Errors: What Each One Means and How to Fix It

In this video

You run npm run lint on your first n8n node and get a wall of red. It looks like everything is wrong. Usually it's a handful of messages, repeated across files, and each one tells you exactly what it wants once you know what it's protecting.

This guide takes the messages a first node runs into, one at a time: the exact text, what it means, and the fix. The messages are from n8n's community-node lint rules as of October 2026; wording can change between versions.

Where these errors come from

A node made with npm create @n8n/node has a lint script that runs n8n-node lint:

npm run lint
npm run lint:fix   # fixes what it can

n8n-node lint applies n8n's own rule set for community nodes, @n8n/eslint-plugin-community-nodes. Fixing its errors early saves you from meeting the same problems when you submit the node for verification.

"Use of restricted global 'setTimeout' is not allowed"

Your node runs inside someone else's n8n instance, so some Node.js globals are off limits:

setTimeout, setInterval, setImmediate and their clear versions, process, global, globalThis, __dirname, __filename.

For a wait, such as a pause before a retry, use n8n's helper:

import { sleep } from 'n8n-workflow';

await sleep(1000);

If you were reading an API key or a base URL from process.env, move it into a credential. The user enters it there, and n8n stores it encrypted.

"Import of 'axios' is not allowed"

The full message: Import of 'axios' is not allowed. n8n Cloud does not allow community nodes with dependencies. A second rule reports a dependencies block in package.json: it must be empty or absent.

Use the request helper n8n gives every node instead:

const contact = await this.helpers.httpRequestWithAuthentication.call(
  this,
  'acmeCrmApi',
  { method: 'GET', url: `${baseUrl}/contacts/${id}`, json: true },
);

n8n-workflow belongs in peerDependencies, not dependencies; n8n provides it at run time.

"Avoid calling this.helpers.httpRequest() …"

The full message asks you to use this.helpers.httpRequestWithAuthentication() instead of fetching the credential with this.getCredentials() and building the request yourself.

This version works, but it's your own login code:

const creds = await this.getCredentials('acmeCrmApi');
await this.helpers.httpRequest({
  url: `${baseUrl}/contacts`,
  headers: { Authorization: `Bearer ${creds.apiKey}` },
});

The authenticated helper reads the credential and applies it the way the credential type's authenticate block says to. As n8n improves things like token refresh, nodes that use the helper get those improvements; hand-built requests don't.

"Credential class … must have a test property"

Every credential needs a test: the request n8n sends when the user clicks Save, so a wrong key shows up immediately instead of in a failed run.

test: ICredentialTestRequest = {
  request: {
    baseURL: 'https://api.acme.example',
    url: '/me',
  },
};

Point it at the cheapest authenticated endpoint you have. Alternatively, a node can test the credential with testedBy.

A related error: Credential "acmeCrmAPI" does not exist in this package … Did you mean 'acmeCrmApi'? The credential name in your node must match the credential class's name exactly, capitals included, and the credential must be listed in package.json. Read to the end of the message; the lint suggests the name you meant.

The execute() rules

If your node has an execute() method (a programmatic node), three rules come together:

  • execute() method must handle this.continueOnFail()
  • Use NodeApiError or NodeOperationError instead of re-throwing raw errors
  • new NodeOperationError(...) inside an item loop must include { itemIndex }

One catch block satisfies all three:

for (let i = 0; i < items.length; i++) {
  try {
    // ... the request for item i
  } catch (error) {
    if (this.continueOnFail()) {
      returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
      continue;
    }
    throw new NodeApiError(this.getNode(), error as JsonObject, { itemIndex: i });
  }
}
  • continueOnFail. Users can set a node to continue when an item fails. Your code puts the error on that item instead of stopping the run.
  • n8n's error classes. A raw error loses the details n8n shows in the error panel.
  • itemIndex. Tells the user which of five hundred items failed.

The pairedItem on the error item matters too. Without it, a lint rule reports Missing pairedItem on INodeExecutionData object, and expressions in later nodes can fail. The paired item guide covers that error.

Declarative nodes have no execute() method, so none of these three rules apply to them.

"Authorization failed - please check your credentials"

This is the one your users see. When an API returns an error, NodeApiError replaces the status code with a standard sentence:

Status n8n shows
400 Bad request - please check your parameters
401 Authorization failed - please check your credentials
403 Forbidden - perhaps check your credentials?
404 The resource you are requesting could not be found
429 The service is receiving too many requests from you
500 The service was not able to process your request

The same sentence appears for every API. The useful part is the API's own message, which n8n looks for in the error response and shows as the details under that title. So:

  • If you own the API, return a clear message field in your error bodies: "API key has expired", not just a status code.
  • If the error shape is unusual, pass your own text when you throw:
throw new NodeApiError(this.getNode(), error as JsonObject, {
  itemIndex: i,
  message: 'Acme rejected the API key',
  description: 'Create a new key in Acme under Settings → API, then update the credential.',
});

Recap

  • sleep from n8n-workflow, not setTimeout; credentials, not process.env
  • no dependencies; n8n's request helpers instead of axios
  • httpRequestWithAuthentication, not a hand-built Authorization header
  • a credential test, and credential names that match exactly
  • continueOnFail, NodeApiError and itemIndex in every execute() catch block
  • error messages from your API that a person can act on

We build NativeShip, which generates n8n nodes from an OpenAPI spec, and its build runs this same n8n-node lint as one of five checks: dependencies, compile, lint, and a check on the requests. On our test build all five passed, and the package can't be downloaded or published until they do. If you'd rather start from a node that already passes, that's what it's for.

Frequently asked questions

Community nodes run inside someone else's n8n, so some Node.js globals are off limits: timers, process, globalThis, __dirname and __filename. For a wait, import sleep from n8n-workflow. Move values you read from process.env into a credential.

No. The lint reports "Import of '…' is not allowed. n8n Cloud does not allow community nodes with dependencies", and the dependencies field in package.json must be empty or absent. Use n8n's request helpers, such as this.helpers.httpRequestWithAuthentication, instead of a library like axios.

Each credential needs a test request, so n8n can check the key when the user saves it. Add a test property with a cheap request, such as a "who am I" endpoint, or have a node test it with testedBy.

That is n8n's standard message for an HTTP 401 from the API. NodeApiError replaces the status code with a standard sentence and puts the API's own error message in the details, if the response has one. Return a clear message field in your API's error bodies, or pass message and description when you throw.

Ready to launch your n8n, Make, Zapier or Activepieces integration?

Give your customers another way to use your product.

Get started