Expressions
Scorecards uses a shared expression language to select what a rule applies to and to define the conditions that selection must meet. The syntax for operators is the same for both rule target types; the fields you can reference differ:
- Catalog entities - Backstage entities (Components, APIs, and more), using fields such as
$kind,$metadata, and$spec. - Repository content - Files in a repository, using fields such as
$exists,$filePath, and$content.
How Expressions Work
Every Scorecards rule has two expression roles:
- Selection - Which entities or files the rule applies to.
- Validation - Whether that selection passes. For repository content rules, validation is optional: leave it empty for an existence-only check.
| Target type | Selection field in the UI | Validation field in the UI |
|---|---|---|
| Catalog entities | Selector | Criteria |
| Repository content | Match files | Content check (optional) |
All expressions use the same operator syntax. Learn the shared operators once, then use the catalog or repository content field references for the target type you are writing.
Shared Operators
These operators are available in both catalog entity and repository content expressions.
Comparison operators
==- Equality comparison!=- Inequality comparison>- Greater than<- Less than>=- Greater than or equal<=- Less than or equalis- Case-insensitive string equality~- Regular expression matchstarts with,ends with,contains,not contains- Case-insensitive string matching
Logical operators
and- Logical AND (and,AND, and&&are equivalent)or- Logical OR (or,OR, and||are equivalent)not- Logical NOT (notandNOTare equivalent)
Access operators
.- Property access (for example$metadata.name)@- Array index access
Arithmetic operators
+- Addition-- Subtraction*- Multiplication/- Division
Operator precedence
Operators follow this precedence order (highest to lowest):
- Access (
.,@) - Arithmetic (
*,/,+,-) - Comparison (
>,<,>=,<=) - Equality (
==,!=,is,~, and the string-matching operators) - Logical (
and,or)
Use parentheses when you need a different evaluation order.
Regular expressions
Regex patterns are double-quoted strings on the right of ~. Write complete DSL expressions, never raw JavaScript regex literals:
WRONG: /^#\s+.+/gm
RIGHT: $content ~ "^#\s"
Additional rules:
- Do not append
/flags(for example/gmor/i). Those characters are treated as literal text, not RegExp flags. - Escape regex metacharacters inside the quoted pattern (for example
\.,\",\\). Write\sand\ddirectly. - Use
\nin patterns to match line breaks. Do not rely on multiline^or$behaviour; use(?:^|\n)or similar when matching line boundaries.
Complex structural checks (for example a full markdown heading hierarchy with no skipped levels) cannot be expressed as a single regex. Prefer pragmatic proxies such as required sections, simpler content patterns, or file existence checks.
Catalog Entity Expressions
Catalog entity rules select Backstage entities and validate their metadata, specifications, and annotations.
Selectors
Selectors determine which entities a rule applies to:
$kind is <EntityType>
For example:
$kind is Componenttargets all Component entities$kind is APItargets all API entities
Field references
-
$entity- The entire entity object. -
$kind- The entity kind.$kind is Component -
$metadata- Core entity information.$metadata.name # Entity name
$metadata.owner # Entity owner -
$namespace- The entity namespace.$namespace == "default" -
$spec- Entity specifications.$spec.type # Entity type
$spec.lifecycle # Lifecycle stage
$spec.definition # API definition -
$annotations- Integration metadata.$annotations."gitlab.com/instance" # GitLab instance
$annotations."gitlab.com/project-slug" # GitLab project
$annotations."jira/project-key" # Jira project
Examples
These examples come from the pre-configured golden rules:
-
API Validation
$metadata.name and $spec.type and $spec.lifecycle and $spec.owner and $spec.definition -
Component Validation
$metadata.name and $spec.type and $spec.lifecycle and $spec.owner -
GitLab Integration
$annotations."gitlab.com/instance" and $annotations."gitlab.com/project-slug" -
Jira Integration
$annotations."jira/project-key"
Catalog criteria often use and to require several fields together. All conditions must be true for validation to pass.
Repository Content Expressions
Repository content rules select files in a repository and, optionally, validate their content. The match files expression decides which files are in scope; the content check expression decides whether those matched files pass.
Venue.sh evaluates the match files expression against each file independently. Every matched file must satisfy the content check for the repository to pass. Make the match files expression narrow enough that the content check is true of every file it selects, but not so narrow that it matches nothing: an existence-only rule whose match files expression finds no files fails.
Leaving the content check empty means "any file the match files expression finds is good enough." This is useful for existence-only checks like "does this repo have unit tests."
Field references
-
$exists("glob")- Whether the current file's path matches the glob pattern. Prefer this for path matching in the match files expression. Combined across the repository, an existence-only rule (empty content check) passes when at least one file matches.$exists("**/*.{test,spec}.{ts,js}") -
$filePath- A matched file's full path (repository-relative, no leading slash).$filePath == "src/index.ts" -
$fileName- A matched file's name, without its directory.$fileName == "package.json" -
$extension- A matched file's extension, without a leading dot (ts, not.ts). Prefer$existsfor path matching; use$filePath,$fileName, or$extensionwith==or~only for conditions a glob cannot express.$extension == "ts" -
$content- A matched file's full text content. Only available where content has actually been loaded: in the content check, or in a match files expression that explicitly needs it (see the compound example below).$content ~ "^#\s"
Glob patterns
$exists uses minimatch-style globs against repository-relative POSIX paths (for example src/index.ts or README.md). Do not use a leading slash or a ./ prefix.
| Pattern | Matches | Notes |
|---|---|---|
$exists("package.json") | Only the root package.json | Anchor to the root when that is intentional |
$exists("*.md") | .md files in the repository root only | * and ? never match / |
$exists("**/*.md") | .md files at any depth, including the root | Prefer **/ when you mean "anywhere" |
$exists("**/*.{ts,tsx}") | .ts and .tsx files at any depth | Brace expansion is supported |
$exists(".github/workflows/*.{yml,yaml}") | Workflow files under .github/workflows/ | Dotfiles and dot-directories match normally |
Matching is case-sensitive: $exists("**/README.md") does not match readme.md. Use the casing from the repository, or a brace list such as $exists("**/{README,readme}.md").
Avoid character classes ([...]). A malformed class can silently become a literal pattern that matches nothing instead of reporting an error. The glob must be a non-empty string.
Examples
The examples below use the field references and glob patterns above — not only $exists and $content.
-
Has unit tests (match files only,
$existswith brace expansion)$exists("**/*.{test,spec}.{ts,tsx,js,jsx}") -
Root
package.jsononly (match files only, anchored glob)$exists("package.json") -
Lockfile present (match files only,
$fileNamewithor)$fileName == "package-lock.json" || $fileName == "yarn.lock" -
Container image definition present (match files only,
$fileName)$fileName == "Dockerfile" || $fileName == "Containerfile" -
CODEOWNERS file at repository root (match files only,
$filePath)$filePath == "CODEOWNERS" -
TypeScript sources under
src/(match files only,$extensioncombined with$exists)$extension == "ts" && $exists("src/**") -
Markdown files in
docs/($extensionand$filePathregex)Match files:
$extension == "md" && $filePath ~ "^docs/"Content check:$content ~ "^#\s" -
React 19+ in package.json (
$exists+$content)Match files:
$exists("package.json")Content check:$content ~ "\"react\"\s*:\s*\"[^\"]*19\." -
Every markdown file has a top-level heading (
$exists+$content)Match files:
$exists("**/*.md")Content check:$content ~ "^#\s" -
ESLint config present (
$existswithor)$exists("**/.eslintrc*") || $exists("**/eslint.config.*") -
GitHub Actions workflows pin action versions (directory-scoped glob +
$content)Match files:
$exists(".github/workflows/*.{yml,yaml}")Content check:$content ~ "uses:\s*\S+@" -
Banned package absent (
$exists+notand$content)Match files:
$exists("package.json")Content check:not ($content ~ "\"left-pad\"") -
Compound match files expression using file content
A match files expression can reference
$contentdirectly when the check only needs to happen once, rather than as a separate content check pass:$exists("**/opentelemetry*") || $content ~ "sentry"
Evaluation outcomes
A repository with no completed content ingestion yet, or a rule with an invalid pattern, reports UNABLE_TO_EVALUATE. If the match files expression matches no files, an existence-only rule (no content check) reports FAIL, since the rule is asserting that a matching file exists and none was found. A rule with a content check instead reports NO_MATCH, since there was nothing to check against. Check the preview panel before saving to see exactly which outcome each targeted repository would get.
Best Practices
- Clarify the goal first - Decide in plain language what should pass or fail before you write selectors or content checks, so the rule targets the right files and outcome.
- Choose the right target type - Use catalog entity rules for metadata and annotations; use repository content rules for files and file contents.
- Reuse shared operators - Prefer the same comparison and logical operators across both target types so rules stay consistent and readable.
- Clear selection - Target a specific entity kind, or narrow match files with
$existsso the validation applies only where it should. - Focused validation - Keep each criteria or content check focused on a single aspect.
- Operator precedence - Be aware of operator precedence and use parentheses when needed.
- Most specific field - Use the most specific field accessor for your needs (
$existsfor paths;$metadata.namerather than a broad$entitywalk). - Consistent annotation paths - For catalog rules, use standard annotation paths across related rules.
- Preview repository rules - Use the preview panel before saving repository content rules so you can confirm matches and outcomes.