Extension Telemetry

August 12, 2026 · View on GitHub

This guide is for extension authors whose telemetry has passed privacy review and whose extension is published to the official azd registry. It explains how to ask azd to record a usage signal on the extension's behalf — for example, which deployment mode a user picked.

Important

Any telemetry must go through a privacy review to ensure that it respects user privacy before you start collecting it. See Your responsibility for content below.

Telemetry is a service azd offers to extensions installed from the verified official azd registry source. Call ReportUsage with an event name and the attributes you care about. azd core owns the identity fields, namespaces your attributes, and bounds their size and number. It does not inspect what they mean.

The reserved azd source is eligible only when its name, source type, and normalized URL match the official registry. This is a configuration-based admission check, not a cryptographic provenance guarantee.

See ADR-001 for the reasoning behind this design.

Require a host with telemetry support

A published extension version that depends on this service should declare the first azd release that includes TelemetryService in its extension.yaml:

requiredAzdVersion: ">=1.31.0"

Use the first released azd version containing the service as the lower bound. azd uses this field while resolving extension installs and updates, so new installations do not select the extension on an older host. See Extension Resolution and Versioning for the compatibility behavior.

This version requirement is the normal compatibility mechanism. Keep the RPC call best-effort for already-installed versions and non-registry sources, which may still run on an older host and receive Unimplemented.

What azd records

Attribute nameAttribute value
extension.idID of the current extension, added automatically
extension.versionVersion of the current extension, added automatically
extension.sourceSource of the current extension, added automatically
extension.eventEvent name chosen by the extension
ext.*Unstructured collection of extension-chosen keys and values

The extension chooses the event name and every ext.* key and value. azd enforces the ext. prefix, the bounds below, and the official-registry requirement.

azd cannot write your attribute keys unprefixed, and you cannot overwrite the identity fields — a key of extension.id is recorded as ext.extension.id.

Reporting an event

if _, err := client.Telemetry().ReportUsage(
    ctx,
    &azdext.ReportUsageRequest{
        EventName: "deploy.completed",
        Attributes: map[string]string{
            "deploy.mode": "container",
            "retries":     "2",
        },
    },
); err != nil {
    log.Printf("telemetry unavailable: %v", err)
}

Treat the call as best-effort. A malformed request is a plain error, and an older host can return Unimplemented when the extension was already installed or came from a non-registry source. Log the error, but never let it change command behavior or retry. Report the event immediately after the fact it represents is known, rather than waiting until the command completes, so a later unrelated failure does not lose the signal.

An event with no attributes is valid — "this happened" is a legitimate signal.

There is a complete working example in the demo extension: extensions/microsoft.azd.demo/internal/cmd/telemetry.go, runnable with azd demo telemetry.

Bounds

RuleLimit
Attributes per event32
Event name length1–128 UTF-8 bytes
Attribute key length1–128 UTF-8 bytes
Attribute value length512 UTF-8 bytes
Recorded events per azd invocation100

There are no charset rules. Exceeding a per-event bound rejects the whole call and records nothing, so a partially-valid event never lands as a complete-looking one. The per-invocation budget behaves differently: see When your event is not recorded.

Your responsibility for content

azd does not review your values at runtime. Registry admission is where that review happens, which makes the content rules your responsibility as the extension author:

  • Never send customer content. No file paths, resource names, connection strings, prompts, URLs, or anything a user typed. If you are unsure whether a value qualifies as customer content, always assume that it does to be safe.
  • Keep values low cardinality. Prefer a small enum such as code | container | unknown. Unbounded values make the data expensive and unusable for aggregation.
  • Document your events the way azd core documents its own fields: what each event and attribute means and why you need it.
  • Get a privacy review as part of reviewing your extension, following the telemetry privacy review checklist.

When the call returns an error

StatusCause
UnauthenticatedThe request did not carry the host-issued extension token
PermissionDeniedThe calling extension is not installed
InvalidArgumentThe event name is missing, or a per-event bound was exceeded
UnimplementedThe azd host predates this service

Error messages do not echo attribute values. When a valid key has an oversized value, the key is included so you can identify the failing field; invalid keys are not echoed. Use the status code plus your own call site to diagnose.

When your event is not recorded

Two outcomes are deliberately not errors. The call succeeds and ReportUsageResponse.Accepted is false:

CauseWhy
Your configured source does not match the verified official azd registryAttribute values are never reviewed at runtime, so registry admission is what keeps unchecked content out of azd's pipeline
The per-invocation event budget is spentReportUsage can be called in a loop, and the per-event bounds do not limit how many events arrive

Run azd with --debug to see which one applied.

This means your events are not recorded while you develop locally, because an extension installed with --source dev or from a file path does not pass the gate. You can still verify your integration end to end: the call succeeds and Accepted comes back false. Because it is not an error, your code runs the same path in development as in production — do not branch on Accepted.

Where the data lands

Each accepted event becomes an ext.usage span carrying extension.id, extension.version, extension.source, extension.event, and one ext.<key> attribute per entry in your map. The span shares the command's trace, so it joins to the originating command on operation_Id in Application Insights:

requests
| where name == "ext.usage"
| where customDimensions["extension.id"] == "contoso.tools"
| where customDimensions["extension.event"] == "deploy.completed"
| summarize count() by tostring(customDimensions["ext.deploy.mode"])