Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Azure Resource Templates: JSON Best Practices

If you maintain Azure Resource Manager templates in raw JSON, the most important current context is simple: Azure still deploys ARM JSON, but Microsoft recommends Bicep for authoring new infrastructure as code because it compiles to the same deployment engine. That makes JSON best practices less about clever syntax and more about keeping existing templates predictable, reviewable, and safe to deploy.

This guide focuses on the real cases where teams still touch ARM JSON directly: legacy repos, exported templates, quick production patches, published template specs, and CI/CD pipelines that already expect JSON.

Start With A Current Template Shape

Keep the template structure boring and explicit. Use the current deployment template schema, and consider languageVersion: "2.0" when you want symbolic resource names and stricter validation. Microsoft documents both the classic array-based format and the newer 2.0 object-based resource format in the ARM template syntax reference.

Example: clear top-level structure

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "languageVersion": "2.0",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "type": "string"
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    }
  },
  "variables": {},
  "resources": {
    "storageAccount": {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2025-06-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "StorageV2"
    }
  },
  "outputs": {
    "storageAccountName": {
      "type": "string",
      "value": "[parameters('storageAccountName')]"
    }
  }
}

If your team already uses the classic resources: [] format, do not rewrite stable templates just for style. Adopt languageVersion: "2.0" deliberately when it makes the dependency graph easier to read or new work benefits from symbolic names.

Treat Parameters As Contracts

Parameters are the public interface of the template. They should tell a reviewer exactly what can change, what must stay constrained, and what must never be logged.

Use strong types and validation

Use the correct parameter type, add allowedValues when the choice set is small, and define length or numeric limits where Azure naming or SKU rules require them.

Document inputs in metadata

Put human-readable descriptions in metadata.description. That keeps JSON valid while still making the template understandable in editors and reviews.

Keep secrets out of defaults and outputs

Use securestring and secureObject for sensitive data, pass them through secure parameter files or pipeline secrets, and avoid default values for credentials or keys.

Parameter example

"parameters": {
  "storageAccountName": {
    "type": "string",
    "minLength": 3,
    "maxLength": 24,
    "metadata": {
      "description": "Globally unique storage account name."
    }
  },
  "environment": {
    "type": "string",
    "allowedValues": ["dev", "test", "prod"],
    "metadata": {
      "description": "Environment name used in tags and naming."
    }
  },
  "location": {
    "type": "string",
    "defaultValue": "[resourceGroup().location]",
    "metadata": {
      "description": "Deployment region. Defaults to the resource group's location."
    }
  },
  "adminPassword": {
    "type": "securestring",
    "metadata": {
      "description": "Only provide this at deployment time or through a secure parameter file."
    }
  }
}

Keep API Versions Predictable

Every resource needs an apiVersion, and this is one of the most common sources of subtle drift. Use a recent stable version that you have actually tested, pin it in the template, and update it intentionally instead of parameterizing it.

  • Prefer stable API versions over preview versions for production templates.
  • Do not make apiVersion a parameter or variable just to look flexible.
  • Keep the same resource type on the same version unless you have a clear reason to split versions.

Microsoft calls this out in its ARM template best practices: stable, explicit versions are easier to review and safer to keep in CI.

Prefer Clear Dependency Graphs

ARM deploys resources in parallel whenever it can. That is good for speed, but it means your template should describe dependencies clearly enough that the deployment engine and the next human reviewer reach the same conclusion.

Use implicit dependencies when they are obvious

References created through symbolic names, reference(), and resourceId() often give ARM enough information to order resources correctly.

Add dependsOn when it improves reviewability

Use explicit dependsOn when nested resources, loops, or cross-resource interactions make the order hard to infer at a glance. Do not add unnecessary dependencies everywhere, because that slows deployments and hides the real graph.

Reuse Without Copy-Paste

If you repeat whole JSON blocks, you are making the template harder to test and easier to break. Use iteration, modularization, or published specs instead.

Use copy loops for repeated resources

When you need several resources or child items with the same shape, use copy rather than manual duplication.

Split large deployments into modules

Use linked or nested templates when the deployment has clear boundaries such as network, compute, and data layers.

Use Template Specs for approved shared building blocks

Template Specs are useful when you want versioned, centrally managed templates inside Azure without copying files between repos or pipelines.

Use Comments And Metadata Correctly

A lot of ARM JSON advice on the web is stale here. Current Azure guidance is not just "JSON has no comments, good luck."

  • ARM templates support a comments element when you want inline explanation attached to a template item.
  • During authoring, you can keep files as .jsonc and use // comments for working notes.
  • Use metadata.description for parameter and output documentation that should stay with the contract.
  • Avoid inventing fake properties such as _comments just to annotate the file.

That combination is cleaner than polluting the deployment model with custom fields that other tooling does not expect.

Validate Before You Deploy

Syntax validation is not enough. Good ARM JSON workflows check what the deployment engine plans to change before resources are touched.

At minimum, use editor support such as ARM Tools and add automated checks with the ARM Template Test Toolkit or equivalent CI validation.

Run What-If in CI or before manual changes

The What-If operation is the fastest way to catch unintended updates, renames, or deletes.

az deployment group what-if \
  --resource-group my-resource-group \
  --template-file mainTemplate.json \
  --parameters @main.parameters.json

Lint and test exported templates before trusting them

Exported templates from the portal are starting points, not finished infrastructure design. Clean them up, remove noise, and validate them with ARM tooling before they become your source of truth.

Keep a non-production deployment target

Use a dedicated test resource group or subscription so refactors can be proven outside production, especially when you are changing API versions, conditions, or copy loops.

Choose Safer Deployment Behavior

Microsoft's current guidance is to use incremental deployments for normal ARM workflows. Complete mode is not recommended and is being gradually deprecated in favor of deployment stacks when you need managed deletions.

  • Use incremental mode by default.
  • When a resource property is authoritative, redeploy the full intended state for that property.
  • Do not rely on complete mode as your cleanup strategy for long-lived environments.

This matters for resources whose child configuration is declared on the parent resource. If you only submit a partial property set, ARM can reset unspecified values back to defaults.

Know When To Stay In JSON

Raw ARM JSON still makes sense when:

  • You are maintaining an existing production template that already works.
  • You consume generated or exported templates and only need small controlled edits.
  • You publish or consume JSON-based artifacts through a workflow that is already standardized.

For new authoring, frequent refactoring, or reusable modules, Bicep is usually the better authoring layer. If you inherit a large JSON template, Microsoft also documents how to decompile ARM JSON to Bicep as a migration starting point.

Conclusion

The best ARM JSON practice in 2026 is not to write more complicated JSON. It is to keep templates explicit, validate them with What-If, pin tested API versions, document parameters properly, avoid unsafe deployment modes, and move to Bicep for new authoring when you control the workflow.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool