Extension Framework
June 24, 2026 · View on GitHub
Table of Contents
Related Guides
| Guide | Description |
|---|---|
| Extension SDK Reference | Complete API reference for azdext SDK helpers (command scaffolding, MCP builder, security policy, service-target base). |
| Extension Migration Guide | Before/after cookbook for migrating from pre-#6856 patterns to SDK helpers. |
| Extension End-to-End Walkthrough | Build a complete extension from scratch with root command, MCP server, lifecycle events, and security. |
| Extension Framework Services | Custom language/framework support via FrameworkServiceProvider. |
| Extension Style Guide | Design guidelines for command integration, flags, and discoverability. |
| Extension Resolution and Versioning | How azd resolves extensions from sources, version constraint syntax, caching, and semver guidance. |
Getting Started
azd extensions are currently a beta feature (Public Preview) within azd.
- Official extensions must be developed in a fork of the azure/azure-dev github repo.
- Extension binaries are shipped as Github releases to the same repo through our official pipelines.
Managing Extensions
Extension Sources
Extension sources are file or URL based manifests that provide a registry of available azd extensions.
Users can add custom extension sources that connect to private, local, or public registries.
Extension sources are an equivalent concept to Nuget or NPM feeds.
Extension registries must adhere to the official extension registry schema.
Official Registry
The official extension source registry is pre-configured in azd and is hosted at https://aka.ms/azd/extensions/registry.
The registry is hosted in the azd github repo.
If you previously removed it and want to add it back:
azd extension source add -n azd -t url -l "https://aka.ms/azd/extensions/registry"
Note: When the
registry.jsonfile is modified, CI automatically runs snapshot tests to ensure extension commands are properly documented in CLI help output and VS Code IntelliSense. See Snapshot Testing for Extensions for details.
Dev (Experimental) Registry
Caution
Extensions hosted in the dev registry are unsigned. They come with no stability guarantees and are not covered by Azure support. Expect breaking changes, rough edges, and possible removal without notice.
A shared development registry can be added to your azd configuration.
This registry contains experimental extensions, community contributions not yet vetted, pre-release builds, and extensions used for internal testing before shipping to the official registry.
The registry is hosted in the azd GitHub repo.
To opt-in for the development registry run the following command:
# Add a new extension source name 'dev' to your `azd` configuration.
azd extension source add -n dev -t url -l "https://aka.ms/azd/extensions/registry/dev"
Extensions installed from the dev registry are automatically promoted to the main registry when a newer version becomes available there. See the Dev/Experimental Extension Registry section for full details on stability expectations, submission guidelines, promotion behavior, and troubleshooting.
A separate nightly registry distributes always-latest, automatically built snapshots of first-party extensions (signed on Windows/macOS, built from main). To opt in:
# Add a new extension source name 'nightly' to your `azd` configuration.
azd extension source add -n nightly -t url -l "https://raw.githubusercontent.com/Azure/azure-dev/nightly/cli/azd/extensions/registry.nightly.json"
See the Nightly Extension Registry section for version semantics, promotion behavior, and caveats.
azd extension source list
Displays a list of installed extension sources.
azd extension source add [flags]
Adds a new named extension source to the global azd configuration.
-l, --locationThe location of the extension source.-n, --nameThe name of the extension source.-t, --typeThe type of extension source. Supported types arefileandurl.
azd extension source remove <name>
Removes an extension source with the specified named argument
Extension Management
Extensions are a collection of executable artifacts that extend or enhance functionality within azd.
azd extension list [flags]
Lists matching extensions from one or more extension sources.
--installedWhen set displays a list of installed extensions.--sourceWhen set will only list extensions from the specified source.--tagsAllows filtering extensions by tags (e.g., AI, test)
azd extension show <extension-id> [flags]
Shows detailed information for a specific extension, including description, tags, versions, and installation status.
-s, --sourceThe extension source to use. Use this flag when the same extension ID exists in multiple sources.
azd extension install <extension-ids> [flags]
Installs one or more extensions from any configured extension source.
-v, --versionSpecifies the exact version to install.-s, --sourceSpecifies the extension source used for installations.
azd extension uninstall <extension-ids> [flags]
Uninstalls one or more previously installed extensions.
--allRemoves all installed extensions when specified.
azd extension upgrade <extension-ids>
Upgrades one or more extensions to the latest versions.
--allUpgrades all previously installed extensions when specified.-v, --versionUpgrades a specified extension to an exact version, if provided.-s, --sourceSpecifies the extension source used for installations.--no-dependency-upgradesSkips upgrading dependencies declared by extension packs.
Developing Extensions
The following guide will help you develop and ship extensions for azd.
Prerequisites
- Create a fork of the
azure/azure-devrepo for extension development. - Clone your forked repo or open in a codespace.
- Navigate to the
cli/azd/extensionsdirectory in your favorite terminal. - Install the
azdDeveloper Extension
Capabilities
Extensions can provide different types of capabilities:
Event Handlers
Extensions can register handlers for project and service lifecycle events (e.g., preprovision, prepackage, predeploy). These handlers execute custom logic at specific points in the azd workflow. The ExtensionHost is the preferred way to wire handlers and manage the long-lived connection.
Example:
host := azdext.NewExtensionHost(azdClient).
WithProjectEventHandler(
"preprovision",
func(ctx context.Context, args *azdext.ProjectEventArgs) error {
// Custom logic before provisioning
return nil
},
).
WithServiceEventHandler(
"prepackage",
func(ctx context.Context, args *azdext.ServiceEventArgs) error {
// Custom packaging logic
return nil
},
nil,
)
if err := host.Run(ctx); err != nil {
return fmt.Errorf("failed to run extension: %w", err)
}
Service Target Providers
Extensions can implement custom service targets that handle the full deployment lifecycle (package, publish, deploy) for specialized Azure services or custom deployment patterns. ExtensionHost handles registration and readiness by default.
Recommended:
// Create a service target provider and register it using the extension host
provider := project.NewCustomServiceTargetProvider(azdClient)
host := azdext.NewExtensionHost(azdClient).
WithServiceTarget("customtype", provider)
// Run blocks until the azd server shuts down the connection
if err := host.Run(ctx); err != nil {
return fmt.Errorf("failed to run extension: %w", err)
}
A service target provider must implement the azdext.ServiceTargetProvider interface:
type ServiceTargetProvider interface {
Initialize(ctx context.Context, serviceConfig *ServiceConfig) error
GetTargetResource(ctx context.Context, subscriptionId string, serviceConfig *ServiceConfig) (*TargetResource, error)
Package(ctx context.Context, serviceConfig *ServiceConfig, frameworkPackage *ServicePackageResult, progress ProgressReporter) (*ServicePackageResult, error)
Publish(ctx context.Context, serviceConfig *ServiceConfig, servicePackage *ServicePackageResult, targetResource *TargetResource, progress ProgressReporter) (*ServicePublishResult, error)
Deploy(ctx context.Context, serviceConfig *ServiceConfig, servicePackage *ServicePackageResult, servicePublish *ServicePublishResult, targetResource *TargetResource, progress ProgressReporter) (*ServiceDeployResult, error)
Endpoints(ctx context.Context, serviceConfig *ServiceConfig, targetResource *TargetResource) ([]string, error)
}
Metadata
Extensions with the metadata capability provide comprehensive metadata about their commands and configuration schemas. This enables:
- CLI Help Integration: Rich help text and usage information for extension commands
- IntelliSense Support: IDE autocompletion for extension flags and arguments
- Configuration Validation: JSON Schema-based validation for extension configuration
- Environment Variable Documentation: Clear documentation of environment variables used by the extension
When an extension with the metadata capability is installed, azd automatically invokes the extension's metadata command to fetch and cache command metadata.
Implementing Metadata Support:
Step 1: Add the capability to your extension.yaml:
capabilities:
- custom-commands
- metadata
Step 2: Create a hidden metadata command that outputs JSON to stdout:
func newMetadataCommand() *cobra.Command {
return &cobra.Command{
Use: "metadata",
Short: "Generate extension metadata",
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
rootCmd := cmd.Root()
// Use the helper to generate metadata from Cobra commands
metadata := azdext.GenerateExtensionMetadata(
"1.0", // schema version
"my.extension", // extension id (must match extension.yaml)
rootCmd,
)
// Add configuration schemas (optional)
metadata.Configuration = generateConfigurationMetadata()
jsonBytes, err := json.MarshalIndent(metadata, "", " ")
if err != nil {
return err
}
fmt.Println(string(jsonBytes))
return nil
},
}
}
Step 3: Define configuration schemas using Go types with JSON schema tags:
import (
"github.com/azure/azure-dev/cli/azd/pkg/extensions"
"github.com/invopop/jsonschema"
)
// Define configuration structures using Go types
type GlobalConfig struct {
APIKey string `json:"apiKey" jsonschema:"required,description=API key,minLength=10"`
Timeout int `json:"timeout" jsonschema:"minimum=1,maximum=300,default=60"`
Debug bool `json:"debug" jsonschema:"description=Enable debug logging"`
}
type ProjectConfig struct {
ProjectName string `json:"projectName" jsonschema:"required,description=Project name"`
Environment string `json:"environment" jsonschema:"enum=dev,enum=staging,enum=prod"`
Features []string `json:"features" jsonschema:"description=Enabled features"`
}
type ServiceConfig struct {
Port int `json:"port" jsonschema:"required,minimum=1,maximum=65535"`
HostName string `json:"hostName" jsonschema:"description=Service hostname"`
Labels map[string]string `json:"labels" jsonschema:"description=Service labels"`
}
func generateConfigurationMetadata() *extensions.ConfigurationMetadata {
return &extensions.ConfigurationMetadata{
// Generate schemas automatically from Go types
Global: jsonschema.Reflect(&GlobalConfig{}),
Project: jsonschema.Reflect(&ProjectConfig{}),
Service: jsonschema.Reflect(&ServiceConfig{}),
// Document environment variables
EnvironmentVariables: []extensions.EnvironmentVariable{
{
Name: "MY_EXT_API_KEY",
Description: "API key for service integration",
Example: "sk-abc123xyz456",
},
{
Name: "MY_EXT_LOG_LEVEL",
Description: "Logging verbosity level",
Default: "info",
Example: "debug",
},
},
}
}
Metadata Schema Structure:
The metadata JSON includes:
schemaVersion: Version of the metadata schema (e.g., "1.0")id: Extension identifier (must matchextension.yaml)commands: Array of command definitions with:name: Command path as array (e.g.,["demo", "context"])short: Brief one-line descriptionlong: Detailed description (markdown supported)usage: Usage template stringexamples: Example usages with description and commandargs: Positional arguments with name, description, required flagflags: Command flags with name, shorthand, type, default, validValuessubcommands: Nested subcommand definitionshidden,aliases,deprecated: Optional command metadata
configuration: Optional configuration schemas:global: JSON Schema for global-level configurationproject: JSON Schema for project-level configurationservice: JSON Schema for service-level configurationenvironmentVariables: Array of environment variable documentation
Pre-packaged Metadata:
Extensions can include a pre-packaged metadata.json file in their distribution. If present in the extension directory, azd uses it directly instead of invoking the metadata command. This is useful for:
- Faster installation (no need to run the extension)
- Extensions in languages that have slower startup times
- Ensuring consistent metadata across installations
Complete Extension Host Builder Pattern
The ExtensionHost provides a fluent builder API that allows you to register all extension capabilities in a single, unified pattern. This is the recommended approach for extension development as it handles all service registration, readiness signaling, and lifecycle management automatically.
Complete Example - Registering All Extension Capabilities:
func newListenCommand() *cobra.Command {
return &cobra.Command{
Use: "listen",
Short: "Starts the extension and listens for events.",
RunE: func(cmd *cobra.Command, args []string) error {
// Create a new context that includes the azd access token.
ctx := azdext.WithAccessToken(cmd.Context())
// Create a new azd client.
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()
// Register all extension capabilities using the builder pattern
host := azdext.NewExtensionHost(azdClient).
// Register a custom service target for specialized deployments
WithServiceTarget("demo", func() azdext.ServiceTargetProvider {
return project.NewDemoServiceTargetProvider(azdClient)
}).
// Register a custom framework service for language support
WithFrameworkService("rust", func() azdext.FrameworkServiceProvider {
return project.NewDemoFrameworkServiceProvider(azdClient)
}).
// Register project-level event handlers
WithProjectEventHandler("preprovision", func(ctx context.Context, args *azdext.ProjectEventArgs) error {
fmt.Printf("Preparing provisioning for project: %s\n", args.Project.Name)
// Perform pre-provisioning logic
return nil
}).
WithProjectEventHandler("predeploy", func(ctx context.Context, args *azdext.ProjectEventArgs) error {
fmt.Printf("Preparing deployment for project: %s\n", args.Project.Name)
// Perform pre-deployment validation
return nil
}).
WithProjectEventHandler("postdeploy", func(ctx context.Context, args *azdext.ProjectEventArgs) error {
fmt.Printf("Deployment completed for project: %s\n", args.Project.Name)
// Perform post-deployment tasks (e.g., health checks, notifications)
return nil
}).
// Register service-level event handlers with optional filtering
WithServiceEventHandler("prepackage", func(ctx context.Context, args *azdext.ServiceEventArgs) error {
fmt.Printf("Packaging service: %s\n", args.Service.Name)
// Access artifacts from previous phases
if len(args.ServiceContext.Build) > 0 {
fmt.Printf("Found %d build artifacts\n", len(args.ServiceContext.Build))
}
return nil
}, nil). // No filtering - applies to all services
WithServiceEventHandler("postpackage", func(ctx context.Context, args *azdext.ServiceEventArgs) error {
fmt.Printf("Package completed for service: %s\n", args.Service.Name)
// Check package artifacts
for _, artifact := range args.ServiceContext.Package {
fmt.Printf("Package artifact: %s\n", artifact.Path)
}
return nil
}, &azdext.ServiceEventOptions{
// Optional: Filter to only handle specific service types
Host: "containerapp",
Language: "python",
})
// Start the extension host - this blocks until shutdown
if err := host.Run(ctx); err != nil {
return fmt.Errorf("failed to run extension: %w", err)
}
return nil
},
}
}
Key Benefits of the Builder Pattern:
- Unified Registration: Register all capabilities in one place
- Automatic Lifecycle Management: ExtensionHost handles readiness signaling and shutdown
- Fluent API: Chain multiple registrations for clean, readable code
- Type Safety: Compile-time checking of provider interfaces
- Centralized Configuration: All extension behavior defined in one location
Extension Capabilities You Can Register:
- Service Target Providers: Custom deployment targets (e.g., VMs, custom Azure services)
- Framework Service Providers: Language/framework-specific build and package logic
- Project Event Handlers: Project-level lifecycle events (preprovision, predeploy, etc.)
- Service Event Handlers: Service-level lifecycle events with optional filtering
Event Handler Filtering:
Service event handlers support optional filtering to only handle specific service types:
WithServiceEventHandler("prepackage", handler, &azdext.ServiceEventOptions{
Host: "containerapp", // Only handle Container App services
Language: "python", // Only handle Python services
})
Supported Languages
azd extensions can be built in any programming language but starter templates are included for the following:
- Go (Best support)
- Dotnet (C#)
- Python
- JavaScript
- TypeScript (Coming Soon)
Each language has different build times and integration capabilities:
| Language | Build Time | Platform Support | Integration Level |
|---|---|---|---|
| Go | Fast (~15s) | All platforms | Full native support |
| Dotnet | Medium (~60s) | All platforms | Strong integration |
| Python | Slower (~4m) | All platforms | Good integration |
| JavaScript | Medium (~90s) | All platforms | Basic integration |
The build process automatically creates binaries for multiple platforms and architectures:
- Windows (AMD64, ARM64)
- Linux (AMD64, ARM64)
- macOS/Darwin (AMD64, ARM64)
Note
Build times may vary depending on your hardware and extension complexity.
Building a Root Command
Go extensions should build their cobra root command using azdext.NewExtensionRootCommand. The helper registers the flags and environment-variable handling that azd standardizes across all extensions, so extension authors do not need to declare them manually (and avoid name collisions with azd's reserved global flags).
import "github.com/azure/azure-dev/cli/azd/pkg/azdext"
func NewRootCommand() *cobra.Command {
rootCmd, extCtx := azdext.NewExtensionRootCommand(azdext.ExtensionCommandOptions{
Name: "agent",
Use: "agent <command> [options]",
Short: "Manage AI agents.",
})
rootCmd.AddCommand(newShowCommand(extCtx))
// ... other subcommands
return rootCmd
}
NewExtensionRootCommand automatically:
- Registers the standard persistent flags:
--debug,--no-prompt,-C/--cwd,-e/--environment,-o/--output, plus the hidden--trace-log-file/--trace-log-urlflags. - Reads the matching
AZD_DEBUG,AZD_NO_PROMPT,AZD_CWD, andAZD_ENVIRONMENTenvironment variables thatazdsets when launching the extension, and applies them when the corresponding flag was not passed explicitly. - Honors
--cwd/AZD_CWDby changing the process working directory before the command runs. - Extracts W3C trace context from
TRACEPARENT/TRACESTATEand callsazdext.WithAccessTokenso the command'scmd.Context()is ready to use with the gRPC client.
The returned *ExtensionContext is the recommended way to read these values inside subcommand RunE handlers — do not redeclare any of the standard flags on subcommands.
type ExtensionContext struct {
Debug bool
NoPrompt bool
Cwd string
Environment string
OutputFormat string
}
Pass extCtx into each subcommand factory and read from it inside RunE:
func newShowCommand(extCtx *azdext.ExtensionContext) *cobra.Command {
cmd := &cobra.Command{
Use: "show",
RunE: func(cmd *cobra.Command, args []string) error {
if extCtx.NoPrompt {
// skip interactive prompts
}
// use extCtx.Environment, extCtx.OutputFormat, etc.
return nil
},
}
return cmd
}
Per-Subcommand Flag Options
The persistent -o/--output flag is shared across the whole command tree, but different subcommands often support different output formats (for example a list subcommand may support json and table, while a version subcommand only supports json). Use azdext.RegisterFlagOptions to declare the allowed values and per-command default for any inherited persistent flag without redeclaring it:
listCmd := &cobra.Command{
Use: "list",
RunE: runList,
}
azdext.RegisterFlagOptions(listCmd, azdext.FlagOptions{
Name: "output",
AllowedValues: []string{"json", "table"},
Default: "json",
})
A single RegisterFlagOptions declaration drives all of the following:
- Help text.
myext list --helprenders the flag as-o, --output string The output format (supported: json, table) (default "json"). - Metadata.
azd ext metadata(and the JSON snapshot extensions ship in their package) populatesflag.validValuesandflag.defaultfor the subcommand from the same declaration. - Parse-time validation. Invocations that pass an unsupported value (e.g.
myext list --output yaml) are rejected by the SDK with a descriptive error beforeRunEruns. - Shell completion. Tab-completing
myext list --outputoffers the registered allowed values. - Default substitution. When the user does not pass
--output, the SDK writes the per-command default intoextCtx.OutputFormatbeforeRunEruns, so subcommands can readextCtx.OutputFormatdirectly without their own resolution logic.
Sibling and parent commands continue to render the SDK default help text and behavior. The helper generalizes to any inherited persistent flag (passing the flag name as the second argument); it is most commonly used for --output.
Distributed Tracing
azd uses OpenTelemetry and W3C Trace Context for distributed tracing. azd sets TRACEPARENT in the environment when it launches the extension process.
The recommended approach is to use azdext.Run, which automatically creates a trace-aware context, injects the access token, reports structured errors, and handles os.Exit:
func main() {
azdext.Run(cmd.NewRootCommand())
}
For lifecycle-listener extensions, azdext.NewListenCommand sets up trace context and access token automatically within its handler.
Note:
azdext.NewContext()is deprecated. Useazdext.Runfor custom-command extensions orazdext.NewListenCommand/azdext.NewExtensionRootCommandfor lifecycle listeners.NewContextremains available for backward compatibility but new extensions should not use it.
To correlate Azure SDK calls with the parent trace, add the correlation policy to your client options:
import "github.com/azure/azure-dev/cli/azd/pkg/azsdk"
clientOptions := &policy.ClientOptions{
PerCallPolicies: []policy.Policy{
azsdk.NewMsCorrelationPolicy(),
},
}
Developer Extension
The easiest way to get started building extensions is to install the azd Developer extension.
Important
Ensure you have added the dev extension source to your azd configuration
# Install the `azd` developer extension
azd extension install microsoft.azd.extensions
Once installed the extension registers a suite of commands under the x namespace.
Note
Having issues or have some ideas to improve the azd developer extension?
Just log an issue in the azure/azure-dev repo.
Commands
init - Initialize a new extension project.
Tip
You'll typically want to run this command inside the azd/cli/extensions directory.
Usage: azd x init
- Collects information for the extension and scaffolds and extension in a specified language of choice.
- Creates local extension source if it doesn't already exist
- Builds initial binaries for extension
- Packs extension
- Publishes extension to local extension source
- Installs the extension locally for immediate use
build - Build the binary for the extension.
Usage: azd x build
-C, --cwd- The extension directory, inherited from azd's global flag (defaults to the current directory).--all- Builds binaries for all supported operating systems and architecture.--output, -o- Path to the output directory, defaults to./bin.--skip-install- When skips local installation after successful build.
watch - Watches the extension directory for changes and automatically rebuilds and installs extension
Usage: azd x watch
-C, --cwd- The extension directory, inherited from azd's global flag (defaults to the current directory).
pack - Package your extension to prepare for publishing.
Usage: azd x pack
-C, --cwd- The extension directory, inherited from azd's global flag (defaults to the current directory).--input, -i- Path to the input directory that contains binary files.--output, -o- Path to the artifacts output directory, defaults to localazdartifacts path,~/.azd/registry.--rebuild- When set forces a rebuild before packaging.
release - Create a new Github release for the extension.
Usage: azd x release --repo {owner}/{name}
-C, --cwd- The extension directory, inherited from azd's global flag (defaults to the current directory).--artifacts- Path to the artifacts to upload for the release, defaults to localazdartifacts path,~/.azd/registry.--repo- The Github repo name in{owner}/{repo}format.--title, -t- The name of the release, defaults to extension name plus version.--prerelease- When set marks the release as a prerelease.--draft, -d- When set marks the release a draft--notes, -n- The release notes for the release, defaults to using contents ofCHANGELOG.mdwithin extension directory.--version, -v- The version of the release, defaults to extension version from extension manifest--confirm- When set bypasses confirmation prompts before release
publish - Updates the extension source registry with new metadata.
Usage: azd x publish --repo {owner}/{name}
-C, --cwd- The extension directory, inherited from azd's global flag (defaults to the current directory).--registry, -r- The path to the registry.json file to update, defaults to local extension registry--repo- The Github repo name in{owner}/{repo}format.--version, -v- The version of the release, defaults to extension version from extension manifest
Extension Structure Overview
When you create a new extension using azd x init, it generates a directory structure with several important files:
contoso.azd.samples.<language>/
├── bin/ # Contains built binaries
├── build.ps1 # Windows build script
├── build.sh # Unix build script
├── CHANGELOG.md # Version history and release notes
├── extension.yaml # Extension metadata and capabilities
├── README.md # Documentation for your extension
└── <language-specific> # Source code files specific to the chosen language
Key files in the extension structure:
- extension.yaml: Defines metadata, capabilities, and commands for your extension
- CHANGELOG.md: Documents changes between versions (used for release notes)
- build scripts: Language-specific scripts for building your extension
Each supported language has a slightly different structure:
Go Extension Structure
├── go.mod # Go module definition
├── go.sum # Go dependency checksums
├── main.go # Entry point for the extension
└── internal/ # Internal implementation code
.NET Extension Structure
├── <ExtensionName>.csproj # .NET project file
├── Program.cs # Entry point for the extension
└── Commands/ # Command implementations
Python Extension Structure
├── pyproject.toml # Python project configuration
├── requirements.txt # Python dependencies
├── setup.py # Package setup script
├── __main__.py # Entry point for the extension
└── src/ # Source code directory
JavaScript Extension Structure
├── package.json # NPM package configuration
├── package-lock.json # NPM dependency locks
├── index.js # Entry point for the extension
└── src/ # Source code directory
Version Upgrade Path
Managing versions of your extension is an important part of the development process. Here's how to properly upgrade your extension version:
-
Update Version Number:
- Modify the
versionfield in yourextension.yamlfile - Follow Semantic Versioning (MAJOR.MINOR.PATCH)
- Modify the
-
Document Changes:
- Update your
CHANGELOG.mdwith details about what's new or fixed - Include any breaking changes or migration notes
- Update your
-
Build, Package and Test:
azd x build --all # Build for all platforms azd x pack # Package the extension -
Release the New Version:
azd x release --repo <owner>/<repo> -
Publish to Registry:
azd x publish --repo <owner>/<repo>
Version Compatibility
- Major Version Changes (1.0.0 → 2.0.0): Indicates breaking changes
- Minor Version Changes (1.0.0 → 1.1.0): Adds new features while maintaining backward compatibility
- Patch Version Changes (1.0.0 → 1.0.1): Includes bug fixes with no feature changes
When upgrading across major versions, users may need to adapt to API changes. Document these changes clearly in your CHANGELOG.md.
GitHub Authentication Requirements
When using the release and publish commands that interact with GitHub repositories, you'll need proper authentication:
- Ensure you're authenticated with GitHub before attempting to create releases
- Use a Personal Access Token (PAT) with appropriate permissions:
- For
azd x release:reposcope permissions - For
azd x publish:reposcope permissions
- For
You can authenticate with GitHub using:
# Login with your GitHub credentials
gh auth login
# Or set the GITHUB_TOKEN environment variable
export GITHUB_TOKEN=your_personal_access_token
Note
Releases and publishing will fail without proper GitHub authentication.
Publishing Workflow
The publishing process for azd extensions involves multiple steps:
- Build the extension for all target platforms
- Package the extension artifacts
- Release the extension to GitHub
- Publish the extension to a registry
Each step can be performed manually or combined in a single script for automation:
# Complete publishing workflow example
cd path/to/your/extension
azd x pack --rebuild
azd x release --repo owner/repo
azd x publish --repo owner/repo
Cross-Platform Support
When publishing an extension, the system automatically generates binaries and packages for multiple platforms:
- Windows (AMD64, ARM64)
- Linux (AMD64, ARM64)
- macOS/Darwin (AMD64, ARM64)
This ensures your extension can be used across different operating systems and architectures without additional configuration.
Troubleshooting
Common issues you might encounter when developing and publishing extensions:
Build Issues
- Language-Specific Dependencies: Ensure all required dependencies for your language are installed
- Platform Compatibility: Test on the platforms you're targeting
- Build Times: Python extensions take significantly longer to build (~4 minutes) compared to Go (~15 seconds)
Release Issues
- GitHub Authentication: Verify your GitHub token has proper permissions
- Version Conflicts: Ensure you're not trying to release a version that already exists
- Release Asset Size: Large extensions may take longer to upload
Publishing Issues
- Registry Conflicts: Check if the extension ID already exists in the registry
- Permission Denied: Verify your GitHub token has proper permissions
- Registry Schema Validation: Ensure your extension manifest follows the schema
Capabilities
Current Capabilities
The following lists the current capabilities available to azd extensions:
Extension Commands (custom-commands)
Extensions must declare the
custom-commandscapability in theirextension.yamlfile.
Extensions can register commands under a namespace or command group within azd.
For example, installing the AI extension adds a new ai command group.
Lifecycle Events (lifecycle-events)
Extensions must declare the
lifecycle-eventscapability in theirextension.yamlfile.
Extensions can subscribe to project and service lifecycle events (both pre and post events) for:
- build
- restore
- package
- provision
- deploy
Your extension must include a listen command to subscribe to these events.
azd will automatically invoke your extension during supported commands to establish bi-directional communication.
Note
Provision lifecycle events (preprovision, postprovision) are not fired when running azd provision --preview.
Service Target Providers (service-target-provider)
Extensions must declare the
service-target-providercapability in theirextension.yamlfile.
Extensions can provide custom service targets that handle the full deployment lifecycle (package, publish, deploy) for specialized Azure services or custom deployment patterns. Examples include:
- Custom VM deployment targets
- Specialized container platforms
- Edge computing platforms
- Custom cloud providers
Framework Service Providers (framework-service-provider)
Extensions must declare the
framework-service-providercapability in theirextension.yamlfile.
Extensions can provide custom language and framework support for build, restore, and package operations. Examples include:
- Custom language support (Rust, PHP, etc.)
- Framework-specific build systems
- Custom package managers
- Specialized build toolchains
Model Context Protocol Server (mcp-server)
Extensions must declare the
mcp-servercapability in theirextension.yamlfile.
Extensions can provide AI agent tools through the Model Context Protocol, enabling:
- Custom AI tools and integrations
- Specialized knowledge bases
- Azure service automation for AI agents
- Custom development workflows for AI-assisted development
Validation Provider (validation-provider)
Extensions must declare the
validation-providercapability in theirextension.yamlfile.
Extensions can contribute validation checks to azd's validation pipeline. Currently supported check types:
local-preflight— Checks run duringazd provisionbefore deployment. The extension receives the Bicep snapshot, ARM template, ARM parameters, and Azure location as context.
Future check types (e.g., project-config, auth) can be added without protocol
changes.
Example:
host := azdext.NewExtensionHost(azdClient).
WithValidationCheck(azdext.ValidationCheckRegistration{
CheckType: "local-preflight",
RuleID: "my_naming_rule",
Factory: func() azdext.ValidationCheckProvider {
return &MyNamingCheck{}
},
})
See local-preflight-validation.md
for full details on the check interface and context keys.
Future Considerations
Future ideas include:
- Registration of pluggable providers for:
- Infrastructure providers (e.g., Pulumi)
- Source control providers (e.g., GitLab)
- Pipeline providers (e.g., TeamCity)
Snapshot Testing for Extensions
Extension commands are included in CLI snapshot tests (TestUsage and TestFigSpec) to ensure they appear in help output and VS Code IntelliSense. Tests run in an isolated environment (temporary AZD_CONFIG_DIR) that installs all extensions from registry.json, generates snapshots, then cleans up.
Update snapshots when modifying registry.json or extension commands:
# From cli/azd directory
UPDATE_SNAPSHOTS=true go test ./cmd -run TestUsage
UPDATE_SNAPSHOTS=true go test ./cmd -run TestFigSpec
The ext-registry-ci workflow runs these tests automatically when registry.json is modified in a PR.
Developer Workflow
The following are the most common developer workflow for developing extensions.
Creating a new Extension
- Run
azd x initto start a new extension
Developing an Extension
- Navigate to the extension folder
azd/cli/extensions/{EXTENSION_ID} - Run
azd x watchduring development to automatically build and install local updates - Run
azd x buildincrementally build & install local version
Validate, Release and Publish development extension
- Create a new feature branch for the extension
git checkout -b <branch-name> - Modify the extension code as needed
- Update the version parameter within the
extension.yamlfile. - Run
azd x pack --rebuild - Run
azd x release --prereleaseto create new Github release for the extension - Run
azd x publish --registry ../registry.jsonto publish the new version metadata into the registry - Commit and push the changes to the forked repo.
To share the forked registry with others just provide the raw github link to the azd/cli/extensions/registry.json file.
Validate, Release and Publish official extension
- Follow above steps for validating, releasing & publishing development extension
- Push the local changes
git push <remote> <branch-name> -u - Create PR for the changes within
azure/azure-devrepo
Important
Make sure modifications to the registry.json file are NOT included on extension code PR.
Changes to the registry.json file should be made after code changes have been merged.
After PR has been merged
- Create a new branch to publish registry changes
git checkout -b <branch-name> - Run
azd x publish --registry ../registry.jsonto publish the new version metadata into the registry - Commit the changes to the local branch,
git commit -am "<description>" - Create PR for the changes within
azure/azure-devrepo
Once PR has been merged the extension updates are now live in the official azd extension source registry.
Extension Manifest
Each extension must declare an extension.yaml file that describes the metadata for the extension and the capabilities that it supports. This metadata is used within the extension registry to provide details to developers when searching for and installing extensions.
A JSON schema is available to support authoring extension manifests.
Schema Properties
Required Properties:
id: Unique identifier for the extensionversion: Semantic version following MAJOR.MINOR.PATCH formatdisplayName: Human-readable name of the extensiondescription: Detailed description of the extension
Each manifest must include either capabilities or dependencies. Extension packs may omit capabilities
when they declare dependencies instead and have no executable.
Optional Properties:
namespace: Command namespace for grouping extension commandsentryPoint: Executable or script that serves as the entry pointcapabilities: Array of extension capabilities (see below)usage: Instructions on how to use the extensionexamples: Array of usage examples with name, description, and usagetags: Keywords for categorization and filteringdependencies: Other extensions this extension depends onproviders: List of providers this extension registersplatforms: Platform-specific metadatamcp: Model Context Protocol server configurationrequiredAzdVersion: Semver constraint on the azd CLI version required to use this extension (e.g.>= 1.24.0,^1.23.4,< 2.0.0). When set, azd filters out incompatible versions during install/upgrade and warns when the latest version cannot be used.
Extension Capabilities
Extensions can declare the following capabilities in their manifest:
custom-commands: Expose new command groups and commands to azdlifecycle-events: Subscribe to azd project and service lifecycle eventsmcp-server: Provide Model Context Protocol tools for AI agentsservice-target-provider: Provide custom service deployment targetsframework-service-provider: Provide custom language frameworks and build systemsprovisioning-provider: Provide a custom infrastructure provisioning experience (alternative to Bicep / Terraform)validation-provider: Contribute validation checks to azd's preflight and future validation pipelinesmetadata: Provide comprehensive metadata about commands and configuration schemas
Complete Extension Manifest Example
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/refs/heads/main/cli/azd/extensions/extension.schema.json
id: microsoft.azd.demo
namespace: demo
displayName: Demo Extension
description: This extension provides examples of the azd extension framework.
usage: azd demo <command> [options]
version: 0.1.0
entryPoint: demo
capabilities:
- custom-commands
- lifecycle-events
- service-target-provider
- framework-service-provider
- validation-provider
- mcp-server
- metadata
examples:
- name: context
description: Displays the current azd project & environment context.
usage: azd demo context
- name: prompt
description: Display prompt capabilities.
usage: azd demo prompt
- name: deploy-vm
description: Deploy application to virtual machine using custom service target.
usage: azd demo deploy-vm
tags:
- demo
- example
- development
dependencies:
- id: microsoft.azd.core
version: "^1.0.0"
providers:
- name: demo-vm
type: service-target
description: Custom VM deployment target for demonstration purposes
- name: rust-framework
type: framework-service
description: Custom Rust language framework support
platforms:
windows:
executable: demo.exe
linux:
executable: demo
darwin:
executable: demo
mcp:
serve:
args: ["mcp", "serve"]
env: ["DEMO_CONFIG=production"]
Extension Dependencies
Extensions can declare dependencies on other extensions using the dependencies array:
dependencies:
- id: microsoft.azd.ai.builder
version: "^1.2.0"
- id: contoso.custom.tools
version: "~2.1.0"
Dependencies support semantic versioning constraints:
^1.0.0: Compatible with version 1.x.x~1.2.0: Compatible with version 1.2.x>=1.0.0 <2.0.0: Range specification
azd installs or upgrades to the highest published version that satisfies the dependency constraint.
Pre-release versions follow standard semver constraint rules: a constraint must include a pre-release comparator to match pre-release versions.
For example, >=0.1.0 does not match 0.1.31-preview; use >=0.1.0-0 or a pre-release range such as ~0.1.0-preview when preview versions should be eligible.
Extension Packs
An extension pack is an extension manifest that groups related extensions so users can install them with one command. Packs declare dependencies but do not provide an executable, command namespace, or runtime capabilities of their own. Use a pack when you want to publish a curated set of extensions, such as a product family or scenario bundle, without asking users to install each extension separately.
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/refs/heads/main/cli/azd/extensions/extension.schema.json
id: contoso.ai
displayName: Contoso AI Extension Pack
description: Installs the Contoso AI azd extensions.
version: 0.1.0
dependencies:
- id: contoso.ai.agents
version: "~0.1.0-preview"
- id: contoso.ai.models
version: "~0.1.0-preview"
Pack manifests must include at least one dependency. They may omit capabilities, namespace, entryPoint, usage, and examples when the pack has no commands of its own. Installing a pack installs its dependencies recursively from the same extension source as the pack. Dependency versions in the manifest support semver constraints, but command-line --version values for azd extension install and azd extension upgrade are exact versions.
Upgrading a pack upgrades the pack and, by default, reconciles installed dependencies to the highest published versions that satisfy the pack's declared dependency constraints. This dependency reconciliation still runs when the pack itself is already current, because an unchanged pack can point to a dependency range with newer matching versions. Users can disable automatic dependency upgrades with azd extension upgrade <pack-id> --no-dependency-upgrades.
Provider Registration
When your extension provides custom service targets or framework services, declare them in the providers section:
providers:
- name: azure-vm
type: service-target
description: Deploy applications to Azure Virtual Machines
- name: go-gin
type: framework-service
description: Support for Go applications using the Gin framework
This metadata helps azd understand what providers your extension offers and enables proper capability validation.
Model Context Protocol (MCP) Configuration
For extensions that provide AI agent tools, configure the MCP server:
capabilities:
- mcp-server
mcp:
serve:
args: ["mcp", "serve"]
env:
- "API_KEY=${API_KEY}"
- "LOG_LEVEL=info"
The mcp.serve.args specifies the command arguments to start your MCP server, while env sets additional environment variables.
Platform-Specific Configuration
Extensions can provide platform-specific metadata for different operating systems:
platforms:
windows:
executable: myext.exe
installScript: install.ps1
linux:
executable: myext
installScript: install.sh
darwin:
executable: myext
installScript: install.sh
Basic Extension Manifest Example
Here's a simple extension manifest for getting started:
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/refs/heads/main/cli/azd/extensions/extension.schema.json
id: microsoft.azd.demo
namespace: demo
displayName: Demo Extension
description: This extension provides examples of the azd extension framework.
usage: azd demo <command> [options]
version: 0.1.0
capabilities:
- custom-commands
- lifecycle-events
examples:
- name: context
description: Displays the current `azd` project & environment context.
usage: azd demo context
- name: prompt
description: Display prompt capabilities.
usage: azd demo prompt
The following is an example of an extension manifest.
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/refs/heads/main/cli/azd/extensions/extension.schema.json
id: microsoft.azd.demo
namespace: demo
displayName: Demo Extension
description: This extension provides examples of the azd extension framework.
usage: azd demo <command> [options]
version: 0.6.0
language: go
capabilities:
- custom-commands
- lifecycle-events
- mcp-server
- service-target-provider
- framework-service-provider
- metadata
providers:
- name: demo
type: service-target
description: Deploys application components to demo
examples:
- name: context
description: Displays the current `azd` project & environment context.
usage: azd demo context
- name: prompt
description: Display prompt capabilities.
usage: azd demo prompt
Invoking Extension Commands
When azd invokes an extension command, the following steps occur:
azdstarts a gRPC server on a random port.azdinvokes your command, passing all arguments and flags:- An environment variable named
AZD_SERVERis set with the server address and random port (e.g.,localhost:12345). - An
azdaccess token environment variableAZD_ACCESS_TOKENis set which is a JWT token signed with a randomly generated key good for the lifetime of the command. The token includes claims that identify each unique extensions and its supported capabilities. - Additional environment variables from the current
azdenvironment are also set.
- An environment variable named
- The extension command can communicate with
azdthrough extension framework gRPC services. azdwaits for the extension command to complete:- If a non-zero exit code is returned,
azdreports the operation as an error.
- If a non-zero exit code is returned,
To enable interaction with azd from within the extension, the extension must leverage a gRPC client and connect to the server using the address specified in the AZD_SERVER environment variable.
The gRPC client must also include an authorization parameter with the value from AZD_ACCESS_TOKEN; otherwise, requests will fail due to invalid authorization. Extensions must declare their supported capabilities within the extension registry otherwise certain service operations may fail with a permission denied error.
Structured Error Reporting (Custom Commands)
For custom command execution, extensions can provide richer telemetry by reporting a structured error to
the azd host via the ExtensionService.ReportError gRPC call before exiting with a non-zero status code.
For Go extensions, the recommended approach is to use azdext.Run, which handles context creation, structured error
reporting, suggestion rendering, and os.Exit automatically:
func main() {
azdext.Run(cmd.NewRootCommand())
}
Alternatively, you can use azdext.ReportError directly for lower-level control
(note: NewContext is deprecated — prefer Run for new extensions):
func main() {
ctx := azdext.NewContext() // Deprecated: prefer azdext.Run
ctx = azdext.WithAccessToken(ctx)
rootCmd := cmd.NewRootCommand()
if err := rootCmd.ExecuteContext(ctx); err != nil {
_ = azdext.ReportError(ctx, err)
os.Exit(1)
}
}
Use typed extension errors to control telemetry mapping:
return &azdext.LocalError{
Message: "invalid configuration: missing required field 'name'",
Code: "invalid_config",
Category: "validation",
Suggestion: "Ensure your azure.yaml has a 'name' field defined.",
}
return &azdext.ServiceError{
Message: "request failed",
ErrorCode: "TooManyRequests",
StatusCode: 429,
ServiceName: "openai.azure.com",
Suggestion: "Wait a moment and retry the request, or increase your rate limit.",
}
Current telemetry result code conventions:
- Service errors:
ext.service.<service>.<statusCode> - Local domain categories:
validation->ext.validation.<code>auth->ext.auth.<code>dependency->ext.dependency.<code>compatibility->ext.compatibility.<code>user->ext.user.<code>internal->ext.internal.<code>
- Unknown local categories fall back to
ext.local.<code>.
Go
For extensions built using Go, the azdext package provides an AzdClient which acts as the gRPC client.
Other Languages
For extensions authored in other programming languages, the gRPC proto files can be used to generate clients in your preferred language.
How to set azd access token on requests
The following example shows how an outgoing Go context can be constructed with the azd access token.
// WithAccessToken sets the access token for the `azd` client into a new Go context.
func WithAccessToken(ctx context.Context, params ...string) context.Context {
tokenValue := strings.Join(params, "")
if tokenValue == "" {
tokenValue = os.Getenv("AZD_ACCESS_TOKEN")
}
md := metadata.Pairs("authorization", tokenValue)
return metadata.NewOutgoingContext(ctx, md)
}
How to call azd gRPC service
// Create a new context that includes the azd access token
ctx := azdext.WithAccessToken(cmd.Context())
// Create a new azd client
// The constructor function automatically constructs the address
// from the `AZD_SERVER` environment variable but can be overridden if required.
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()
getProjectResponse, err := azdClient.Project().Get(ctx, &azdext.EmptyRequest{})
if err != nil {
// No project found
return err
}
// Print out the `azd` project name
fmt.Println(getProjectResponse.Project.Name)
How to subscribe to lifecycle events
The following is an example of subscribing to project & service lifecycle events within an azd template.
In this example the extension is leveraging the azdext.NewExtensionHost constructor. This provides a fluent API to register event handlers, service target providers, and other extension capabilities in a unified way.
Other languages will need to manually handle the different message types invoked by the service.
// Create a new context that includes the azd access token.
ctx := azdext.WithAccessToken(cmd.Context())
// Create a new azd client.
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()
// Create an extension host and register event handlers using the fluent API
host := azdext.NewExtensionHost(azdClient).
WithProjectEventHandler("preprovision", func(ctx context.Context, args *azdext.ProjectEventArgs) error {
// This is your event handler code
for i := 1; i <= 20; i++ {
fmt.Printf("%d. Doing important work in extension...\n", i)
time.Sleep(250 * time.Millisecond)
}
return nil
}).
WithServiceEventHandler("prepackage", func(ctx context.Context, args *azdext.ServiceEventArgs) error {
// Access service context with artifacts from previous phases
fmt.Printf("Processing service: %s\n", args.Service.Name)
// Check build artifacts from previous build phase
if len(args.ServiceContext.Build) > 0 {
fmt.Printf("Found %d build artifacts:\n", len(args.ServiceContext.Build))
for _, artifact := range args.ServiceContext.Build {
fmt.Printf(" - %s (kind: %s)\n", artifact.Path, artifact.Kind)
}
}
// Check restore artifacts
if len(args.ServiceContext.Restore) > 0 {
fmt.Printf("Found %d restore artifacts:\n", len(args.ServiceContext.Restore))
for _, artifact := range args.ServiceContext.Restore {
fmt.Printf(" - %s\n", artifact.Path)
}
}
// Perform your custom packaging logic here
for i := 1; i <= 10; i++ {
fmt.Printf("%d. Preparing package for %s...\n", i, args.Service.Name)
time.Sleep(250 * time.Millisecond)
}
return nil
}, &azdext.ServiceEventOptions{
// Optionally filter your subscription by service host and/or language
Host: "containerapp",
Language: "python",
}).
WithServiceEventHandler("postdeploy", func(ctx context.Context, args *azdext.ServiceEventArgs) error {
// Access deployment results
fmt.Printf("Service %s deployment completed\n", args.Service.Name)
// Check deployment artifacts
for _, artifact := range args.ServiceContext.Deploy {
if artifact.Kind == azdext.ARTIFACT_KIND_ENDPOINT {
fmt.Printf("Service endpoint: %s\n", artifact.Path)
} else if artifact.Kind == azdext.ARTIFACT_KIND_DEPLOYMENT {
fmt.Printf("Deployment ID: %s\n", artifact.Path)
}
}
return nil
}, nil)
// Start the extension host
// This is a blocking call and will not return until the server connection is closed.
if err := host.Run(ctx); err != nil {
return fmt.Errorf("failed to run extension: %w", err)
}
Developer Artifacts
azd leverages gRPC for the communication protocol between Core azd and extensions. gRPC client & server components are automatically generated from profile files.
- Proto files @ grpc/proto
- Generated files @ pkg/azdext
- Make file @ Makefile
To re-generate gRPC clients:
- Run
protoc --versionto check ifprotocis installed. If not, download and install it from GitHub. - Run
make --versionto check ifmakeis installed. - Run
go install google.golang.org/protobuf/cmd/protoc-gen-go@latestandgo install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestto install the required Go tools. - Run
make protofrom the~/cli/azdfolder of the repo inGit Bash. - Run
../../eng/scripts/copyright-check.sh . --fixto add copyright.
gRPC Services
The following are a list of available gRPC services for extension developer to integrate with azd core.
Table of Contents
- Project Service
- Environment Service
- User Config Service
- Deployment Service
- Account Service
- Prompt Service
- AI Model Service
- Event Service
- Container Service
- Framework Service
- Service Target Service
- Compose Service
- Workflow Service
- Copilot Service
Project Service
This service manages project configuration retrieval and related operations, including project and service-level configuration management.
See project.proto for more details.
Get
Gets the current project configuration.
- Request: EmptyRequest (no fields)
- Response: GetProjectResponse
- Contains ProjectConfig:
name(string)resource_group_name(string)path(string)metadata:{ template: string }services: map of ServiceConfiginfra: InfraOptions
- Contains ProjectConfig:
AddService
Adds a new service to the project.
- Request: AddServiceRequest
- Contains:
service: ServiceConfig
- Contains:
- Response: EmptyResponse
GetConfigSection
Retrieves a project configuration section by path from AdditionalProperties.
- Request: GetProjectConfigSectionRequest
path(string): Dot-notation path to the config section
- Response: GetProjectConfigSectionResponse
- Contains:
section(google.protobuf.Struct): The configuration sectionfound(bool): Whether the section exists
- Contains:
SetConfigSection
Sets a project configuration section at the specified path in AdditionalProperties.
- Request: SetProjectConfigSectionRequest
path(string): Dot-notation path to the config sectionsection(google.protobuf.Struct): The configuration section to set
- Response: EmptyResponse
GetConfigValue
Retrieves a single project configuration value by path from AdditionalProperties.
- Request: GetProjectConfigValueRequest
path(string): Dot-notation path to the config value
- Response: GetProjectConfigValueResponse
- Contains:
value(google.protobuf.Value): The configuration valuefound(bool): Whether the value exists
- Contains:
SetConfigValue
Sets a single project configuration value at the specified path in AdditionalProperties.
- Request: SetProjectConfigValueRequest
path(string): Dot-notation path to the config valuevalue(google.protobuf.Value): The configuration value to set
- Response: EmptyResponse
UnsetConfig
Removes a project configuration value or section at the specified path from AdditionalProperties.
- Request: UnsetProjectConfigRequest
path(string): Dot-notation path to the config to remove
- Response: EmptyResponse
GetServiceConfigSection
Retrieves a service configuration section by path from service AdditionalProperties.
- Request: GetServiceConfigSectionRequest
service_name(string): Name of the servicepath(string): Dot-notation path to the config section
- Response: GetServiceConfigSectionResponse
- Contains:
section(google.protobuf.Struct): The configuration sectionfound(bool): Whether the section exists
- Contains:
SetServiceConfigSection
Sets a service configuration section at the specified path in service AdditionalProperties.
- Request: SetServiceConfigSectionRequest
service_name(string): Name of the servicepath(string): Dot-notation path to the config sectionsection(google.protobuf.Struct): The configuration section to set
- Response: EmptyResponse
GetServiceConfigValue
Retrieves a single service configuration value by path from service AdditionalProperties.
- Request: GetServiceConfigValueRequest
service_name(string): Name of the servicepath(string): Dot-notation path to the config value
- Response: GetServiceConfigValueResponse
- Contains:
value(google.protobuf.Value): The configuration valuefound(bool): Whether the value exists
- Contains:
SetServiceConfigValue
Sets a single service configuration value at the specified path in service AdditionalProperties.
- Request: SetServiceConfigValueRequest
service_name(string): Name of the servicepath(string): Dot-notation path to the config valuevalue(google.protobuf.Value): The configuration value to set
- Response: EmptyResponse
UnsetServiceConfig
Removes a service configuration value or section at the specified path from service AdditionalProperties.
- Request: UnsetServiceConfigRequest
service_name(string): Name of the servicepath(string): Dot-notation path to the config to remove
- Response: EmptyResponse
Environment Service
This service handles environment management including retrieval, selection, and key-value operations.
See environment.proto for more details.
GetCurrent
Gets the current environment.
- Request: EmptyRequest
- Response: EnvironmentResponse (includes an Environment with
name)
List
Retrieves all azd environments.
- Request: EmptyRequest
- Response: EnvironmentListResponse (list of EnvironmentDescription)
Get
Retrieves an environment by its name.
- Request: GetEnvironmentRequest
name(string)
- Response: EnvironmentResponse
- Contains an Environment with
name(string)
- Contains an Environment with
Select
Sets the selected environment as default.
- Request: SelectEnvironmentRequest
name(string)
- Response: EmptyResponse
GetValues
Retrieves all key-value pairs in the specified environment.
- Request: GetEnvironmentRequest
name(string)
- Response: KeyValueListResponse
- Contains a list of KeyValue:
key(string)value(string)
- Contains a list of KeyValue:
GetValue
Retrieves the value of a specific key.
- Request: GetEnvRequest
env_name(string)key(string)
- Response: KeyValueResponse
- Contains:
key(string)value(string)
- Contains:
SetValue
Sets the value of a key in an environment.
- Request: SetEnvRequest
env_name(string)key(string)value(string)
- Response: EmptyResponse
GetConfig
Retrieves a config value by path.
- Request: GetConfigRequest
path(string)
- Response: GetConfigResponse
- Contains:
value(bytes)found(bool)
- Contains:
GetConfigString
Retrieves a config value as a string by path.
- Request: GetConfigStringRequest
path(string)
- Response: GetConfigStringResponse
- Contains:
value(string)found(bool)
- Contains:
GetConfigSection
Retrieves a config section by path.
- Request: GetConfigSectionRequest
path(string)
- Response: GetConfigSectionResponse
- Contains:
section(bytes)found(bool)
- Contains:
SetConfig
Sets a config value at a given path.
- Request: SetConfigRequest
path(string)value(bytes)
- Response: EmptyResponse
UnsetConfig
Removes a config value at a given path.
- Request: UnsetConfigRequest
path(string)
- Response: EmptyResponse
User Config Service
This service manages user-specific configuration retrieval and updates.
See user_config.proto for more details.
Get
Retrieves a user configuration value by path.
- Request: GetRequest
path(string)
- Response: GetResponse
- Contains:
value(bytes)found(bool)
- Contains:
GetString
Retrieves a user configuration value as a string.
- Request: GetStringRequest
path(string)
- Response: GetStringResponse
- Contains:
value(string)found(bool)
- Contains:
GetSection
Retrieves a section of the user configuration by path.
- Request: GetSectionRequest
path(string)
- Response: GetSectionResponse
- Contains:
section(bytes)found(bool)
- Contains:
Set
Sets a user configuration value.
- Request: SetRequest
path(string)value(bytes)
- Response: SetResponse
- Contains:
status(string)
- Contains:
Unset
Removes a user configuration value.
- Request: UnsetRequest
path(string)
- Response: UnsetResponse
- Contains:
status(string)
- Contains:
Deployment Service
This service provides operations for deployment retrieval and context management.
See deployment.proto for more details.
GetDeployment
Retrieves the latest Azure deployment provisioned by azd.
- Request: EmptyRequest
- Response: GetDeploymentResponse
- Contains Deployment:
id(string)location(string)deploymentId(string)name(string)type(string)tags(map<string, string>)outputs(map<string, string>)resources(repeated string)
- Contains Deployment:
GetDeploymentContext
Retrieves the current deployment context.
- Request: EmptyRequest
- Response: GetDeploymentContextResponse
- Contains AzureContext:
scopewith:tenantId(string)subscriptionId(string)location(string)resourceGroup(string)
resources(repeated string)
- Contains AzureContext:
Prompt Service
This service manages user prompt interactions for subscriptions, locations, resources, and confirmations.
See prompt.proto for more details.
PromptSubscription
Prompts the user to select a subscription.
- Request: PromptSubscriptionRequest (empty)
- Response: PromptSubscriptionResponse
- Contains Subscription
PromptLocation
Prompts the user to select a location.
- Request: PromptLocationRequest
azure_context(AzureContext)
- Response: PromptLocationResponse
- Contains Location
PromptResourceGroup
Prompts the user to select a resource group.
- Request: PromptResourceGroupRequest
azure_context(AzureContext)
- Response: PromptResourceGroupResponse
- Contains ResourceGroup
Confirm
Prompts the user to confirm an action.
- Request: ConfirmRequest
options(ConfirmOptions) with:default_value(optional bool)message(string)helpMessage(string)hint(string)placeholder(string)
- Response: ConfirmResponse
- Contains an optional
value(bool)
- Contains an optional
Prompt
Prompts the user for text input.
- Request: PromptRequest
options(PromptOptions) with:message(string)help_message(string)hint(string)placeholder(string)validation_message(string)required_message(string)required(bool)defaultValue(string)clear_on_completion(bool)ignore_hint_keys(bool)secret(bool): When true, the typed value is masked as*in the terminal and?is treated as an input character (so it can be part of the secret) instead of triggering the help message; the auto-generated[Type ? for hint]affordance is therefore not shown. Use this for passwords, API keys, and other sensitive input.
- Response: PromptResponse
- Contains
value(string)
- Contains
Select
Prompts the user to select an option from a list.
- Request: SelectRequest
options(SelectOptions) with:SelectedIndex(optional int32)message(string)allowed(repeated string)help_message(string)hint(string)display_count(int32)display_numbers(optional bool)enable_filtering(optional bool)
- Response: SelectResponse
- Contains an optional
value(int32)
- Contains an optional
MultiSelect
Prompts the user to select multiple options from a list.
- Request: MultiSelectRequest
options(MultiSelectOptions) with:message(string)choices(repeated MultiSelectChoice) with:value(string): The actual valuedisplay(string): Display text for the choiceselected(bool): Whether initially selected
help_message(string)hint(string)display_count(int32)
- Response: MultiSelectResponse
- Contains a list of selected MultiSelectChoice items
Example Usage (Go):
// Prompt for multiple environment selections
ctx := azdext.WithAccessToken(cmd.Context())
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()
choices := []*azdext.MultiSelectChoice{
{Value: "dev", Display: "Development Environment", Selected: true},
{Value: "staging", Display: "Staging Environment", Selected: false},
{Value: "prod", Display: "Production Environment", Selected: false},
}
response, err := azdClient.Prompt().MultiSelect(ctx, &azdext.MultiSelectRequest{
Options: &azdext.MultiSelectOptions{
Message: "Select environments to deploy to:",
Choices: choices,
HelpMessage: "Choose one or more environments for deployment",
Hint: "Use space to select, enter to confirm",
},
})
if err != nil {
return fmt.Errorf("failed to prompt for environments: %w", err)
}
for _, choice := range response.Values {
fmt.Printf("Selected: %s (%s)\n", choice.Display, choice.Value)
}
PromptSubscriptionResource
Prompts the user to select a resource from a subscription.
- Request: PromptSubscriptionResourceRequest with:
azure_context(AzureContext)options(PromptResourceOptions) with:resource_type(string)kinds(repeated string)resource_type_display_name(string)select_options(PromptResourceSelectOptions)
- Response: PromptSubscriptionResourceResponse
- Contains ResourceExtended
PromptResourceGroupResource
Prompts the user to select a resource from a resource group.
- Request: PromptResourceGroupResourceRequest with:
azure_context(AzureContext)options(PromptResourceOptions) (same structure as above)
- Response: PromptResourceGroupResourceResponse
- Contains ResourceExtended
PromptAiModel
Prompts the user to select an AI model from the AI catalog.
- Request: PromptAiModelRequest
azure_context(AzureContext) withscope.subscription_idrequiredfilter(AiModelFilterOptions): optional model filtersselect_options(SelectOptions): optional prompt customization (currentlymessageoverride)quota(QuotaCheckOptions): optional quota-aware filtering
- Response: PromptAiModelResponse
- Contains
model(AiModel)
- Contains
Effective location is defined by filter.locations.
When filter.locations is empty, models are considered across subscription locations.
When quota is set:
- if
filter.locationsis empty, quota is evaluated across available locations - if
filter.locationscontains exactly one location, quota is evaluated at that location - if
filter.locationscontains multiple locations, quota is evaluated across that provided location set Models are kept when quota is sufficient in at least one effective location. Effective location only controls model eligibility for selection; returnedmodel.locationsremains canonical.
PromptAiDeployment
Prompts the user to select deployment configuration (version, SKU, and capacity).
- Request: PromptAiDeploymentRequest
azure_context(AzureContext) withscope.subscription_idrequiredmodel_name(string): target modelquota(QuotaCheckOptions): optional quota-aware filteringoptions(AiModelDeploymentOptions): optional filters for locations/versions/skus/capacityuse_default_version(bool): use default version when available; otherwise prompt for versionuse_default_capacity(bool): skip capacity prompt when trueinclude_finetune_skus(bool): include fine-tune SKUs
- Response: PromptAiDeploymentResponse
- Contains
deployment(AiModelDeployment)
- Contains
Effective location is defined by options.locations.
When options.locations is empty, model catalog is considered across subscription locations.
When quota is set, exactly one effective location is required via options.locations.
SKU selection is always prompted when one or more valid SKU candidates are available.
PromptAiLocationWithQuota
Prompts the user to select a location that satisfies quota requirements.
- Request: PromptAiLocationWithQuotaRequest
azure_context(AzureContext) withscope.subscription_idrequiredrequirements(repeated QuotaRequirement): usage meter requirementsallowed_locations(repeated string): optional allowed location filterselect_options(SelectOptions): optional prompt customization (currentlymessageoverride)
- Response: PromptAiLocationWithQuotaResponse
- Contains
location(Location)
- Contains
PromptAiModelLocationWithQuota
Prompts the user to select a location for a specific model and shows quota available in list labels.
- Request: PromptAiModelLocationWithQuotaRequest
azure_context(AzureContext) withscope.subscription_idrequiredmodel_name(string): required model nameallowed_locations(repeated string): optional location filterquota(QuotaCheckOptions): optional minimum available requirement (defaults to 1)select_options(SelectOptions): optional prompt customization (currentlymessageoverride)
- Response: PromptAiModelLocationWithQuotaResponse
- Contains
location(Location) andmax_remaining_quota(double, maximum quota available across model SKUs)
- Contains
AI Model Service
This service provides non-interactive AI catalog, deployment resolution, and quota usage primitives.
See ai_model.proto for more details.
ListModels
Returns available AI models for a subscription.
- Request: ListModelsRequest
azure_context(AzureContext) withscope.subscription_idrequiredfilter(AiModelFilterOptions), optional:locations(repeated string)capabilities(repeated string)formats(repeated string)statuses(repeated string, applied to version lifecycle status before aggregation)exclude_model_names(repeated string)
- Response: ListModelsResponse
models(repeated AiModel)
filter.statuses matches version-level lifecycle status before aggregation. Returned models
only contain versions (and locations) that matched. AiModel.lifecycle_status is deprecated
and always empty; use AiModelVersion.lifecycle_status for lifecycle state.
If filter.locations is empty, models are listed across all subscription locations.
When filter.locations is provided, it limits which models are returned, but each returned model still contains canonical
locations.
ResolveModelDeployments
Resolves valid deployment configurations for a model.
- Request: ResolveModelDeploymentsRequest
azure_context(AzureContext) withscope.subscription_idrequiredmodel_name(string)options(AiModelDeploymentOptions), optional:locations(repeated string)versions(repeated string)skus(repeated string)capacity(optional int32)
quota(QuotaCheckOptions), optional:min_remaining_capacity(double)
- Response: ResolveModelDeploymentsResponse
deployments(repeated AiModelDeployment)- each deployment includes model/version/location/SKU/capacity and optional
remaining_quota
- each deployment includes model/version/location/SKU/capacity and optional
If options.locations is empty, all subscription locations are considered.
When quota is set, options.locations must contain exactly one location.
ListUsages
Returns usage meter data for a specific location.
- Request: ListUsagesRequest
azure_context(AzureContext) withscope.subscription_idrequiredlocation(string), required (no fallback fromazure_context.scope.location)
- Response: ListUsagesResponse
usages(repeated AiModelUsage) with:name(string)current_value(double)limit(double)
ListLocationsWithQuota
Returns locations that satisfy all provided quota requirements.
- Request: ListLocationsWithQuotaRequest
azure_context(AzureContext) withscope.subscription_idrequiredrequirements(repeated QuotaRequirement)allowed_locations(repeated string), optional
- Response: ListLocationsWithQuotaResponse
locations(repeated Location)
ListModelLocationsWithQuota
Returns locations where a model has sufficient quota, with per-location available quota.
- Request: ListModelLocationsWithQuotaRequest
azure_context(AzureContext) withscope.subscription_idrequiredmodel_name(string), requiredallowed_locations(repeated string), optionalquota(QuotaCheckOptions), optional (min_remaining_capacitydefaults to1)
- Response: ListModelLocationsWithQuotaResponse
locations(repeated ModelLocationQuota)- each entry includes
location(Location) andmax_remaining_quota(double, maximum quota available)
- each entry includes
AI Error Reasons
AI model and AI prompt APIs return structured gRPC errors with ErrorInfo:
domain:azd.aireason: one of:AI_MISSING_SUBSCRIPTIONAI_LOCATION_REQUIREDAI_QUOTA_LOCATION_REQUIREDAI_MODEL_NOT_FOUNDAI_NO_MODELS_MATCHAI_NO_DEPLOYMENT_MATCHAI_NO_VALID_SKUSAI_NO_LOCATIONS_WITH_QUOTAAI_INVALID_CAPACITYAI_INTERACTIVE_REQUIRED
Some reasons include ErrorInfo.metadata for additional context (for example model_name, version, sku).
Extensions should prefer ErrorInfo.reason over parsing error text when handling recoverable branches.
Example Usage (Go):
ctx := azdext.WithAccessToken(cmd.Context())
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()
azureContext := &azdext.AzureContext{
Scope: &azdext.AzureScope{
SubscriptionId: "<subscription-id>",
Location: "eastus2",
},
}
modelResp, err := azdClient.Prompt().PromptAiModel(ctx, &azdext.PromptAiModelRequest{
AzureContext: azureContext,
Filter: &azdext.AiModelFilterOptions{
Locations: []string{"eastus2"},
Capabilities: []string{"chatCompletion"},
},
Quota: &azdext.QuotaCheckOptions{MinRemainingCapacity: 1},
})
if err != nil {
return fmt.Errorf("selecting model: %w", err)
}
deploymentResp, err := azdClient.Prompt().PromptAiDeployment(ctx, &azdext.PromptAiDeploymentRequest{
AzureContext: azureContext,
ModelName: modelResp.Model.Name,
Options: &azdext.AiModelDeploymentOptions{
Locations: []string{"eastus2"},
},
Quota: &azdext.QuotaCheckOptions{MinRemainingCapacity: 1},
})
if err != nil {
return fmt.Errorf("selecting deployment: %w", err)
}
fmt.Printf("Selected deployment: %s %s %s\n",
deploymentResp.Deployment.ModelName,
deploymentResp.Deployment.Version,
deploymentResp.Deployment.Sku.Name)
Event Service
This service defines methods for event subscription, invocation, and status updates.
Clients can subscribe to events and receive notifications via a bidirectional stream.
EventStream
- Establishes a bidirectional stream that enables clients to:
- Subscribe to project and service events.
- Invoke event handlers.
- Send status updates regarding event processing.
See event.proto for more details.
Message Types
-
EventMessage Encapsulates a single event payload among several possible types.
Contains:
- Uses a oneof field to encapsulate different event types.
-
ExtensionReadyEvent Signals that an extension is ready, including any status updates.
Contains:
status: Indicates the readiness state of the extension.message: Provides additional details.
-
SubscribeProjectEvent Allows clients to subscribe to events specific to project lifecycle changes.
Contains:
event_names: A list of event names to subscribe to for project events.
-
SubscribeServiceEvent Allows clients to subscribe to service-specific events along with context details.
Contains:
event_names: A list of event names to subscribe to for service events.language: The language of the service.host: The host identifier.
-
InvokeProjectHandler Instructs the invocation of a project event handler with configuration details.
Contains:
event_name: The name of the event being invoked.project: The project configuration.
-
InvokeServiceHandler Instructs the invocation of a service event handler including associated configurations.
Contains:
event_name: The name of the event being invoked.project: The project configuration.service: The specific service configuration.service_context: The service context containing artifacts from all lifecycle phases (restore, build, package, publish, deploy).
-
ProjectHandlerStatus Provides status updates for project events.
Contains:
event_name: The event name for which this status update applies.status: Status such as "running", "completed", or "failed".message: Optional additional details.
-
ServiceHandlerStatus Provides status updates for service events.
Contains:
event_name: The event name for which this status update applies.service_name: The name of the service.status: Status such as "running", "completed", or "failed".message: Optional additional details.
ServiceContext and Service Event Arguments
When service events are invoked, extensions receive a ServiceEventArgs structure that includes:
Project: The current project configurationService: The specific service configurationServiceContext: Artifacts accumulated across all service lifecycle phases
ServiceContext Structure:
type ServiceContext struct {
Restore []*Artifact // Artifacts from restore phase
Build []*Artifact // Artifacts from build phase
Package []*Artifact // Artifacts from package phase
Publish []*Artifact // Artifacts from publish phase
Deploy []*Artifact // Artifacts from deploy phase
}
Artifact Definition:
type Artifact struct {
Kind ArtifactKind // Type of artifact (Directory, Config, Archive, Container, etc.)
Path string // Location of the artifact
LocationKind LocationKind // Whether it's local, remote, or service-based
}
Artifact Kinds:
ARTIFACT_KIND_DIRECTORY: Directory containing project or build artifactsARTIFACT_KIND_CONFIG: Configuration fileARTIFACT_KIND_ARCHIVE: Zip/archive packageARTIFACT_KIND_CONTAINER: Docker/container imageARTIFACT_KIND_ENDPOINT: Service endpoint URLARTIFACT_KIND_DEPLOYMENT: Deployment result or endpointARTIFACT_KIND_RESOURCE: Azure Resource
Example: Using ServiceContext in Event Handlers
host := azdext.NewExtensionHost(azdClient).
WithServiceEventHandler("prepackage", func(ctx context.Context, args *azdext.ServiceEventArgs) error {
// Access build artifacts from previous phase
for _, artifact := range args.ServiceContext.Build {
fmt.Printf("Build artifact: %s (kind: %s)\n", artifact.Path, artifact.Kind)
}
// Access restore artifacts
for _, artifact := range args.ServiceContext.Restore {
fmt.Printf("Restore artifact: %s\n", artifact.Path)
}
return nil
}, nil).
WithServiceEventHandler("postdeploy", func(ctx context.Context, args *azdext.ServiceEventArgs) error {
// Access deployment results
for _, artifact := range args.ServiceContext.Deploy {
if artifact.Kind == azdext.ARTIFACT_KIND_ENDPOINT {
fmt.Printf("Service deployed to: %s\n", artifact.Path)
}
}
return nil
}, nil)
Container Service
This service provides container build, package, and publish operations for extensions that need to work with containers but don't want to implement the full complexity of Docker CLI integration, registry authentication, etc.
See container.proto for more details.
Build
Builds a service's container image.
- Request: ContainerBuildRequest
- Contains:
service_name(string): Name of the service to buildservice_context(ServiceContext): Current service context with artifacts
- Contains:
- Response: ContainerBuildResponse
- Contains:
result(ServiceBuildResult): Build result with artifacts
- Contains:
Example Usage (Go):
// Build a container for a service
buildResponse, err := azdClient.Container().Build(ctx, &azdext.ContainerBuildRequest{
ServiceName: "api",
ServiceContext: &azdext.ServiceContext{
Restore: restoreArtifacts,
Build: buildArtifacts,
},
})
if err != nil {
return fmt.Errorf("failed to build container: %w", err)
}
// Access build artifacts
for _, artifact := range buildResponse.Result.Artifacts {
fmt.Printf("Built artifact: %s\n", artifact.Path)
}
Package
Packages a service's container for deployment.
- Request: ContainerPackageRequest
- Contains:
service_name(string): Name of the service to packageservice_context(ServiceContext): Current service context with artifacts
- Contains:
- Response: ContainerPackageResponse
- Contains:
result(ServicePackageResult): Package result with artifacts
- Contains:
Publish
Publishes a container service to a registry.
- Request: ContainerPublishRequest
- Contains:
service_name(string): Name of the service to publishservice_context(ServiceContext): Current service context with artifacts
- Contains:
- Response: ContainerPublishResponse
- Contains:
result(ServicePublishResult): Publish result with artifacts
- Contains:
Complete Container Workflow Example (Go):
ctx := azdext.WithAccessToken(cmd.Context())
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()
serviceName := "web-api"
serviceContext := &azdext.ServiceContext{}
// Build the container
buildResp, err := azdClient.Container().Build(ctx, &azdext.ContainerBuildRequest{
ServiceName: serviceName,
ServiceContext: serviceContext,
})
if err != nil {
return fmt.Errorf("container build failed: %w", err)
}
// Update context with build artifacts
serviceContext.Build = buildResp.Result.Artifacts
// Package the container
packageResp, err := azdClient.Container().Package(ctx, &azdext.ContainerPackageRequest{
ServiceName: serviceName,
ServiceContext: serviceContext,
})
if err != nil {
return fmt.Errorf("container package failed: %w", err)
}
// Update context with package artifacts
serviceContext.Package = packageResp.Result.Artifacts
// Publish the container
publishResp, err := azdClient.Container().Publish(ctx, &azdext.ContainerPublishRequest{
ServiceName: serviceName,
ServiceContext: serviceContext,
})
if err != nil {
return fmt.Errorf("container publish failed: %w", err)
}
fmt.Printf("Container published successfully with %d artifacts\n", len(publishResp.Result.Artifacts))
Framework Service
This service handles language and framework-specific operations like restore, build, and package for services. Extensions can register framework service providers to handle custom languages or override default behavior.
See framework_service.proto for more details.
Provider Interface
Framework service providers implement the FrameworkServiceProvider interface:
type FrameworkServiceProvider interface {
Initialize(ctx context.Context, serviceConfig *ServiceConfig) error
RequiredExternalTools(ctx context.Context, serviceConfig *ServiceConfig) ([]*ExternalTool, error)
Requirements() (*FrameworkRequirements, error)
Restore(ctx context.Context, serviceConfig *ServiceConfig, serviceContext *ServiceContext, progress ProgressReporter) (*ServiceRestoreResult, error)
Build(ctx context.Context, serviceConfig *ServiceConfig, serviceContext *ServiceContext, progress ProgressReporter) (*ServiceBuildResult, error)
Package(ctx context.Context, serviceConfig *ServiceConfig, serviceContext *ServiceContext, progress ProgressReporter) (*ServicePackageResult, error)
}
Stream
The framework service uses a bidirectional stream for communication between azd and the extension.
- Request/Response: FrameworkServiceMessage (bidirectional stream)
- Contains various message types:
RegisterFrameworkServiceRequest/Response: Register a framework providerFrameworkServiceInitializeRequest/Response: Initialize the serviceFrameworkServiceRequiredExternalToolsRequest/Response: Get required toolsFrameworkServiceRequirementsRequest/Response: Get framework requirementsFrameworkServiceRestoreRequest/Response: Restore dependenciesFrameworkServiceBuildRequest/Response: Build the serviceFrameworkServicePackageRequest/Response: Package the service
- Contains various message types:
Example: Registering a Framework Service Provider (Go):
// Custom Rust framework provider
type RustFrameworkProvider struct{}
func (r *RustFrameworkProvider) Initialize(ctx context.Context, serviceConfig *azdext.ServiceConfig) error {
// Initialize Rust-specific settings
return nil
}
func (r *RustFrameworkProvider) RequiredExternalTools(ctx context.Context, serviceConfig *azdext.ServiceConfig) ([]*azdext.ExternalTool, error) {
return []*azdext.ExternalTool{
{
Name: "cargo",
InstallUrl: "https://rustup.rs/",
},
}, nil
}
func (r *RustFrameworkProvider) Requirements() (*azdext.FrameworkRequirements, error) {
return &azdext.FrameworkRequirements{
Package: &azdext.FrameworkPackageRequirements{
RequireRestore: true,
RequireBuild: true,
},
}, nil
}
func (r *RustFrameworkProvider) Restore(ctx context.Context, serviceConfig *azdext.ServiceConfig, serviceContext *azdext.ServiceContext, progress azdext.ProgressReporter) (*azdext.ServiceRestoreResult, error) {
// Run cargo fetch or similar
return &azdext.ServiceRestoreResult{
Artifacts: []*azdext.Artifact{
{
Kind: azdext.ARTIFACT_KIND_DIRECTORY,
Path: "target/deps",
},
},
}, nil
}
func (r *RustFrameworkProvider) Build(ctx context.Context, serviceConfig *azdext.ServiceConfig, serviceContext *azdext.ServiceContext, progress azdext.ProgressReporter) (*azdext.ServiceBuildResult, error) {
// Run cargo build
return &azdext.ServiceBuildResult{
Artifacts: []*azdext.Artifact{
{
Kind: azdext.ARTIFACT_KIND_DIRECTORY,
Path: "target/release",
},
},
}, nil
}
func (r *RustFrameworkProvider) Package(ctx context.Context, serviceConfig *azdext.ServiceConfig, serviceContext *azdext.ServiceContext, progress azdext.ProgressReporter) (*azdext.ServicePackageResult, error) {
// Package Rust application
return &azdext.ServicePackageResult{
Artifacts: []*azdext.Artifact{
{
Kind: azdext.ARTIFACT_KIND_ARCHIVE,
Path: "dist/app.tar.gz",
},
},
}, nil
}
// Register the framework provider
func main() {
ctx := azdext.WithAccessToken(azdext.NewContext())
azdClient, err := azdext.NewAzdClient()
if err != nil {
log.Fatal(err)
}
defer azdClient.Close()
host := azdext.NewExtensionHost(azdClient).
WithFrameworkService("rust", func() azdext.FrameworkServiceProvider {
return &RustFrameworkProvider{}
})
if err := host.Run(ctx); err != nil {
log.Fatalf("failed to run extension: %v", err)
}
}
Service Target Service
This service handles the full deployment lifecycle for services, including packaging, publishing, and deploying to Azure resources. Extensions can register service target providers for custom deployment scenarios.
See service_target.proto for more details.
Provider Interface
Service target providers implement the ServiceTargetProvider interface:
type ServiceTargetProvider interface {
Initialize(ctx context.Context, serviceConfig *ServiceConfig) error
GetTargetResource(ctx context.Context, subscriptionId string, serviceConfig *ServiceConfig) (*TargetResource, error)
Package(ctx context.Context, serviceConfig *ServiceConfig, frameworkPackage *ServicePackageResult, progress ProgressReporter) (*ServicePackageResult, error)
Publish(ctx context.Context, serviceConfig *ServiceConfig, servicePackage *ServicePackageResult, targetResource *TargetResource, progress ProgressReporter) (*ServicePublishResult, error)
Deploy(ctx context.Context, serviceConfig *ServiceConfig, servicePackage *ServicePackageResult, servicePublish *ServicePublishResult, targetResource *TargetResource, progress ProgressReporter) (*ServiceDeployResult, error)
Endpoints(ctx context.Context, serviceConfig *ServiceConfig, targetResource *TargetResource) ([]string, error)
}
Stream
The service target service uses a bidirectional stream for communication between azd and the extension.
- Request/Response: ServiceTargetMessage (bidirectional stream)
- Contains various message types:
RegisterServiceTargetRequest/Response: Register a service target providerServiceTargetInitializeRequest/Response: Initialize the service targetGetTargetResourceRequest/Response: Get the target Azure resourceServiceTargetPackageRequest/Response: Package the serviceServiceTargetPublishRequest/Response: Publish the serviceServiceTargetDeployRequest/Response: Deploy the serviceServiceTargetEndpointsRequest/Response: Get service endpoints
- Contains various message types:
Example: Custom Service Target Provider (Go):
// Custom VM service target provider
type VMServiceTargetProvider struct{}
func (v *VMServiceTargetProvider) Initialize(ctx context.Context, serviceConfig *azdext.ServiceConfig) error {
// Initialize VM-specific settings
return nil
}
func (v *VMServiceTargetProvider) GetTargetResource(ctx context.Context, subscriptionId string, serviceConfig *azdext.ServiceConfig) (*azdext.TargetResource, error) {
return &azdext.TargetResource{
ResourceId: fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s",
subscriptionId, serviceConfig.ResourceGroupName, serviceConfig.ResourceName),
}, nil
}
func (v *VMServiceTargetProvider) Package(ctx context.Context, serviceConfig *azdext.ServiceConfig, frameworkPackage *azdext.ServicePackageResult, progress azdext.ProgressReporter) (*azdext.ServicePackageResult, error) {
// Create deployment package for VM
return &azdext.ServicePackageResult{
Artifacts: []*azdext.Artifact{
{
Kind: azdext.ARTIFACT_KIND_ARCHIVE,
Path: "vm-deploy.zip",
},
},
}, nil
}
func (v *VMServiceTargetProvider) Publish(ctx context.Context, serviceConfig *azdext.ServiceConfig, servicePackage *azdext.ServicePackageResult, targetResource *azdext.TargetResource, progress azdext.ProgressReporter) (*azdext.ServicePublishResult, error) {
// Upload package to storage or registry
return &azdext.ServicePublishResult{
Artifacts: []*azdext.Artifact{
{
Kind: azdext.ARTIFACT_KIND_ENDPOINT,
Path: "https://storage.azure.com/packages/vm-deploy.zip",
},
},
}, nil
}
func (v *VMServiceTargetProvider) Deploy(ctx context.Context, serviceConfig *azdext.ServiceConfig, servicePackage *azdext.ServicePackageResult, servicePublish *azdext.ServicePublishResult, targetResource *azdext.TargetResource, progress azdext.ProgressReporter) (*azdext.ServiceDeployResult, error) {
// Deploy to VM using scripts or ARM templates
return &azdext.ServiceDeployResult{
Artifacts: []*azdext.Artifact{
{
Kind: azdext.ARTIFACT_KIND_DEPLOYMENT,
Path: "deployment-12345",
},
},
}, nil
}
func (v *VMServiceTargetProvider) Endpoints(ctx context.Context, serviceConfig *azdext.ServiceConfig, targetResource *azdext.TargetResource) ([]string, error) {
// Return VM endpoints
return []string{"https://myvm.azure.com:8080"}, nil
}
// Register the service target provider
func main() {
ctx := azdext.WithAccessToken(azdext.NewContext())
azdClient, err := azdext.NewAzdClient()
if err != nil {
log.Fatal(err)
}
defer azdClient.Close()
host := azdext.NewExtensionHost(azdClient).
WithServiceTarget("vm", func() azdext.ServiceTargetProvider {
return &VMServiceTargetProvider{}
})
if err := host.Run(ctx); err != nil {
log.Fatalf("failed to run extension: %v", err)
}
}
Compose Service
This service manages composability resources in an azd project.
See compose.proto for more details.
ListResources
Lists all configured composability resources.
- Request: EmptyRequest
- Response: ListResourcesResponse
- Contains a list of ComposedResource
GetResource
Retrieves the configuration of a specific composability resource.
- Request: GetResourceRequest
- Contains:
name(string)
- Contains:
- Response: GetResourceResponse
- Contains:
resource: ComposedResource
- Contains:
ListResourceTypes
Lists all supported composability resource types.
- Request: EmptyRequest
- Response: ListResourceTypesResponse
- Contains a list of ComposedResourceType
GetResourceType
Retrieves the schema of a specific composability resource type.
- Request: GetResourceTypeRequest
- Contains:
type_name(string)
- Contains:
- Response: GetResourceTypeResponse
- Contains:
resource_type: ComposedResourceType
- Contains:
AddResource
Adds a new composability resource to the project.
- Request: AddResourceRequest
- Contains:
resource: ComposedResource
- Contains:
- Response: AddResourceResponse
- Contains:
resource: ComposedResource
- Contains:
Workflow Service
This service executes workflows defined within the project.
See workflow.proto for more details.
Run
Executes a workflow consisting of sequential steps.
- Request: RunWorkflowRequest
- Contains:
workflow: Workflow (withnameandsteps)
- Contains:
- Response: EmptyResponse
Account Service
This service provides information about the currently logged-in user or identity.
See account.proto for more details.
ListSubscriptions
Lists all subscriptions accessible by the current account.
- Request: ListSubscriptionsRequest
- Contains:
tenant_id(optional string): Filter subscriptions by tenant ID
- Contains:
- Response: ListSubscriptionsResponse
- Contains a list of Subscription:
id(string): Subscription ID (GUID)name(string): Subscription display nametenant_id(string): The tenant that owns the subscriptionuser_tenant_id(string): The tenant under which the user has access to the subscriptionis_default(bool): Whether this is the user's default subscription
- Contains a list of Subscription:
Example Usage (Go):
ctx := azdext.WithAccessToken(cmd.Context())
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()
// List all subscriptions
response, err := azdClient.Account().ListSubscriptions(ctx, &azdext.ListSubscriptionsRequest{})
if err != nil {
return fmt.Errorf("failed to list subscriptions: %w", err)
}
fmt.Printf("Found %d subscriptions:\n", len(response.Subscriptions))
for _, sub := range response.Subscriptions {
defaultMarker := ""
if sub.IsDefault {
defaultMarker = " (default)"
}
fmt.Printf(" %s (%s)%s\n", sub.Name, sub.Id, defaultMarker)
fmt.Printf(" Tenant: %s\n", sub.TenantId)
if sub.UserTenantId != sub.TenantId {
fmt.Printf(" User Tenant: %s\n", sub.UserTenantId)
}
}
// Filter by tenant ID
tenantId := "your-tenant-id"
filteredResponse, err := azdClient.Account().ListSubscriptions(ctx, &azdext.ListSubscriptionsRequest{
TenantId: &tenantId,
})
if err != nil {
return fmt.Errorf("failed to list subscriptions for tenant: %w", err)
}
fmt.Printf("Subscriptions in tenant %s: %d\n", tenantId, len(filteredResponse.Subscriptions))
Use Cases:
- List available subscriptions for user selection
- Filter subscriptions by tenant in multi-tenant scenarios
- Display subscription details for user confirmation
- Find default subscription for automated operations
LookupTenant
Resolves the tenant ID required by the current account to access a given subscription. This is useful in multi-tenant scenarios where a user may have access to subscriptions through different tenants.
- Request: LookupTenantRequest
- Contains:
subscription_id(string): The subscription ID to look up
- Contains:
- Response: LookupTenantResponse
- Contains:
tenant_id(string): The tenant ID that provides access to the subscription
- Contains:
Example Usage (Go):
// Look up the tenant for a specific subscription
subscriptionId := "12345678-1234-1234-1234-123456789abc"
response, err := azdClient.Account().LookupTenant(ctx, &azdext.LookupTenantRequest{
SubscriptionId: subscriptionId,
})
if err != nil {
return fmt.Errorf("failed to lookup tenant for subscription %s: %w", subscriptionId, err)
}
fmt.Printf("Subscription %s requires tenant %s for access\n", subscriptionId, response.TenantId)
// Complete example: Get subscription info and tenant
func getSubscriptionDetails(ctx context.Context, azdClient *azdext.AzdClient, subscriptionId string) error {
// First lookup the required tenant
tenantResp, err := azdClient.Account().LookupTenant(ctx, &azdext.LookupTenantRequest{
SubscriptionId: subscriptionId,
})
if err != nil {
return fmt.Errorf("failed to lookup tenant: %w", err)
}
// Then get subscription details using the tenant filter
subsResp, err := azdClient.Account().ListSubscriptions(ctx, &azdext.ListSubscriptionsRequest{
TenantId: &tenantResp.TenantId,
})
if err != nil {
return fmt.Errorf("failed to list subscriptions: %w", err)
}
// Find the specific subscription
for _, sub := range subsResp.Subscriptions {
if sub.Id == subscriptionId {
fmt.Printf("Subscription Details:\n")
fmt.Printf(" Name: %s\n", sub.Name)
fmt.Printf(" ID: %s\n", sub.Id)
fmt.Printf(" Tenant: %s\n", sub.TenantId)
fmt.Printf(" User Tenant: %s\n", sub.UserTenantId)
fmt.Printf(" Is Default: %t\n", sub.IsDefault)
break
}
}
return nil
}
Use Cases:
- Determine which tenant ID to use when making Azure API calls for a subscription
- Handle multi-tenant scenarios where users access subscriptions through different tenants
- Validate subscription access before performing operations
- Set up proper authentication context for Azure SDK calls
Copilot Service
This service provides Copilot agent capabilities to extensions. Sessions are created lazily on the first SendMessage call and can be reused across multiple calls. Sessions run in headless/autopilot mode by default when invoked via gRPC, suppressing all console output.
See copilot.proto for more details.
Initialize
Starts the Copilot client, verifies authentication, and resolves model/reasoning configuration. Call before SendMessage to warm up the client and validate settings. This method is idempotent.
- Request: InitializeCopilotRequest
- Contains:
model(string): Model to configure (empty = use existing/default)reasoning_effort(string): Reasoning effort level (low,medium,high)
- Contains:
- Response: InitializeCopilotResponse
- Contains:
model(string): Resolved model namereasoning_effort(string): Resolved reasoning effort levelis_first_run(bool): True if this was the first configuration
- Contains:
ListSessions
Returns available Copilot sessions for a working directory.
- Request: ListCopilotSessionsRequest
- Contains:
working_directory(string): Directory to list sessions for (empty = current working directory)
- Contains:
- Response: ListCopilotSessionsResponse
- Contains a list of CopilotSessionMetadata:
session_id(string): Unique session identifiermodified_time(string): Last modified time (RFC 3339 format)summary(string): Optional session summary
- Contains a list of CopilotSessionMetadata:
SendMessage
Sends a prompt to the Copilot agent. On the first call, a new session is created using the provided configuration. If session_id is set, that existing session is resumed instead. Subsequent calls with the returned session_id reuse the same session without re-bootstrapping.
- Request: SendCopilotMessageRequest
- Contains:
prompt(string): The prompt/message to sendsession_id(string, optional): Session to reuse or resumemodel(string, optional): Model override (first call or resume)reasoning_effort(string, optional): Reasoning effort (low,medium,high)system_message(string, optional): Custom system message appended to the defaultmode(string, optional): Agent mode (autopilot,interactive,plan)debug(bool, optional): Enable debug loggingheadless(bool, optional): Set to true for headless mode (suppresses console output)
- Contains:
- Response: SendCopilotMessageResponse
- Contains:
session_id(string): Session ID — use in subsequent calls to reuse the sessionusage(CopilotUsageMetrics): Usage metrics for this message turnfile_changes(repeated CopilotFileChange): Files changed during this message turn
- Contains:
GetUsageMetrics
Returns cumulative usage metrics cached for a session.
- Request: GetCopilotUsageMetricsRequest
- Contains:
session_id(string): Session to get metrics for
- Contains:
- Response: GetCopilotUsageMetricsResponse
- Contains CopilotUsageMetrics:
model(string): Model usedinput_tokens(double): Total input tokens consumedoutput_tokens(double): Total output tokens consumedtotal_tokens(double): Sum of input + output tokensbilling_rate(double): Per-request cost multiplier (e.g., 1.0x, 2.0x)premium_requests(double): Number of premium requests usedduration_ms(double): Total API duration in milliseconds
- Contains CopilotUsageMetrics:
GetFileChanges
Returns files created, modified, or deleted during a session.
- Request: GetCopilotFileChangesRequest
- Contains:
session_id(string): Session to get file changes for
- Contains:
- Response: GetCopilotFileChangesResponse
- Contains a list of CopilotFileChange:
path(string): File path (relative to working directory)change_type(CopilotFileChangeType): One ofCREATED,MODIFIED,DELETED
- Contains a list of CopilotFileChange:
StopSession
Stops and cleans up a Copilot agent session.
- Request: StopCopilotSessionRequest
- Contains:
session_id(string): Session to stop
- Contains:
- Response: EmptyResponse
GetMessages
Returns the session event log from the Copilot SDK. Each event contains a type, timestamp, and dynamic data as a google.protobuf.Struct.
- Request: GetCopilotMessagesRequest
- Contains:
session_id(string): Session to get messages for
- Contains:
- Response: GetCopilotMessagesResponse
- Contains a list of CopilotSessionEvent:
type(string): Event type (e.g.,assistant.message,tool.execution_complete)timestamp(string): ISO 8601 timestampdata(google.protobuf.Struct): Full event data as a dynamic struct
- Contains a list of CopilotSessionEvent:
Example Usage (Go):
ctx := azdext.WithAccessToken(cmd.Context())
azdClient, err := azdext.NewAzdClient()
if err != nil {
return fmt.Errorf("failed to create azd client: %w", err)
}
defer azdClient.Close()
copilot := azdClient.Copilot()
// Optional: warm up the client and resolve configuration
initResp, err := copilot.Initialize(ctx, &azdext.InitializeCopilotRequest{
Model: "gpt-4o",
ReasoningEffort: "medium",
})
if err != nil {
return fmt.Errorf("failed to initialize copilot: %w", err)
}
fmt.Printf("Model: %s, Reasoning: %s\n", initResp.Model, initResp.ReasoningEffort)
// Send the first message — creates a new session
sendResp, err := copilot.SendMessage(ctx, &azdext.SendCopilotMessageRequest{
Prompt: "Add a health check endpoint to the API",
Mode: "autopilot",
Headless: true,
SystemMessage: "You are an expert Go developer.",
})
if err != nil {
return fmt.Errorf("failed to send message: %w", err)
}
// Capture the session ID for subsequent calls
sessionID := sendResp.SessionId
// Send a follow-up message in the same session
followUp, err := copilot.SendMessage(ctx, &azdext.SendCopilotMessageRequest{
Prompt: "Now add tests for the health check endpoint",
SessionId: sessionID,
})
if err != nil {
return fmt.Errorf("failed to send follow-up: %w", err)
}
fmt.Printf("Turn usage: %.0f tokens\n", followUp.Usage.TotalTokens)
// Retrieve cumulative metrics
metricsResp, err := copilot.GetUsageMetrics(ctx, &azdext.GetCopilotUsageMetricsRequest{
SessionId: sessionID,
})
if err != nil {
return fmt.Errorf("failed to get metrics: %w", err)
}
fmt.Printf("Total tokens: %.0f, Premium requests: %.0f\n",
metricsResp.Usage.TotalTokens, metricsResp.Usage.PremiumRequests)
// Retrieve file changes
changesResp, err := copilot.GetFileChanges(ctx, &azdext.GetCopilotFileChangesRequest{
SessionId: sessionID,
})
if err != nil {
return fmt.Errorf("failed to get file changes: %w", err)
}
for _, change := range changesResp.FileChanges {
fmt.Printf(" %s: %s\n", change.ChangeType, change.Path)
}
// Clean up the session
_, err = copilot.StopSession(ctx, &azdext.StopCopilotSessionRequest{
SessionId: sessionID,
})
if err != nil {
return fmt.Errorf("failed to stop session: %w", err)
}
Use Cases:
- Automate code generation or refactoring tasks within an extension workflow
- Build interactive AI-assisted tools powered by Copilot
- Track token consumption and file modifications during AI-driven operations
- Resume previous sessions for iterative, multi-step tasks
Registry Schema Versioning
The extension registry format includes a schemaVersion field that enables
forward-compatible evolution of the registry schema.
Version Format
Registry schema versions use major.minor format (e.g. "1.0", "1.1", "2.0").
{
"schemaVersion": "1.0",
"extensions": [ ... ]
}
Compatibility Rules
| Scenario | Behavior |
|---|---|
Missing schemaVersion | Treated as "1.0" for backward compatibility |
Same major, newer minor (e.g. "1.1") | Accepted silently — minor bumps are backward compatible |
Newer major (e.g. "2.0") | Rejected with an error and upgrade guidance |
0.x (e.g. "0.1") | Accepted — pre-release schema versions are valid |
| Malformed version string | Rejected with a descriptive parse error |
Upgrade Guidance
When azd encounters a registry with a schema version it cannot support, it will display an error with a suggestion to upgrade:
ERROR: registry schema version 2.0 is not supported (max supported: 1.0)
Suggestion: Upgrade azd to the latest version to use this registry
https://aka.ms/azd/install
For Registry Authors
When publishing a third-party registry:
- Include
schemaVersion: Add"schemaVersion": "1.0"at the top level of your registry JSON. Omitting it works but triggers a validation warning. - Use the JSON schema: Reference
extensions/registry.schema.jsonfor the full format specification. - Minor version bumps add optional fields that older azd versions can safely ignore.
- Major version bumps indicate breaking changes that require a newer azd version.