Overview
YAML plugins provide a simple, declarative way to extend azqr with custom Azure Resource Graph (ARG) queries without writing Go code. This is ideal for:
- Quick custom checks and validations
- Organization-specific compliance rules
- Temporary or experimental recommendations
- Non-developers who want to extend azqr
Plugin Structure
A YAML plugin consists of a single .yaml file containing plugin metadata and one or more Azure Resource Graph queries.
Basic Structure
name: plugin-name
version: 1.0.0
description: Brief description of what this plugin does
author: Plugin Author Name (optional)
license: MIT (optional)
queries:
- aprlGuid: unique-id-001
description: Short description of the recommendation
longDescription: |
Detailed description of the issue and why it matters.
Can be multiple lines.
recommendationControl: Category Name
recommendationImpact: Impact Level
recommendationResourceType: Microsoft.Service/resourceType
learnMoreLink:
- name: Documentation Title
url: https://learn.microsoft.com/...
query: |
resources
| where type =~ 'microsoft.service/resourcetype'
| where some_condition == true
| project id, name, resourceGroup, location
Required Fields
Plugin Level
- name: Unique plugin identifier (string)
- version: Semantic version (e.g., “1.0.0”)
- description: What the plugin does
Query Level
- aprlGuid: Unique identifier for the recommendation
- description: Short recommendation title
- recommendationControl: Category (see Categories section)
- recommendationImpact: Impact level (High, Medium, Low)
- recommendationResourceType: Azure resource type (e.g., “Microsoft.Storage/storageAccounts”)
- query OR queryFile: The KQL query to execute
Optional Fields
Plugin Level
- author: Plugin author name
- license: License type (e.g., MIT, Apache-2.0)
Query Level
- longDescription: Detailed explanation
- learnMoreLink: Array of documentation links
- recommendationTypeId: Azure Policy or APRL recommendation ID
- recommendationMetadataState: State (Active, Deprecated)
- potentialBenefits: Benefits of implementing the recommendation
- pgVerified: Whether verified by product group (boolean)
- automationAvailable: Whether automation is available (boolean)
- tags: Array of tags for categorization
Categories
Valid values for recommendationControl:
- High Availability: Availability and redundancy
- Security: Security and access control
- Disaster Recovery: Backup and recovery
- Scalability: Scaling and performance
- Governance: Compliance and governance
- Monitoring and Alerting: Observability
- Business Continuity: Business continuity planning
- Service Upgrade and Retirement: Service lifecycle
- Other Best Practices: General best practices (default)
Impact Levels
Valid values for recommendationImpact:
- High: Critical issues that should be addressed immediately
- Medium: Important issues that should be addressed soon (default)
- Low: Nice-to-have improvements
Query Format
Inline Query
Include the KQL query directly in the YAML file:
queries:
- aprlGuid: example-001
description: Example recommendation
recommendationControl: Security
recommendationImpact: High
recommendationResourceType: Microsoft.Storage/storageAccounts
query: |
resources
| where type =~ 'microsoft.storage/storageaccounts'
| where properties.supportsHttpsTrafficOnly == false
| project id, name, resourceGroup, location
External Query File
Reference an external .kql file:
queries:
- aprlGuid: example-002
description: Example with external query
recommendationControl: Governance
recommendationImpact: Medium
recommendationResourceType: Microsoft.Network/publicIPAddresses
queryFile: ./kql/unused-public-ips.kql
The path is relative to the YAML file location. Create a kql/ subdirectory next to your plugin YAML file.
Query Requirements
Your Azure Resource Graph queries must:
Return resources: Query the
resourcestableProject required fields: Include at minimum:
id: Resource IDname: Resource nameresourceGroup: Resource group namelocation: Azure region
Filter appropriately: Use
whereclauses to identify non-compliant resourcesUse case-insensitive comparisons: Use
=~instead of==for type comparisons
Query Example
resources
| where type =~ 'microsoft.network/networkinterfaces'
| where properties.virtualMachine == "" or isnull(properties.virtualMachine)
| where properties.privateEndpoint == "" or isnull(properties.privateEndpoint)
| project id, name, resourceGroup, location,
tags,
sku = properties.ipConfigurations[0].properties.privateIPAllocationMethod
Plugin Discovery
YAML plugins are discovered from the following locations:
- Current directory:
./plugins/*.yaml - User plugins directory:
~/.azqr/plugins/*.yaml - System plugins directory:
/etc/azqr/plugins/*.yaml(Linux/macOS)
azqr searches recursively in these directories for any .yaml or .yml files.
Complete Example
Here’s a complete example plugin (custom-checks.yaml):
name: example-custom-checks
version: 1.0.0
description: Example YAML plugin with custom Azure Resource Graph queries
author: Azure Quick Review Team
license: MIT
queries:
# Check for unused network interfaces
- description: Network interfaces not attached to any VM
aprlGuid: yaml-001-unused-nics
recommendationTypeId: null
recommendationControl: Governance
recommendationImpact: Low
recommendationResourceType: Microsoft.Network/networkInterfaces
recommendationMetadataState: Active
longDescription: |
Network interfaces that are not attached to any virtual machine.
These resources incur costs and should be reviewed for cleanup.
potentialBenefits: Cost optimization and resource cleanup
pgVerified: false
automationAvailable: false
tags:
- cost-optimization
- cleanup
learnMoreLink:
- name: Network Interface Overview
url: "https://learn.microsoft.com/azure/virtual-network/virtual-network-network-interface"
query: |
resources
| where type =~ 'Microsoft.Network/networkInterfaces'
| where properties.virtualMachine == "" or isnull(properties.virtualMachine)
| where properties.privateEndpoint == "" or isnull(properties.privateEndpoint)
| project id, name, resourceGroup, location, tags
# Check for unused public IPs (from external file)
- description: Public IP addresses not associated with any resource
aprlGuid: yaml-002-unused-public-ips
recommendationControl: Governance
recommendationImpact: Medium
recommendationResourceType: Microsoft.Network/publicIPAddresses
longDescription: |
Public IP addresses that are not associated with any Azure resource.
These IPs cost money even when not in use.
learnMoreLink:
- name: Public IP Addresses
url: "https://learn.microsoft.com/azure/virtual-network/ip-services/public-ip-addresses"
queryFile: kql/unused-public-ips.kql
# Security check
- description: Storage accounts without secure transfer enabled
aprlGuid: yaml-003-storage-secure-transfer
recommendationControl: Security
recommendationImpact: High
recommendationResourceType: Microsoft.Storage/storageAccounts
longDescription: |
Storage accounts that do not have secure transfer (HTTPS) required.
This is a security risk as data can be transmitted over insecure connections.
potentialBenefits: Improved security and data protection
learnMoreLink:
- name: Require secure transfer
url: "https://learn.microsoft.com/azure/storage/common/storage-require-secure-transfer"
query: |
resources
| where type =~ 'Microsoft.Storage/storageAccounts'
| where properties.supportsHttpsTrafficOnly == false
| project id, name, resourceGroup, location,
sku = sku.name,
tier = sku.tier
Usage
Once you’ve created your YAML plugin:
Place the file in one of the plugin directories
Run azqr scan as normal:
azqr scanView plugin info:
azqr plugins list azqr plugins info <plugin-name>
The recommendations from your YAML plugin will be included in all outputs (Excel, CSV, JSON) alongside built-in recommendations.
Best Practices
1. Use Descriptive Names
name: org-security-checks
description: Organization-specific security compliance checks
2. Group Related Checks
Put related recommendations in the same plugin file:
name: network-optimization
queries:
- aprlGuid: net-001-unused-nics
description: Unused network interfaces
...
- aprlGuid: net-002-unused-ips
description: Unused public IPs
...
3. Version Your Plugins
Follow semantic versioning:
- 1.0.0: Initial release
- 1.1.0: Add new queries
- 2.0.0: Breaking changes
4. Provide Learn More Links
Always include documentation links:
learnMoreLink:
- name: Official Documentation
url: https://learn.microsoft.com/...
- name: Best Practices Guide
url: https://learn.microsoft.com/...
5. Test Your Queries
Test queries in Azure Resource Graph Explorer first:
6. Use External Files for Complex Queries
For queries over ~10 lines, use external .kql files:
my-plugin/
├── custom-checks.yaml
└── kql/
├── query1.kql
├── query2.kql
└── query3.kql
7. Document Your Plugin
Include comprehensive descriptions:
longDescription: |
This check identifies resources that...
Why it matters:
- Cost implications
- Security risks
- Performance impact
How to fix:
1. Step one
2. Step two
Troubleshooting
Plugin Not Discovered
- Check the file extension (
.yamlor.yml) - Verify the file is in a plugin directory
- Run with debug logging:
azqr scan --debug
Query Errors
- Syntax errors: Test the query in Azure Resource Graph Explorer
- No results: Verify the resource type filter
- Permission errors: Ensure you have Reader access to subscriptions
Invalid YAML
Use a YAML validator to check syntax:
yamllint custom-checks.yaml
Limitations
- Query-based only: YAML plugins can only use Azure Resource Graph queries, not ARM API calls
- Subscription scope: Queries run within subscription context
- No custom logic: Cannot include complex evaluation logic (use built-in plugins for that)
Migration from Graph Queries
If you have existing ARG queries, convert them to YAML plugins:
Before (separate .kql files):
resources
| where type =~ 'microsoft.storage/storageaccounts'
| where properties.supportsHttpsTrafficOnly == false
After (YAML plugin):
name: my-checks
version: 1.0.0
description: My custom checks
queries:
- aprlGuid: check-001
description: Storage accounts without HTTPS
recommendationControl: Security
recommendationImpact: High
recommendationResourceType: Microsoft.Storage/storageAccounts
query: |
resources
| where type =~ 'microsoft.storage/storageaccounts'
| where properties.supportsHttpsTrafficOnly == false
| project id, name, resourceGroup, location