squirrelscan
GitHub

Rule Options

Configure individual rule thresholds, limits, and behavior

The [rule_options] section allows fine-tuning individual audit rules with custom thresholds and parameters.

Configuration

[rule_options]

[rule_options."core/meta-title"]
min_length = 30
max_length = 60

[rule_options."content/word-count"]
min_words = 300
warn_threshold = 500

[rule_options."adblock/element-hiding"]
maxMatchesToReport = 20

How rule options work

Structure

Rule options use TOML table syntax:

[rule_options."<rule-id>"]
option1 = value1
option2 = value2
  • <rule-id> - Full rule ID (e.g., core/meta-title, content/word-count). It must be the exact ID: a category pattern such as core/* configures nothing.
  • Option names are rule-specific and spelled exactly as the rule reads them. Most are snake_case, but some are camelCase (excludePatterns, maxMatchesToReport), so copy them from the rule’s page.
  • All options have defaults (configuration is optional)

Where to find a rule’s options

Each rule’s page in the Rules reference has an Options table listing every option the rule reads, with its type and default. A rule with no Options table takes no options, for example a11y/skip-link: to stop one of those running, disable it instead.

What happens to a mistake

squirrel config validate checks every [rule_options] table against the rule it names, and squirrel audit checks them again before it crawls. Each message names the table and the option:

  • An unknown rule ID or option name changes nothing: the rule runs on its defaults. config validate reports it and exits 1, and squirrel audit prints a warning and carries on. The message suggests a rule with the same name in another category, or lists the options the rule takes:

    Invalid config: [rule_options."performance/ttfb"]: no rule has this id, so the table changes nothing (did you mean "perf/ttfb"?)
    Invalid config: [rule_options."core/meta-description"] no_such_option: not an option of core/meta-description, so it changes nothing (options: min_length, max_length)
  • A value of the wrong type would fail the rule. config validate reports it, and squirrel audit stops before crawling when the audit runs that rule. On a rule the audit does not run, it is a warning.

    Error: [rule_options."core/meta-title"] min_length: Invalid input: expected number, received string
  • An enabled that is not true or false is ignored, and reported like an unknown option.


Common rule options

Core rules

core/meta-title

Control title tag length requirements.

Options:

[rule_options."core/meta-title"]
min_length = 30   # Minimum title length (characters)
max_length = 75   # Maximum title length (characters)

Defaults:

  • min_length: 30
  • max_length: 75

Examples:

Strict title requirements:

[rule_options."core/meta-title"]
min_length = 40
max_length = 55

Lenient title requirements:

[rule_options."core/meta-title"]
min_length = 20
max_length = 70

Behavior: a title shorter than min_length or longer than max_length → warn. A missing or empty title fails whatever the options say.

Why configure:

  • Different industries have different conventions
  • Brand names may require more characters
  • Mobile SERPs show fewer characters (~50)

core/meta-description

Control meta description length requirements.

Options:

[rule_options."core/meta-description"]
min_length = 120   # Minimum description length
max_length = 160   # Maximum description length

Defaults:

  • min_length: 120
  • max_length: 160

Examples:

Google’s recommended length:

[rule_options."core/meta-description"]
min_length = 120
max_length = 158

Allow longer descriptions:

[rule_options."core/meta-description"]
min_length = 100
max_length = 200

Behavior: a description outside min_length to max_length → warn. A missing description fails whatever the options say.

Why configure:

  • Google truncates ~160 characters
  • Longer descriptions provide more context
  • E-commerce may need more details

Content rules

content/word-count

Set minimum word count for content quality.

Options:

[rule_options."content/word-count"]
min_words = 300        # Minimum acceptable word count
warn_threshold = 500   # Optimal word count threshold

Defaults:

  • min_words: 300
  • warn_threshold: 500

Examples:

Blog posts (longer content):

[rule_options."content/word-count"]
min_words = 500
warn_threshold = 1000

Product pages (shorter acceptable):

[rule_options."content/word-count"]
min_words = 150
warn_threshold = 300

Landing pages (minimal):

[rule_options."content/word-count"]
min_words = 100
warn_threshold = 200

Why configure:

  • Blog posts need more words (800-2000+)
  • Product pages can be shorter (200-500)
  • Landing pages focus on conversion

Behavior:

min_words = 300
warn_threshold = 500
  • < 300 words → warn (thin content)
  • 300-499 words → info (could be longer)
  • ≥ 500 words → pass (good length)

content/keyword-stuffing

Detect keyword over-optimization.

Options:

[rule_options."content/keyword-stuffing"]
density_threshold = 3   # Maximum keyword density, in percent
min_occurrences = 5     # A word must appear more often than this to be flagged
whitelist = []          # Words never flagged, such as a product name your domain doesn't carry

Defaults:

  • density_threshold: 3 (3%)
  • min_occurrences: 5
  • whitelist: []

density_threshold is a percentage: 3 means 3%. A fraction such as 0.03 means 0.03%, which flags nearly every word that appears more than min_occurrences times.

Examples:

Strict (avoid over-optimization):

[rule_options."content/keyword-stuffing"]
density_threshold = 2   # 2% max

Lenient, and ignore a product name (the name in your domain is already ignored):

[rule_options."content/keyword-stuffing"]
density_threshold = 5   # 5% max
whitelist = ["squirrelscan"]

Why configure:

  • Different content types have different natural repetition
  • Technical docs may repeat terms more
  • E-commerce descriptions can be repetitive

How density is calculated:

density = (keyword_occurrences / total_words) × 100

Example:

  • “widget” appears 30 times
  • Page has 1000 words
  • Density = 30/1000 × 100 = 3%

Words are counted from the page’s body text (scripts and styles excluded) as runs of three or more letters a to z, in any case, so “SEO” counts and “AI” does not. Pages with fewer than 100 such words are not checked (info). Generic button and link labels such as “Learn more” and “Add to cart” are then taken out of both counts. A word is flagged when its density is above density_threshold and it appears more than min_occurrences times, and common words are never flagged.


Check that articles link to other pages and cite external sources, scaled to the article’s length. It only runs on article pages (an article page type or Article structured data).

Options:

[rule_options."content/article-links"]
short_max_words = 800     # Under this many words, an article is short
long_min_words = 1500     # From this many words, an article is long (medium in between)

short_min_internal = 1    # Minimum internal links, by length
medium_min_internal = 2
long_min_internal = 3

short_min_external = 1    # Minimum external links, by length
medium_min_external = 2
long_min_external = 2

Defaults: as shown above.

Examples:

Require more citations (journalism):

[rule_options."content/article-links"]
short_min_external = 3
medium_min_external = 4
long_min_external = 5

No external link requirement:

[rule_options."content/article-links"]
short_min_external = 0
medium_min_external = 0
long_min_external = 0

Behavior: an article with fewer internal or external links than its length band requires → warn, one check each for internal and external links.

Why configure:

  • Academic/news needs citations
  • Product pages may not need external links
  • E-E-A-T prefers authoritative sources

URL patterns

links/orphan-pages, links/dead-end-pages and crawl/all-noindex-pages take lists of URL patterns. There are three forms, and no * or ** globs:

  • "/thank-you" matches any URL containing that text.
  • "/blog/" (starting and ending with /) matches the path /blog and everything under /blog/, but not /blog-archive.
  • 'regex:^/blog/\d{4}/' matches the path against a regular expression. Add flags after a colon: 'regex:^/blog:i'. Use single quotes (a TOML literal string), because TOML rejects \d inside double quotes.

links/orphan-pages

Detect pages with too few incoming internal links.

Options:

[rule_options."links/orphan-pages"]
minInboundLinks = 2                        # Pages with fewer incoming links are flagged
excludePatterns = ["/404", "/.well-known/"]  # Pages never flagged

Defaults:

  • minInboundLinks: 2
  • excludePatterns: []

Examples:

Ignore utility pages:

[rule_options."links/orphan-pages"]
excludePatterns = [
  "/sitemap.xml",
  "/robots.txt",
  "/404",
  "/500",
  "/.well-known/"
]

Flag only pages nothing links to:

[rule_options."links/orphan-pages"]
minInboundLinks = 1

Why configure:

  • Some pages are intentionally unlinked
  • Utility pages don’t need internal links
  • Reduce false positives

links/dead-end-pages

Find pages with no outgoing internal links.

Options:

[rule_options."links/dead-end-pages"]
excludePatterns = ["/thank-you", "/confirmation"]   # Pages allowed to be dead ends
minLinks = 1               # Fewer outgoing internal links than this is a dead end
countAnchorLinks = false   # Count #section links (useful for single-page apps)

Defaults:

  • excludePatterns: ["/thank-you", "/confirmation", "/download", "/success", "/submitted"]
  • minLinks: 1
  • countAnchorLinks: false

Setting excludePatterns replaces the default list, so include the defaults you still want.

Examples:

Ignore conversion pages:

[rule_options."links/dead-end-pages"]
excludePatterns = [
  "/thank-you",
  "/confirmation",
  "/download",
  "/success",
  "/submitted",
  "/checkout/success"
]

Why configure:

  • Conversion pages often have no links (by design)
  • Focus user on one action
  • Reduce false positives

Control internal linking recommendations.

Options:

[rule_options."links/internal-links"]
min_internal_links = 1     # Minimum internal links per page
max_internal_links = 100   # Maximum internal links per page

Defaults:

  • min_internal_links: 1
  • max_internal_links: 100

Examples:

Require more internal linking:

[rule_options."links/internal-links"]
min_internal_links = 5

Allow large navigation menus:

[rule_options."links/internal-links"]
max_internal_links = 250

Behavior: fewer than min_internal_links or more than max_internal_links → warn.

Why configure:

  • Landing pages may have fewer links
  • Blog posts benefit from more internal links
  • E-commerce categories need many links

Performance rules

perf/ttfb

Time to First Byte threshold.

Options:

[rule_options."perf/ttfb"]
good_threshold = 600   # Under this is good (ms)
poor_threshold = 1000  # From this is poor (ms)

Defaults:

  • good_threshold: 600
  • poor_threshold: 1000

Examples:

Strict performance (fast hosting):

[rule_options."perf/ttfb"]
good_threshold = 300
poor_threshold = 600

Lenient (slow hosting):

[rule_options."perf/ttfb"]
good_threshold = 1000
poor_threshold = 2000

Why configure:

  • Shared hosting is slower
  • CDN hosting is faster
  • Geographic distance affects TTFB

Behavior:

good_threshold = 600
poor_threshold = 1000
  • < 600ms → pass
  • 600-999ms → warn
  • ≥ 1000ms → fail

perf/dom-size

DOM size, depth and width limits.

Options:

[rule_options."perf/dom-size"]
warn_threshold = 1500   # Warning threshold (elements)
error_threshold = 3000  # Error threshold (elements)
max_depth = 32          # Deepest nesting before a warning
max_children = 60       # Most children of one <body> element before a warning

Defaults:

  • warn_threshold: 1500
  • error_threshold: 3000
  • max_depth: 32
  • max_children: 60

Examples:

Simple sites:

[rule_options."perf/dom-size"]
warn_threshold = 800
error_threshold = 1500

Complex apps:

[rule_options."perf/dom-size"]
warn_threshold = 2000
error_threshold = 4000
max_depth = 40

Behavior: fewer elements than warn_threshold → pass, fewer than error_threshold → warn, otherwise fail. Depth over max_depth and an element inside <body> with more than max_children children each warn on their own. <head> children are not counted for max_children.

Why configure:

  • Simple pages have fewer elements
  • Complex apps have more elements
  • Mobile performance more sensitive

Security rules

security/http-to-https

Probe whether the http:// versions of your URLs redirect to HTTPS. The rule requests a sample of URLs over plain HTTP and lists the ones that redirect.

Options:

[rule_options."security/http-to-https"]
sampleLimit = 20   # Most URLs to probe (1 to 100)

Default:

  • sampleLimit: 20

Examples:

Probe more of a large site:

[rule_options."security/http-to-https"]
sampleLimit = 100

Why configure:

  • A larger sample covers more of a big site
  • A smaller one makes fewer live requests to your server

Crawl rules

crawl/all-noindex-pages

Count the pages blocked from indexing and list a sample, and raise the severity for sections that must stay indexable.

Options:

[rule_options."crawl/all-noindex-pages"]
errorOnPatterns = ["/products/"]   # A blocked page here fails
warnOnPatterns = ["/blog/"]        # A blocked page here warns

Defaults:

  • errorOnPatterns: []
  • warnOnPatterns: []

Examples:

Treat any blocked product or blog page as a release blocker:

[rule_options."crawl/all-noindex-pages"]
errorOnPatterns = ["/products/", "/blog/"]

Why configure:

  • Staging sites should be fully noindexed
  • Production sites shouldn’t be noindexed
  • Detect misconfiguration

Behavior: pages blocked by a noindex meta tag or header, or by robots.txt, are reported as info with a count (the first ten listed). Blocked pages matching warnOnPatterns → warn, and ones matching errorOnPatterns → fail, each with a count and the first five listed. The patterns use the URL pattern forms.


Blocking rules

adblock/element-hiding

Check for elements that ad blockers would hide. This is a cloud rule: it needs squirrel auth login, and the cloud matches your selectors against the full EasyList cosmetic rules. The filter list is not configurable.

Options:

[rule_options."adblock/element-hiding"]
maxMatchesToReport = 10   # Matching selectors to list in detail

Default:

  • maxMatchesToReport: 10

Examples:

List more matches:

[rule_options."adblock/element-hiding"]
maxMatchesToReport = 20

Check for links and resources that ad blockers (EasyList) would block. Also a cloud rule, with no list choice.

Options:

[rule_options."adblock/blocked-links"]
maxMatchesToReport = 10   # Blocked links to list in detail

Default:

  • maxMatchesToReport: 10

Configuration examples

Blog site

[rule_options."core/meta-title"]
min_length = 40
max_length = 60

[rule_options."content/word-count"]
min_words = 500
warn_threshold = 1000

[rule_options."content/article-links"]
short_min_external = 3
medium_min_external = 3
long_min_external = 3

Why:

  • Blog posts need longer titles
  • Content should be comprehensive
  • External citations improve E-E-A-T

E-commerce site

[rule_options."core/meta-title"]
min_length = 30
max_length = 55  # Shorter for mobile

[rule_options."content/word-count"]
min_words = 150
warn_threshold = 300  # Product pages are shorter

[rule_options."links/dead-end-pages"]
excludePatterns = ["/thank-you", "/confirmation", "/checkout/success"]

Why:

  • Product titles often shorter
  • Product descriptions concise
  • Conversion pages are dead-ends

Documentation site

[rule_options."content/word-count"]
min_words = 200
warn_threshold = 400

[rule_options."links/internal-links"]
min_internal_links = 5  # Docs should cross-reference

[rule_options."content/keyword-stuffing"]
density_threshold = 5  # Percent; technical terms repeat

Why:

  • Docs can be shorter (reference material)
  • Cross-referencing improves UX
  • Technical terms naturally repeat

Landing pages

[rule_options."content/word-count"]
min_words = 100
warn_threshold = 200  # Conversion-focused

[rule_options."links/internal-links"]
min_internal_links = 1  # Focus on CTA

[rule_options."links/dead-end-pages"]
excludePatterns = ["/lp/"]  # Landing pages

Why:

  • Landing pages are concise
  • Minimize distractions from CTA
  • Dead-ends by design
  • Single page focus

Checking your options

squirrel config show prints the [rule_options] you have set (it does not list defaults). squirrel config validate checks each table against the rule it names:

squirrel config validate
Invalid config: [rule_options."core/meta-title"] min_length: Invalid input: expected number, received string

Fix it and validate again:

# Before (fails the audit)
[rule_options."core/meta-title"]
min_length = "thirty"
# After
[rule_options."core/meta-title"]
min_length = 30

Complete example

[project]
name = "myblog"

[crawler]
max_pages = 500

[rules]
enable = ["*"]
disable = ["ai/*"]

# Rule-specific configuration
[rule_options."core/meta-title"]
min_length = 40
max_length = 60

[rule_options."core/meta-description"]
min_length = 120
max_length = 160

[rule_options."content/word-count"]
min_words = 500
warn_threshold = 1000

[rule_options."content/article-links"]
short_min_external = 2
medium_min_external = 2
long_min_external = 2

[rule_options."links/orphan-pages"]
excludePatterns = ["/404", "/500", "/.well-known/"]

[rule_options."perf/ttfb"]
good_threshold = 600
poor_threshold = 1000

This configuration:

  • Requires longer titles (40-60 chars)
  • Enforces substantial content (500+ words)
  • Expects external citations
  • Ignores utility pages in orphan check
  • Sets strict TTFB thresholds

Type to search…

↑↓ navigate↵ openesc close