Contributing to nCode

Table of contents

  1. Code of Conduct
  2. Ways to Contribute
  3. Development Setup
  4. Adding a Node Handler
    1. 1. Create or extend a handler file
    2. 2. Register the import
    3. 3. Write tests
    4. 4. Key rules for handlers
  5. Coding Standards
    1. Python
    2. TypeScript / React
    3. Commit messages
  6. Pull Request Checklist
  7. Project Roadmap

Thank you for considering a contribution! nCode is open source under the MIT License and welcomes all contributions — from bug reports and documentation improvements to full node handler implementations.


Code of Conduct

All contributors are expected to follow the Contributor Covenant Code of Conduct. Please read it before participating.


Ways to Contribute

Contribution type Where to start
Report a bug Bug Report issue template
Request a feature Feature Request issue template
Request a new node handler Node Handler Request template
Browse open tasks Issues labelled good first issue
Ask a question GitHub Discussions — Q&A

Development Setup

git clone https://github.com/opsingh861/nCode.git
cd nCode

# Backend
cd backend
python -m venv .venv
# Windows:  .\.venv\Scripts\Activate.ps1
# macOS/Linux: source .venv/bin/activate
pip install -r ../requirements.txt

# Frontend (optional)
cd ../frontend
npm install

Run all tests before making changes to establish a baseline:

backend\.venv\Scripts\python.exe -m pytest backend/tests/ -v

Adding a Node Handler

This is the most impactful contribution. Here is the complete pattern:

1. Create or extend a handler file

Add your class to an existing file in backend/handlers/ (e.g., apps.py for SaaS integrations) or create a new file.

# backend/handlers/apps.py  (or a new file)
import re
from backend.handlers.registry import register
from backend.handlers.base import GenerationContext
from backend.core.ir import IRNode, IRNodeKind
from backend.models.workflow import N8nNode


def _safe_var(name: str) -> str:
    var = re.sub(r"[^a-zA-Z0-9]+", "_", name).strip("_").lower()
    return f"n_{var}" if var[0].isdigit() else var


@register("n8n-nodes-base.myNode")
class MyNodeHandler:
    def generate(self, node: N8nNode, ctx: GenerationContext) -> IRNode:
        var = _safe_var(node.name)
        ctx.register_node_var(node.name, var)
        ctx.add_import("import my_library")
        ctx.add_package("my-library>=1.0")

        code = [
            f"# MyNode: {node.name}",
            f"{var}_output = [",
            f'    json',
            f"]",
        ]

        return IRNode(
            node_id=node.id,
            node_name=node.name,
            kind=IRNodeKind.STATEMENT,
            python_var=var,
            code_lines=code,
        )

    def supported_operations(self) -> list[str]:
        return ["defaultOperation"]

    def required_packages(self) -> list[str]:
        return ["my-library"]

2. Register the import

Add an import at the bottom of backend/handlers/__init__.py to trigger the @register side-effect:

from backend.handlers import apps  # noqa: F401 — registers handlers

3. Write tests

Add test cases in backend/tests/test_handlers.py:

def test_my_node_generates_valid_code():
    node = N8nNode(
        id="1", name="My Node", type="n8n-nodes-base.myNode",
        typeVersion=1.0, parameters={"operation": "defaultOperation"},
    )
    ctx = GenerationContext()
    ir = MyNodeHandler().generate(node, ctx)

    assert ir.python_var == "my_node"
    assert any("my_library" in line for line in ir.code_lines)
    assert "my-library" in MyNodeHandler().required_packages()

4. Key rules for handlers

Data-flow contract: Every handler output must be list[dict] where each dict has {"json": {...}}. Never break this shape.

  • Use {python_var}_output as the variable name for the node’s result.
  • Use _safe_var() (defined locally in each handler file — copy the small function, do not import).
  • When a path is unsupported, emit a # TODO comment and pass items through — never raise an exception.
  • Credentials must use os.getenv("CREDENTIAL_NAME"), never hard-coded values.

Coding Standards

Python

  • PEP 8 style — enforced by black (88-char line limit) and isort
  • Type hints on all public functions
  • No Any without justification

TypeScript / React

  • interface declarations (not type aliases) — see frontend/src/types/api.ts
  • Strict TypeScript — no implicit any
  • HTTP calls through frontend/src/api/client.ts only — no direct fetch() in components
  • Functional components with hooks

Commit messages

Follow Conventional Commits:

feat(handlers): add full Slack send-message handler
fix(expression-engine): handle nested ternary with method chain
docs(architecture): add post-dominator merge detection explanation
test(handlers): add Slack handler unit tests

Types: feat, fix, docs, style, refactor, test, chore


Pull Request Checklist

Before opening a PR:

  • All tests pass: pytest backend/tests/ -v
  • Code is formatted: black backend/ and isort backend/
  • Frontend builds if you changed frontend files: npm run build
  • CHANGELOG.md updated under [Unreleased]
  • PR description uses the PR template

Project Roadmap

See open issues and the Discussions — Ideas board for the current roadmap.

High-priority areas:

  1. Full Slack, Notion, and Google Sheets handler implementations
  2. Switch node full expression support
  3. splitInBatches → Python for loop with itertools.islice
  4. Merge node strategies (append, combine, multiplex)
  5. JavaScript code node → Python best-effort transpilation