# How to Programmatically Use the DESIGN.md Linter API in Node.js

> Programmatically use the DESIGN.md linter API in Node.js. Import the lint function and get a typed LintReport with findings and Tailwind CSS config for your markdown.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: how-to-guide
- Published: 2026-06-27

---

**Import the `lint` function from `@google/design.md/linter` and pass a DESIGN.md markdown string to receive a fully typed `LintReport` containing findings, severity summaries, and a generated Tailwind CSS configuration.**

The `@google/design.md` package exposes a pure‑JavaScript/TypeScript linter that can be embedded directly into build pipelines, CI steps, or custom tooling without invoking the CLI. By importing from the `./linter` entry point declared in [`packages/cli/package.json`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/package.json), you gain access to the same parsing, validation, and Tailwind emission logic used internally by the command‑line tool. All core functions are exported from [`packages/cli/src/linter/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/index.ts) and provide full type safety for Node.js applications.

## Core Architecture and Pipeline

The linter API follows a strict four‑phase pipeline implemented in [`packages/cli/src/linter/lint.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/lint.ts). First, the **ParserHandler** ingests raw markdown and produces a `ParsedDesignSystem` object. Next, the **ModelHandler** resolves these tokens into a fully typed **DesignSystemState**, performing default value inference and cross‑reference checks. The **runLinter** function then executes the rule set against this state, and finally the **TailwindEmitterHandler** generates the CSS theme configuration. Each phase is exposed individually, allowing you to intercept or customize specific stages while maintaining type safety.

## Basic Programmatic Linting

The simplest integration calls the high‑level `lint` function exported from `@google/design.md/linter`. This single call orchestrates parsing, model resolution, rule evaluation, and Tailwind emission.

```typescript
import { lint } from '@google/design.md/linter';
import { readFileSync } from 'node:fs';

const designMd = readFileSync('path/to/DESIGN.md', 'utf8');
const report = lint(designMd);

// Inspect findings
report.findings.forEach(finding => {
  console.log(`[${finding.severity}] ${finding.path ?? ''}: ${finding.message}`);
});

// Access the generated Tailwind config
console.log('Tailwind theme:', report.tailwindConfig);

```

The `lint` function automatically uses **DEFAULT_RULES** from [`packages/cli/src/linter/rules/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/rules/index.ts) and returns a comprehensive **LintReport** containing the resolved design system, all findings, severity counts, and the Tailwind configuration object.

## Advanced Integration Patterns

### Implementing Custom Lint Rules

You can supply your own **LintRule** functions or replace the default set entirely. Custom rules conform to the signature `(state: DesignSystemState) => Finding[]` and are merged with any other rules passed to the `lint` options.

```typescript
import { lint, type LintRule } from '@google/design.md/linter';

const tempTokenRule: LintRule = (state) => {
  const findings = [];
  for (const [path, token] of Object.entries(state.tokens ?? {})) {
    if (path.includes('temp')) {
      findings.push({
        severity: 'warning',
        path,
        message: `Token name contains "temp": ${path}`,
      });
    }
  }
  return findings;
};

const report = lint(designMd, { rules: [tempTokenRule] });

```

The rule runner in [`packages/cli/src/linter/linter/runner.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/runner.ts) handles both plain functions and descriptor objects, aggregating findings into the final report.

### Linting Without Parsing

If you already have a parsed `DesignSystemState`, you can skip the parsing phase and invoke **runLinter** directly. This is useful when integrating with custom parsers or caching layers.

```typescript
import { runLinter, DEFAULT_RULES } from '@google/design.md/linter';
import { ModelHandler } from '@google/design.md/linter/model/handler.js';

// Assume `parsed` is a ParsedDesignSystem obtained elsewhere
const { designSystem } = new ModelHandler().execute(parsed);

// Run only the linting phase with default rules
const result = runLinter(designSystem, DEFAULT_RULES);
console.log('Errors:', result.summary.errors);

```

### Extracting Tailwind Configuration

The linter produces a Tailwind theme object that you can merge into your existing Tailwind configuration. The **TailwindEmitterHandler** in [`packages/cli/src/linter/tailwind/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/tailwind/handler.ts) generates this object during the linting process.

```typescript
import { lint } from '@google/design.md/linter';
import type { Config } from 'tailwindcss';

const report = lint(designMd);
const generatedTheme = report.tailwindConfig; // { theme: { extend: … } }

const tailwindConfig: Config = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      ...generatedTheme.theme.extend,
      // add your own extensions here
    },
  },
};

export default tailwindConfig;

```

## Summary

- Import the **`lint`** function from `@google/design.md/linter` to run the complete pipeline with a single call.
- The linter processes markdown through four distinct phases: parsing, model resolution, rule evaluation, and Tailwind emission, as implemented in [`packages/cli/src/linter/lint.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/lint.ts).
- Supply custom **`LintRule`** functions to the `lint` options to enforce project‑specific constraints.
- Use **`runLinter`** directly from [`packages/cli/src/linter/linter/runner.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/runner.ts) when you need to lint an already resolved `DesignSystemState`.
- Access the generated Tailwind theme via **`report.tailwindConfig`** for immediate use in your CSS build pipeline.

## Frequently Asked Questions

### What entry point should I import from to use the linter API?

Import from `@google/design.md/linter`, which is defined as the `./linter` conditional export in [`packages/cli/package.json`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/package.json). This entry point re‑exports all public primitives from [`packages/cli/src/linter/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/index.ts), including `lint`, `runLinter`, `LintReport`, and `DesignSystemState`.

### Can I run the linter without generating Tailwind CSS?

Yes. The `lint` function always produces a `tailwindConfig` property, but you can ignore it if you only need validation. Alternatively, use `runLinter` directly from [`packages/cli/src/linter/linter/runner.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/linter/runner.ts) with your own `DesignSystemState` to execute only the rule evaluation phase without any emission handlers.

### How do I create custom lint rules that access the full design system?

Define a function conforming to the `LintRule` type: `(state: DesignSystemState) => Finding[]`. The `state` object contains the fully resolved tokens, groups, and aliases after the `ModelHandler` has processed the raw markdown. Return an array of `Finding` objects with `severity`, `path`, and `message` properties.

### Where are the default lint rules defined?

The **DEFAULT_RULES** array is exported from [`packages/cli/src/linter/rules/index.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/rules/index.ts). These rules are automatically applied when you call `lint` without specifying a custom `rules` option, or when you call `runLinter` with the `DEFAULT_RULES` import.