
N8n:Create Community Node Lint Rule
- 168 installs
- 199k repo stars
- Updated August 5, 2026
- n8n-io/n8n
Author custom ESLint rules that enforce n8n community node conventions, catching packaging, credential, and API surface mistakes before publish.
About
The n8n:create-community-node-lint-rule skill helps maintainers and contributors add ESLint rules tailored to n8n community node development. It standardizes how nodes declare credentials, operations, and packaging so integration code stays consistent, testable, and ready for community distribution.
- Scaffolds lint rules for n8n community node packages
- Encodes node naming, credential, and export conventions
- Catches common integration mistakes early in CI
- Aligns contributors to upstream n8n node standards
- Reduces review churn on community node pull requests
N8n:Create Community Node Lint Rule by the numbers
- 168 all-time installs (skills.sh)
- +6 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #625 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/n8n-io/n8n --skill n8ncreate-community-node-lint-ruleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 168 |
|---|---|
| repo stars | ★ 199k |
| Last updated | August 5, 2026 |
| Repository | n8n-io/n8n ↗ |
What it does
Author custom ESLint rules that enforce n8n community node conventions, catching packaging, credential, and API surface mistakes before publish.
Files
Create Community Node Lint Rule
Guide for adding new ESLint rules to packages/@n8n/eslint-plugin-community-nodes/.
All paths below are relative to packages/@n8n/eslint-plugin-community-nodes/.
Step 1: Understand the Rule
Before writing code, clarify:
- What does the rule detect? (missing property, wrong pattern, bad value)
- Where does it apply? (
.node.tsfiles, credential classes, both) - Severity:
error(must fix) orwarn(should fix)? - Fixable? Can it be auto-fixed safely, or only suggest?
- Scope: Both
recommendedconfigs, or exclude fromrecommendedWithoutN8nCloudSupport?
Step 2: Implement the Rule
Create src/rules/<rule-name>.ts:
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import {
isNodeTypeClass, // or isCredentialTypeClass
findClassProperty,
findObjectProperty,
createRule,
} from '../utils/index.js';
export const YourRuleNameRule = createRule({
name: 'rule-name',
meta: {
type: 'problem', // or 'suggestion'
docs: {
description: 'One-line description of what the rule enforces',
},
messages: {
messageId: 'Human-readable message. Use {{placeholder}} for dynamic data.',
},
fixable: 'code', // omit if not auto-fixable
hasSuggestions: true, // omit if no suggestions
schema: [], // add options schema if configurable
},
defaultOptions: [],
create(context) {
return {
ClassDeclaration(node) {
if (!isNodeTypeClass(node)) return;
const descriptionProperty = findClassProperty(node, 'description');
if (!descriptionProperty) return;
const descriptionValue = descriptionProperty.value;
if (descriptionValue?.type !== AST_NODE_TYPES.ObjectExpression) return;
// Rule logic here — use findObjectProperty(), getLiteralValue(), etc.
context.report({
node: targetNode,
messageId: 'messageId',
data: { /* template vars */ },
fix(fixer) {
return fixer.replaceText(targetNode, 'replacement');
},
});
},
};
},
});Naming: Export as PascalCaseRule (e.g. MissingPairedItemRule). The name field is kebab-case.
Available AST helpers — see reference.md for the full catalog of ast-utils and file-utils exports.
Step 3: Write Tests
Create src/rules/<rule-name>.test.ts:
import { RuleTester } from '@typescript-eslint/rule-tester';
import { YourRuleNameRule } from './rule-name.js';
const ruleTester = new RuleTester();
// Helper to generate test code — keeps test cases readable
function createNodeCode(/* parameterize the varying parts */): string {
return `
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
export class TestNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'Test Node',
name: 'testNode',
group: ['input'],
version: 1,
description: 'A test node',
defaults: { name: 'Test Node' },
inputs: [],
outputs: [],
properties: [],
};
}`;
}
ruleTester.run('rule-name', YourRuleNameRule, {
valid: [
{ name: 'class that does not implement INodeType', code: '...' },
{ name: 'node with correct pattern', code: createNodeCode(/* correct */) },
],
invalid: [
{
name: 'descriptive case name',
code: createNodeCode(/* incorrect */),
errors: [{ messageId: 'messageId', data: { /* expected template vars */ } }],
output: createNodeCode(/* expected after fix */), // or `output: null` if no fix
},
],
});Test guidelines:
- Always test that non-INodeType classes are skipped (valid case)
- Test both the error message and the fixed output for fixable rules
- For rules with options, test each option combination
- For rules using filesystem, mock with
vi.mock('../utils/file-utils.js') - For suggestion-only rules, use
errors: [{ messageId, suggestions: [...] }]
Step 4: Register the Rule
4a. Add to src/rules/index.ts
import { YourRuleNameRule } from './rule-name.js';
// Add to the rules object:
export const rules = {
// ... existing rules
'rule-name': YourRuleNameRule,
} satisfies Record<string, AnyRuleModule>;4b. Add to src/plugin.ts configs
Add to both config objects (unless the rule depends on n8n cloud features):
'@n8n/community-nodes/rule-name': 'error', // or 'warn'- Use
errorfor rules that catch bugs or required patterns - Use
warnfor style/convention rules (likeoptions-sorted-alphabetically) - If the rule uses
no-restricted-globalsorno-restricted-importspatterns,
only add to recommended (not recommendedWithoutN8nCloudSupport)
Step 5: Write Documentation
Create docs/rules/<rule-name>.md:
# Description of what the rule does (`@n8n/community-nodes/rule-name`)
<!-- end auto-generated rule header -->
## Rule Details
Explain why this rule exists and what problem it prevents.
## Examples
### Incorrect
\`\`\`typescript
// code that triggers the rule
\`\`\`
### Correct
\`\`\`typescript
// code that passes the rule
\`\`\`The header above <!-- end auto-generated rule header --> will be regenerated by pnpm build:docs. Write a reasonable first version — it gets overwritten.
Step 6: Verify
Run from packages/@n8n/eslint-plugin-community-nodes/:
pushd packages/@n8n/eslint-plugin-community-nodes
pnpm test <rule-name>.test.ts # tests pass
pnpm typecheck # types are clean
pnpm build # compiles
pnpm build:docs # regenerates doc headers and README table
pnpm lint:docs # docs match schema
popdChecklist
- [ ] Rule file:
src/rules/<rule-name>.ts - [ ] Test file:
src/rules/<rule-name>.test.ts - [ ] Registered in
src/rules/index.ts - [ ] Added to configs in
src/plugin.ts - [ ] Doc file:
docs/rules/<rule-name>.md - [ ] README table updated via
pnpm build:docs - [ ] All verification commands pass
AST & File Utilities Reference
Helpers available from ../utils/index.js. Use these instead of writing custom AST traversal.
ast-utils.ts
Class/Interface detection
| Function | Returns | Use when |
|---|---|---|
isNodeTypeClass(node) | boolean | Check if class implements INodeType or extends Node |
isCredentialTypeClass(node) | boolean | Check if class implements ICredentialType |
Property finding
| Function | Returns | Use when |
|---|---|---|
findClassProperty(node, name) | `PropertyDefinition \ | null` |
findObjectProperty(obj, name) | `Property \ | null` |
findJsonProperty(obj, name) | `Property \ | null` |
findArrayLiteralProperty(obj, name) | `Property \ | null` |
Value extraction
| Function | Returns | Use when |
|---|---|---|
getLiteralValue(node) | `string \ | boolean \ |
getStringLiteralValue(node) | `string \ | null` |
getBooleanLiteralValue(node) | `boolean \ | null` |
getModulePath(node) | `string \ | null` |
Array operations
| Function | Returns | Use when |
|---|---|---|
hasArrayLiteralValue(arr, value) | boolean | Check if array contains a specific string literal |
extractCredentialInfoFromArray(element) | `{ name, testedBy } \ | null` |
extractCredentialNameFromArray(element) | `string \ | null` |
Method matching
| Function | Returns | Use when |
|---|---|---|
isThisHelpersAccess(node) | boolean | Match this.helpers member expression |
isThisMethodCall(node, method) | boolean | Match this.methodName(...) calls |
isThisHelpersMethodCall(node, method) | boolean | Match this.helpers.methodName(...) calls |
Similarity
| Function | Returns | Use when |
|---|---|---|
findSimilarStrings(target, candidates, maxDistance?) | string[] | Suggest similar names (Levenshtein distance) |
file-utils.ts
Path operations
| Function | Use when |
|---|---|
isContainedWithin(child, parent) | Check path is within a directory |
safeJoinPath(base, ...parts) | Join paths with traversal prevention |
Package.json
| Function | Returns | Use when |
|---|---|---|
findPackageJson(startDir) | `string \ | null` |
readPackageJsonN8n(startDir) | `N8nPackageJson \ | null` |
readPackageJsonCredentials(startDir) | Set<string> | Get credential names from package.json |
readPackageJsonNodes(startDir) | string[] | Get resolved node file paths |
File system
| Function | Use when |
|---|---|
validateIconPath(filePath, iconValue) | Check icon file exists and is SVG |
extractCredentialNameFromFile(filePath) | Parse credential class name from file |
fileExistsWithCaseSync(filePath) | Case-sensitive existence check |
findSimilarSvgFiles(dir, name) | Suggest similar SVG filenames |
Credential verification
| Function | Use when |
|---|---|
areAllCredentialUsagesTestedByNodes(startDir) | Check all credentials have testedBy |