A GitHub Action that validates examples in documentation against your action's schema. Ensures that all usage examples in your README and other documentation files reference valid inputs and outputs as defined in your action.yml files.
- ๐ Automatic Action Discovery: Finds all
action.ymlandaction.yamlfiles in your repository - ๐ Documentation Validation: Scans markdown files for YAML code blocks containing action usage examples
- ๐ Description Examples: Validates YAML examples embedded in action.yml descriptions (root and input descriptions)
- โ Input Validation: Checks that all inputs used in examples are defined in the action schema
- ๐ข Type Checking: Validates input types (boolean, number, string, choice) when specified
- ๐ฏ Value Validation: Checks that input values match allowed options when specified
- ๐ Pattern Matching: Validates string inputs against regex patterns (via schema file)
- ๐ Custom Types: Define reusable types in schema file for consistent validation
- ๐ซ Expression Handling: Skips validation for values containing GitHub Actions expressions (
${{ ... }}) - ๐ Output Validation: Ensures referenced outputs exist in the action schema
- ๐ Fork Support: Automatically detects forks and validates examples using either fork or parent repository names
- ๐ฌ Comment Support: Handles trailing
#comments in YAML examples - ๐ Multi-line Values: Supports YAML literal (
|) and folded (>) block scalars - ๐ฏ Precise Error Reporting: Reports errors with file, line, and column information using GitHub Actions workflow commands
- ๐ Schema File Support: Optional
action.schema.ymlfor advanced input/output validation
- uses: jessehouwing/actions-example-checker@v1
with:
# GitHub token for API access to check fork relationships. When provided,
# the action will detect if the repository is a fork and allow examples to
# reference either the fork name or the upstream parent name.
#
# For example, if your repository jessehouwing/azdo-marketplace is forked
# from microsoft/azure-devops-extension-tasks, examples can use either:
# - uses: jessehouwing/azdo-marketplace@v1
# - uses: microsoft/azure-devops-extension-tasks@v1
#
# Both will be validated against the same action.yml schema from your repository.
#
# Default: ${{ github.token }}
token: ''
# Repository name in 'owner/repo' format. Used to match action references
# in documentation. If not provided, will be auto-detected from git or
# GITHUB_REPOSITORY environment variable.
#
# Examples:
# - 'jessehouwing/actions-example-checker'
# - 'actions/checkout'
#
# Default: Auto-detected from git remote or GITHUB_REPOSITORY
repository: ''
# Path to the repository root. Use this when your action.yml files are not
# in the default checkout location or when you need to validate a subdirectory.
#
# Examples:
# - '.' (current directory)
# - './my-action' (subdirectory)
# - '/path/to/repo' (absolute path)
#
# Default: .
repository-path: ''
# Glob pattern to find action files in your repository. The action will search
# for all files matching this pattern and validate examples against their schemas.
#
# The default pattern finds:
# - action.yml and action.yaml in the root directory
# - action.yml and action.yaml in any subdirectory
#
# Examples:
# - '{**/,}action.{yml,yaml}' (default - root and all subdirectories)
# - 'action.yml' (only root directory)
# - '**/action.yml' (only subdirectories)
# - 'actions/**/action.{yml,yaml}' (subdirectories under 'actions' folder)
#
# Default: {**/,}action.{yml,yaml}
action-pattern: ''
# Glob pattern to find documentation files that contain YAML code blocks
# to validate. The action will scan these files for usage examples.
#
# The default pattern finds all Markdown files in the repository, but you can
# customize it to target specific directories or file types.
#
# Examples:
# - '**/*.md' (default - all Markdown files)
# - 'README.md' (only root README)
# - 'docs/**/*.md' (only files in docs directory)
# - '**/*.{md,markdown}' (Markdown files with either extension)
#
# Note: The action also validates YAML examples embedded in action.yml
# description fields, regardless of this pattern.
#
# Default: **/*.md
docs-pattern: ''For more precise validation, you can create an optional action.schema.yml or action.schema.yaml file alongside your action.yml. This schema file allows you to define:
- Type validation:
boolean,number,string, orchoice - Pattern matching: Validate string inputs against regex patterns
- Choice validation: Specify allowed values for inputs
- Custom types: Define reusable types for consistency
Create action.schema.yml next to your action.yml:
# Define reusable custom types
types:
url:
type: string
match: '^https?://.*'
semver:
type: string
match: "^\\d+\\.\\d+\\.\\d+$"
# Validate inputs
inputs:
environment:
type: choice
options:
- development
- staging
- production
version:
type: semver # Reference to custom type
api-url:
type: url # Reference to custom type
timeout:
type: number
dry-run:
type: boolean
log-level:
type: string
match: '^(debug|info|warn|error)$'
# Validate outputs
outputs:
deployment-id:
type: string
deployment-url:
type: urlAccepts truthy/falsy values:
- Truthy:
true,yes,y,1,on - Falsy:
false,no,n,0,off, `` (empty)
inputs:
enabled:
type: booleanAccepts numeric values including scientific notation:
inputs:
timeout:
type: numberValidates strings against regex patterns:
inputs:
email:
type: string
match: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"Regex patterns support both formats:
- Plain string:
"^pattern$" - With flags:
"/pattern/i"(case-insensitive)
Restricts input to specific values:
inputs:
log-level:
type: choice
options:
- debug
- info
- warning
- errorAccepts any value without validation. Use this when you want to skip type checking for an input:
inputs:
custom-data:
type: any
# Also works with multi-value inputs
flexible-list:
type: string
separators: ','
items:
type: anyValidates inputs that contain multiple values separated by delimiter(s). Each value is validated against the specified type and pattern.
inputs:
# Single separator (comma)
tags:
type: string
separators: ','
items:
type: string
match: '^[a-z0-9-]+$'
# Multiple separators (comma, semicolon, or pipe)
flexible-tags:
type: string
separators: [',', ';', '|']
items:
type: string
# Newline-separated environments
environments:
type: string
separators: newline # Can also use '\n'
items:
type: choice
options:
- development
- staging
- production
# Comma-separated port numbers
ports:
type: string
separators: ','
items:
type: number
# Combined newline AND comma separators
# Split by newline first, then by comma within each line
combined:
type: string
separators: ['newline', ',']
items:
type: stringSeparator format options:
The separators field accepts:
- String:
separators: ','- single separator (must be quoted for special YAML characters like,,[,{) - Array:
separators: [',']- single separator in array format - Multiple separators:
separators: [',', ';', '|']- splits on any of the specified separators - Combined with newline:
separators: ['newline', ',']- splits by newline first, then by comma within each line
All these are equivalent for a single comma separator:
separators: ',' # quoted string (required for comma)
separators: [','] # single-element arrayFor characters that aren't special in YAML, quotes are optional:
separators: ; # unquoted (works for semicolon)
separators: ';' # quoted (also works)Supported separators:
,- Comma (must be quoted:','or in array:[',']);- Semicolon|- Pipenewlineor\n- Newline (for multiline inputs with|or>)- Any single character
- Arrays of separators:
[',', ';', '|']- splits on any of the specified separators - Combined newline + others:
['newline', ',']- splits by newline first, then by comma within each line
Usage examples:
# Single separator (comma)
- uses: owner/action@v1
with:
tags: tag1, tag-2, tag3
# Multiple separators (comma, semicolon, or pipe)
- uses: owner/action@v1
with:
tags: tag1, tag-2; tag3| tag4
# Newline-separated (multiline)
- uses: owner/action@v1
with:
environments: |
development
staging
production
# Combined newline AND comma - splits by newline, then by comma
- uses: owner/action@v1
with:
tags: |
tag1, tag2, tag3
tag4, tag5
tag6Note: If items is specified without separators, it defaults to newline.
Before validation, values are normalized by:
- Removing leading/trailing whitespace
- Removing enclosing quotes (
"..."or'...') - Removing trailing comments (
value # comment) - Unindenting multiline values
- Collapsing multiline values to single line
- Skipping non-literal expressions (
${{ secrets.TOKEN }})
Schema files are optional. Without a schema file:
- Explicit
typefield inaction.ymlis still respected - Basic validation continues to work
- No breaking changes to existing workflows
Add this action to your CI workflow to validate documentation examples:
name: Validate Documentation
on:
pull_request:
push:
branches: [main]
jobs:
validate-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: jessehouwing/actions-example-checker@v1Validate only specific documentation:
- uses: jessehouwing/actions-example-checker@v1
with:
docs-pattern: 'docs/**/*.md'
action-pattern: 'action.yml'For forked repositories, enable automatic parent detection:
- uses: jessehouwing/actions-example-checker@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}For monorepo with multiple actions:
- uses: jessehouwing/actions-example-checker@v1
with:
action-pattern: 'actions/**/action.{yml,yaml}'
docs-pattern: 'actions/**/*.md'| Input | Description | Required | Default |
|---|---|---|---|
token |
GitHub token for API access to check fork relationships (enables fork detection) | No | ${{ github.token }} |
repository |
Repository name in 'owner/repo' format (auto-detected if not provided) | No | Auto-detected |
repository-path |
Path to the repository root (defaults to current directory) | No | . |
action-pattern |
Glob pattern to find action files | No | {**/,}action.{yml,yaml} |
docs-pattern |
Glob pattern to find documentation files | No | **/*.md |
| Output | Description |
|---|---|
errors-found |
Number of validation errors found |
files-checked |
Number of documentation files checked |
- Fork Detection (optional): If a token is provided, checks if the repository is a fork and identifies the parent repository
- Action Discovery: The action scans your repository for all
action.ymlandaction.yamlfiles using the specified pattern - Schema Loading: Each action file is parsed to extract:
- Input names, types, and allowed values
- Output names
- Required/optional status
- Alternative names (fork and parent repository names)
- If an
action.schema.ymlexists, loads advanced validation rules (types, patterns, custom types)
- Documentation Scanning:
- Scans markdown files for YAML code blocks (marked with
```yamlor```yml) - Extracts YAML code blocks from action.yml descriptions (root and input descriptions)
- Scans markdown files for YAML code blocks (marked with
- Example Validation: For each code block containing action usage (identified by
uses: owner/repo@version):- Validates that all
with:inputs are defined in the action schema - Checks input types (boolean, number, string, choice) when specified
- Validates input values against allowed options (choice type)
- Validates string inputs against regex patterns (when defined in schema)
- Validates output references (e.g.,
steps.my-step.outputs.result) exist in the action schema - Skips validation for non-literal expressions containing
${{ ... }}
- Validates that all
- Error Reporting: Reports errors using GitHub Actions workflow commands with file, line, and column information
Given an action.yml:
name: My Action
inputs:
environment:
description: 'Environment to deploy to. Options: development, staging, production'
required: true
debug:
description: 'Enable debug mode (boolean)'
required: false
timeout:
description: 'Timeout in seconds (number)'
required: false
outputs:
deployment-url:
description: 'URL of the deployment'Valid example in README.md:
- uses: owner/my-action@v1
with:
environment: production
debug: true
timeout: 300Invalid example (will be caught):
- uses: owner/my-action@v1
with:
environment: prod # Error: Not in [development, staging, production]
debug: yes # Error: Not a boolean (true/false)
unknown-input: value # Error: Input not defined in action.ymlExpressions are allowed and skipped:
- uses: owner/my-action@v1
with:
environment: ${{ inputs.env }} # OK: Expression
debug: ${{ github.event.inputs.debug }} # OK: ExpressionOutput validation example:
- uses: owner/my-action@v1
id: deploy
with:
environment: production
- run: echo "Deployed to ${{ steps.deploy.outputs.deployment-url }}" # OK: Valid output
- run: echo "${{ steps.deploy.outputs.invalid }}" # Error: Unknown output 'invalid'This action validates its own documentation! Here's a valid example:
- uses: jessehouwing/actions-example-checker@v1
with:
repository: jessehouwing/actions-example-checker
repository-path: .
action-pattern: '{**/,}action.{yml,yaml}'
docs-pattern: '**/*.md'The action validates YAML examples from multiple sources:
Examples in *.md files are validated:
# Usage
```yaml
- uses: owner/action@v1
with:
input: value
```Examples in action.yml descriptions are also validated:
name: My Action
description: |-
Example usage:
```yaml
- uses: owner/my-action@v1
with:
mode: standardinputs: auth-type: description: |- Authentication type
Setup example:
```yaml
- uses: azure/login@v2
with:
client-id: abc
- uses: owner/my-action@v1
with:
auth-type: oidc
```
This ensures that examples in your action.yml file are always correct and up-to-date.
## Advanced Syntax Support
The action supports various YAML syntax features commonly used in GitHub Actions:
### Comments
Trailing comments are supported and stripped before validation:
```yaml
- uses: owner/action@v1
id: my-step # step identifier
with:
environment: production # deployment target
debug: true # enable debugging
Comments inside quoted strings are preserved:
- uses: owner/action@v1
with:
message: 'Build #123 completed' # hash in string is preservedThe action supports YAML literal block scalars (|) and folded block scalars (>):
- uses: owner/action@v1
with:
script: |
echo "Line 1"
echo "Line 2"
echo "Line 3"
description: >
This is a long description
that will be folded into
a single lineMulti-line values are validated just like single-line values:
- Type checking applies (boolean, number)
- Option validation applies
- Expressions are still skipped
# With dash
- uses: owner/action@v1
with:
input: value
# Without dash
uses: owner/action@v1
with:
input: value
# Dash without space
-uses: owner/action@v1
with:
input: valueThe action detects input types from:
- Explicit type field:
type: booleanortype: numberin action.yml - Description hints: Keywords like "boolean" or "number" in the input description
- Options/choices: Extracts allowed values from descriptions like "Options: dev, prod"
Contributions are welcome! Please feel free to submit a Pull Request.
MIT