

Building at Speed with Custom Lint Rules

At Daylight Security, we believe the best engineering happens when you can move fast, but only within guardrails that keep you aligned, consistent, and secure.
This is the first in a series of articles titled “pragmatic AI-native engineering”, where we share how we put these principles into practice in our day-to-day development.
AI writes its best code when the boundaries are clear. Custom lint rules transform our collective knowledge - the patterns we value, the pitfalls we avoid - into an automated system that runs everywhere, for everyone.
Writing custom rules are a perfect example of pragmatic AI-native engineering in action: given explicit boundaries, auto-fix guidance, and testable expectations across the repository, AI tools can provide the best results.
This means less ambiguity, fewer regressions, reduced review churn, and far more consistency between human and AI-generated code.
Here’s how we did it, and how you can too.

Setting Up Custom ESLint Rules
Step 1: Create the Plugin Structure
Create an eslint-rules/ directory at your project root:
Install the required dependencies:
eslint: Required for running the lint rules@typescript-eslint/parser: Enables TypeScript support for your rulesmocha: For writing tests (alternatives: jest/vitest)
Step 2: Create the Plugin Entry Point
Create eslint-rules/index.js:
Step 3: Write Your First Rule
Create eslint-rules/rules/ directory and add your rules. Here’s an example snippet representing a rule from our codebase:
Step 4: Install and Configure
In your main package.json, add the local plugin:
Then in eslint.config.js:
Step 5: Write Tests for Your Rules
Testing your custom ESLint rules ensures they work as expected and prevents regressions. The good news? LLMs are excellent at writing these tests.
Create a test file in eslint-rules/tests/:
When to Use Custom ESLint Rules
Custom rules shine when you need to enforce patterns that can’t be captured by off-the-shelf linting. Here are real examples from our codebase:
1. Enforcement: Preventing Direct Third-Party Imports
Problem: Direct imports from auth provider libraries scattered across the codebase can make migrations painful.
Solution: no-direct-auth-provider-imports rule
Why it matters: When we need to swap auth providers, we only update one wrapper instead of hunting down dozens of direct imports. Same rule of thumb can be applied to external icon libraries, analytics providers, or anything else that could require flexibility
2. Custom Business Logic: Enforcing Tenant Context
Problem: Next.js’s useRouter doesn’t know about our multi-tenant architecture.
Solution: no-next-router rule
Why it matters: Every route automatically includes tenant context, preventing bugs where tenant ID is missing from URLs.
3. Intentional Observability: No Console Statements
Problem: console.error statements scattered throughout the codebase (a pattern that LLMs use generously in their generated try/catch blocks) can make it impossible to track errors efficiently.
Solution: no-console-statements rule that guides to use our custom utility function.
Why it matters: Forces deliberate observability decisions, allowing us to distinguish known from unknown errors, create dashboards, set up alerts, and maintain centralized error tracking across our entire application.
The Bottom Line
Writing custom lint rules is an effective way to turn implicit knowledge into explicit constraints, which is equally great for humans and LLMs.
Interested in building AI-native security engineering? We’re hiring at Daylight Security.
Subscribe to newsletter
Subscribe to receive the latest blog posts to your inbox every week.
.avif)




Related posts
.png)

For expert judgment, scaled to your world, head for Daylight



.png)