npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cloudflare-expression-lint

v0.11.1

Published

A parser, validator, and linter for Cloudflare Rules Language expressions with phase-aware field and function checking

Readme

cloudflare-expression-lint

A parser, validator, linter, formatter, and auto-fixer for Cloudflare Rules Language expressions with phase-aware field and function checking.

Catches errors before terraform apply — no API calls required.

Features

  • Full expression parser — lexer + recursive-descent parser for the Cloudflare wirefilter expression syntax
  • 211+ known fields with type information
  • Deprecated field detection — warns on legacy fields like ip.geoip.country with replacement suggestions
  • Phase-aware validation — knows which fields are available in which Cloudflare phase
  • Function context validationregex_replace() is only valid in rewrite/redirect contexts
  • Expression Builder compatibility — flags expressions that can't be loaded in the Cloudflare UI
  • Auto-fixer--fix rewrites expressions for Builder compatibility (wraps bare expressions, merges and-groups, applies De Morgan's law, normalizes operators)
  • Prettifier--prettify reformats long expressions across multiple lines using >- block scalars
  • Operator style — configurable preference for English (eq, and) vs C-like (==, &&)
  • YAML scanner — auto-detects expressions in YAML files and infers Cloudflare phase from context
  • Raw string preservationr"..." prefixes survive formatting and fixing
  • CLI tool — validate, fix, and format expressions from the command line or CI/CD pipelines
  • Programmatic API — use as a library in your own tools

Installation

npm install cloudflare-expression-lint

Or run directly with npx:

npx cloudflare-expression-lint config/**/*.yaml

CLI Usage

Validate YAML files

# Scan all YAML files for expressions
cf-expr-lint config/**/*.yaml

# Scan with JSON output (for CI integration)
cf-expr-lint --format json config/**/*.yaml

# Only show errors (suppress warnings)
cf-expr-lint --quiet config/**/*.yaml

Validate a single expression

# Filter expression (default)
cf-expr-lint -e '(http.host eq "example.com")'

# Rewrite expression
cf-expr-lint -e 'regex_replace(http.request.uri.path, "^/old/", "/new/")' -t rewrite_url

# With phase validation
cf-expr-lint -e 'http.response.code eq 200' -p http_request_firewall_custom
# ✗ [field-not-in-phase]: Field "http.response.code" is not available in phase "http_request_firewall_custom"

# From stdin
echo '(ip.src.country in {"US" "JP"})' | cf-expr-lint --stdin

Auto-fix expressions

# Fix a single expression
cf-expr-lint --fix -e 'not (http.cookie eq "a" or http.cookie eq "b")'
# Output: (not http.cookie eq "a" and not http.cookie eq "b")

# Fix all expressions in YAML files
cf-expr-lint --fix --config .cf-expr-lint.json config/**/*.yaml

# Dry-run — check if fixes are needed (exits non-zero if so)
cf-expr-lint --fix --check config/**/*.yaml

The fixer applies these transformations:

  • Wrap bare expressions: A eq B(A eq B)
  • Merge and-groups: (A) and (B)(A and B)
  • Remove outer parens from or-chains: ((A) or (B))(A) or (B)
  • Wrap or-branches: A or B(A) or (B)
  • Unwrap individually-wrapped and-conditions: ((A) and (B))(A and B)
  • De Morgan's law: not (A or B)(not A and not B)
  • Operator style: ==eq, <=le, &&and, etc.

Prettify expressions

# Format a single expression
cf-expr-lint --prettify -e '(http.host eq "test.com" and http.request.method eq "POST" and not ip.src in $blocklist)'

# Prettify all YAML files (rewrites in-place with >- block scalars)
cf-expr-lint --prettify --config .cf-expr-lint.json config/**/*.yaml

# Also convert existing | and |- block scalars to >-
cf-expr-lint --prettify --convert-block-scalars config/**/*.yaml

# Custom max line width (default: 120)
cf-expr-lint --prettify --max-width 100 config/**/*.yaml

# Dry-run — check if formatting is needed
cf-expr-lint --prettify --check config/**/*.yaml

Before:

expression: (http.host eq "example.com" and http.request.method eq "POST" and not ip.src in $blocklist and http.request.uri.path eq "/api/webhook")

After:

expression: >-
  (
    http.host eq "example.com"
    and http.request.method eq "POST"
    and not ip.src in $blocklist
    and http.request.uri.path eq "/api/webhook"
  )

Custom YAML key mappings (CLI)

By default, the scanner only looks for the expression key (the standard Cloudflare Terraform provider attribute). If your YAML uses other key names for expressions, tell the scanner about them:

cf-expr-lint \
  --expr-key rewrite_expression:rewrite_url:http_request_transform \
  --expr-key source_url_expression:filter:http_request_dynamic_redirect \
  --phase-map waf_rules:http_request_firewall_custom \
  config/**/*.yaml

Config file

For projects with many custom mappings, use a .cf-expr-lint.json config file:

{
  "expressionKeys": {
    "rewrite_expression": { "type": "rewrite_url", "phaseHint": "http_request_transform" },
    "source_url_expression": { "type": "filter", "phaseHint": "http_request_dynamic_redirect" }
  },
  "phaseMappings": {
    "waf_rules": "http_request_firewall_custom",
    "custom_rules": "http_request_firewall_custom",
    "ratelimit_rules": "http_ratelimit"
  },
  "accountLevelPaths": ["config/account/"],
  "ignoreCodes": ["contains-placeholders"],
  "operatorStyle": "english"
}

CLI Options

| Option | Short | Description | |--------|-------|-------------| | --expression | -e | Validate a single expression string | | --stdin | | Read expression from stdin | | --type | -t | Expression type: filter (default), rewrite_url, rewrite_header, redirect_target | | --phase | -p | Cloudflare phase for field validation | | --config | -c | Path to config file (JSON) with custom mappings | | --expr-key | | Add expression key mapping: key:type[:phase] (repeatable) | | --phase-map | | Add phase mapping: yaml_key:phase (repeatable) | | --format | -f | Output format: text (default), json | | --quiet | -q | Only show errors (suppress warnings) | | --warn-exit-code | | Exit code when warnings found (default: 0, use 2 for CI) | | --ignore-code | | Suppress a diagnostic code (repeatable) | | --operator-style | | Operator style: english (default), clike, off | | --fix | | Auto-fix expressions for Builder compatibility | | --prettify | | Reformat long expressions as multi-line >- block scalars | | --convert-block-scalars | | Convert \| and \|- to >- (use with --prettify) | | --max-width | | Max line width for --prettify (default: 120) | | --check | | Dry-run for --fix or --prettify (exits 1 if changes needed) | | --help | -h | Show help |

Expression Builder Compatibility

The Cloudflare Expression Builder UI requires expressions in a specific format:

Compatible:

  • Single group: (A and B and C)
  • Or-chain: (A) or (B and C) or (D)
  • Not toggle: (not A and not B)
  • Functions: (starts_with(field, "val")), (ends_with(field, "val"))

Not compatible (with suggested rewrites):

  • (A) and (B) → merge: (A and B)
  • (A or B) → split: (A) or (B)
  • not (A) → move inside: (not A)
  • not (A or B) → De Morgan's: (not A and not B)
  • ((A) or (B)) → remove outer: (A) or (B)
  • ((A) and (B)) → unwrap: (A and B)

Use --fix to apply these automatically.

CI/CD Integration

GitLab CI

lint-expressions:
  stage: validate
  image: node:20
  script:
    - npm install -g cloudflare-expression-lint@latest
    - cf-expr-lint --warn-exit-code 2 --config .cf-expr-lint.json $(find config -name "*.yaml" -o -name "*.yml")
    - cf-expr-lint --fix --check --config .cf-expr-lint.json $(find config -name "*.yaml" -o -name "*.yml")
    - cf-expr-lint --prettify --check --config .cf-expr-lint.json $(find config -name "*.yaml" -o -name "*.yml")
  allow_failure:
    exit_codes: [2]

GitHub Actions

- name: Lint Cloudflare expressions
  run: |
    npm install -g cloudflare-expression-lint@latest
    cf-expr-lint --warn-exit-code 2 config/**/*.yaml
    cf-expr-lint --fix --check config/**/*.yaml
    cf-expr-lint --prettify --check config/**/*.yaml

Programmatic API

import { validate, parse, fixExpression, formatExpression } from 'cloudflare-expression-lint';

// Validate
const result = validate('(http.host eq "example.com")', {
  expressionType: 'filter',
  phase: 'http_request_firewall_custom',
});

// Auto-fix
const fixed = fixExpression('not (http.cookie eq "a" or http.cookie eq "b")');
console.log(fixed.expression); // '(not http.cookie eq "a" and not http.cookie eq "b")'

// Prettify
const pretty = formatExpression('(A and B and C)', { maxWidth: 40 });

Diagnostic Codes

| Code | Severity | Description | |------|----------|-------------| | parse-error | error | Syntax error in expression | | unknown-field | error | Field name not recognized | | unknown-function | error | Function name not recognized | | field-not-in-phase | error | Field not available in the specified Cloudflare phase | | function-not-in-context | error | Function not available in the expression context | | function-max-exceeded | error | Function used more times than allowed | | operator-type-mismatch | error | Operator not compatible with field type | | invalid-cidr-mask | error | CIDR mask out of valid range | | deprecated-field | warning | Field is deprecated; replacement suggested | | expression-too-long | warning | Expression exceeds 4096 character limit | | ambiguous-precedence | warning | Mixed and/or without explicit grouping | | expression-whitespace | warning | Leading or trailing whitespace | | missing-zone-plan-filter | warning | Account-level expression missing ENT suffix | | empty-in-list | warning | Empty in {} will never match | | too-many-regex | warning | More than 64 regex patterns | | header-key-not-lowercase | warning | Header map key should be lowercase | | invalid-wildcard-pattern | warning | Wildcard contains ** | | builder-incompatible | info | Not in Expression Builder format | | prefer-english-operator | info | Suggests English notation (eq, and) | | prefer-clike-operator | info | Suggests C-like notation (==, &&) | | prefer-bare-boolean | info | Prefer ssl over ssl == true |

Architecture

src/
├── lexer.ts          # Tokenizer: string → Token[]
├── parser.ts         # Parser: Token[] → AST (recursive descent)
├── validator.ts      # Validator: AST → Diagnostic[] (semantic analysis)
├── fixer.ts          # Auto-fixer: AST → AST (Builder compatibility transforms)
├── formatter.ts      # Prettifier: AST → multi-line string
├── rewriter.ts       # YAML rewriter: replaces expressions in files
├── yaml-scanner.ts   # YAML file scanner with configurable phase inference
├── eslint-plugin.ts  # ESLint plugin adapter (optional)
├── cli.ts            # CLI entry point
├── types.ts          # Shared type definitions
├── index.ts          # Public API exports
└── schemas/
    ├── fields.ts     # 211+ field definitions
    ├── functions.ts  # 25+ function definitions
    └── operators.ts  # Operator type constraints

License

MIT