bicep-modules
activeGenerates Azure Bicep module files following established project conventions, with secure-by-default and cost-optimised configurations. Reads per-resource .md reference files for security baselines, cost guidelines, and code patterns. Use when creating a new Bicep module, adding a resource to an existing module, writing a role assignment module, or reviewing Bicep for convention violations. Validates parameter ordering, decorator patterns, security defaults (RBAC, soft delete, TLS 1.2, private endpoints), cost-appropriate SKU defaults, idiomatic output ordering, and code patterns (role GUIDs, conditional resources, private endpoints, parent-child relationships, null-safe access). Do NOT use for Terraform, ARM JSON, or Pulumi authoring.
Quality Score Breakdown
Show checks (11)
- ✓ SKILL.md exists with exact casing 3/3
- ✓ Valid YAML frontmatter 3/3
- ✓ No unexpected frontmatter keys 1/1
- ✓ Name field valid (kebab-case) 2/2
- ✓ Name matches folder name 1/1
- ✓ Description field present 2/2
- ✓ No angle brackets in frontmatter 1/1
- ✓ Folder name is kebab-case 1/1
- ✓ No README.md inside skill folder 1/1
- ✓ Test directory with test-cases.yml exists 2/2
- ✓ Status 'active' is valid 1/1
Show checks (7)
- ✓ Contains action verbs: generates, validates 4/4
- ✓ Contains trigger indicators: use when, use for 5/5
- ✓ Description is specific and actionable 4/4
- ✓ File types mentioned in description 3/3
- ✓ Description length: 746/1024 chars 2/2
- ✓ Has negative triggers (scope boundaries) 2/2
- ✓ Owner/author specified in metadata 2/2
Show checks (8)
- ✓ Skill body has content 3/3
- ✓ Has step/section structure 4/4
- ✓ Includes examples 5/5
- ✓ Includes error handling 4/4
- ✓ Uses progressive disclosure (references/scripts) 4/4
- ✓ Actionable language: 4/10 verb patterns found 3/3
- ✓ Word count: 953/5000 2/2
- ✓ All referenced paths exist 3/3
Show checks (10)
- ✓ test-cases.yml exists and parses 3/3
- ✓ 6 should-trigger tests ✓ 4/4
- ✓ 4 should-not-trigger tests ✓ 3/3
- ✓ 7 functional tests ✓ 5/5
- ✓ 2 negative tests ✓ 3/3
- ✓ 2 edge case tests ✓ 3/3
- ✓ Performance baseline documented 2/2
- ✓ All functional tests have ≥2 assertions 2/2
- ✓ All trigger phrases are diverse 2/2
- ✓ All assertions are specific 2/2
Show checks (5)
- ✓ No secrets detected 5/5
- ✓ No injection vectors in frontmatter 3/3
- ✓ Name is not reserved 3/3
- ✓ No suspicious code patterns 2/2
- ✓ External URLs: 0 2/2
Test Coverage
Bicep Modules
Workflow
Always follow this order for every module task:
- Identify resource type — determine the Azure resource being authored or modified
- Read security baselines — open
references/security-baselines.mdand locate the section for this resource type; apply all defaults - Read cost guidelines — if SKU or tier selection is needed, open
references/cost-guidelines.mdand apply the appropriate default with upgrade path documented in the parameter description - Read conventions — open
references/conventions.mdfor role assignments, private endpoints, network rule construction, App Config key-values, or any complex structural pattern - Produce the module — write the complete Bicep file
- Self-check — verify against the checklist at the bottom of this file before returning output
File and Naming Rules
| Rule | Convention |
|---|---|
| File names | camelCase: keyVault.bicep, containerApp.bicep, blobStorage.bicep |
| Role assignment files | <resource>RoleAssignment.bicep (e.g., keyVaultRoleAssignment.bicep) |
| All identifiers | camelCase — resources, variables, parameters, outputs |
| API versions | Pinned to a specific date (e.g., @2023-07-01); stable preferred; preview acceptable when no stable alternative exists (add a comment explaining why) |
| Target scope | No targetScope declaration — all modules use implicit resource group scope |
Parameter Block Rules
Ordering
name (or primary identifier)
location
[required resource-specific params]
[optional resource-specific params]
tags object = {}
nameis first unless the primary identifier has a resource-specific name (e.g.,appName,storeName,keyVaultName) — use that insteadlocation string = resourceGroup().location— always included, placed immediately after the name/identifier grouptags object = {}— always last, no exceptions
Decorator Order
Apply decorators in this order (omit those that don’t apply):
@description('...')— always present, every parameter@metadata({ example: ... })— for complex array/object parameters only, placed after@description()@allowed([...])@minLength()/@maxLength()@minValue()/@maxValue()@secure()— last decorator, applies only tostringparameters (neverobject)
Examples
@description('Name of the storage account (3–24 lowercase alphanumeric characters only)')
@minLength(3)
@maxLength(24)
param name string
@description('Administrator password')
@secure()
param administratorPassword string
@description('SKU tier for the search service')
@allowed([
'basic'
'standard'
'standard2'
])
param sku string = 'basic'
@description('Database names to create')
@metadata({ example: ['app', 'analytics'] })
param databaseNames array = ['app']
Code Pattern Quick Reference
| Pattern | Form |
|---|---|
| Conditional resource | resource x '...' = if (condition) { ... } |
| Private endpoint flag | var usePrivateEndpoint = !empty(privateEndpointSubnetId) |
| Extract name from ID | last(split(resourceId, '/'))! |
| Null-safe field access | obj.?field ?? defaultValue |
| Parent-child resource | parent: parentResource — never string concatenation |
| Existing resource reference | resource x '...' existing = { name: name } |
| Loop output | [for (item, i) in items: resources[i].property] |
| Purge protection (never false) | enablePurgeProtection: param ? true : null |
| Network rules from arrays | [for subnetId in allowedSubnetIds: { id: subnetId, ignoreMissingVnetServiceEndpoint: false }] |
Full pattern implementations with context: see references/conventions.md.
Output Block Rules
- Every output has
@description()— no exceptions - Order:
id→name→ resource-specific outputs (endpoint, fqdn, principalId, etc.) - Child resources (e.g.,
keyVaultSecret,appConfigKeyValue): no independent.id— start withname - Role assignment modules: NO outputs at all
@description('The resource ID of the Key Vault')
output id string = keyVault.id
@description('The name of the Key Vault')
output name string = keyVault.name
@description('The URI of the Key Vault')
output vaultUri string = keyVault.properties.vaultUri
Common Issues and Troubleshooting
| Issue | Resolution |
|---|---|
enablePurgeProtection: false rejected by ARM | Use param ? true : null — ARM rejects false after the feature is enabled; omit the property to leave it unchanged |
| Conditional resource compile error | Both the conditional resource and any parent: child resources must share the same if (condition) guard |
last(split(...)) type error | Append ! non-null assertion: last(split(resourceId, '/'))! |
| Role assignment fails with duplicate name | The guid() call must include roleName as a third argument — omitting it causes collisions when assigning multiple roles to the same principal |
| Private DNS zone group depends-on error | DNS zone group must use parent: privateEndpoint (not name: concat) and be guarded by the same if (usePrivateEndpoint) condition |
| Preview API version with no stable alternative | Add an inline comment: // No stable API version available as of <date> |
Self-Check Checklist
Before returning any module, verify:
-
@description()on every parameter and every output -
name/ primary identifier is first parameter;tagsis last - Decorator order:
@description→@metadata→@allowed→@minLength/@maxLength→@minValue/@maxValue→@secure - Security defaults applied per
references/security-baselines.mdfor this resource type - SKU/tier defaults are cost-appropriate per
references/cost-guidelines.md; upgrade path documented in parameter description - API version is pinned to a specific date; stable preferred; preview has explanatory comment
- No
targetScopedeclaration - First output is
id, second isname— unless child resource (start withname) or role assignment (no outputs) -
enablePurgeProtectionusesparam ? true : nullpattern — neverfalse - Conditional resources use
= if (condition)pattern - Private endpoint uses
var usePrivateEndpoint = !empty(...)flag - Parent-child resources use
parent:— never name string concatenation -
@secure()only onstringparameters, neverobject - Role assignment modules:
principalType: 'ServicePrincipal'hardcoded; no outputs - App Config key-values: key format
App:Component:Setting(PascalCase); secrets via KV reference