Azure Naming Convention — Azure DevOps Extension
An Azure DevOps hub extension that implements an Azure Resource Naming Convention
document as a working tool inside Azure DevOps: users fill in a form, click Generate,
and get a name that's validated against the standard, checked for duplicates, and
optionally recorded as a work item and/or used to kick off a pipeline.
It shows up as a new "Naming Convention" entry in the project-level left nav (alongside
Boards, Repos, Pipelines, etc.) in any project where it's installed.
What it does
| Capability |
How |
| Generate names |
src/generator.ts builds the name from the Section 2 pattern (type-workload-env-region-instance), or the Section 5 concatenated pattern for restricted types (Storage Account, Container Registry, Key Vault). |
| Validate against the standard |
Same module checks length limits, allowed character sets, and required components, and flags anything the standard doesn't allow. |
| Check for duplicates |
Every generated name is checked against, and saved to, an Extension Data Service collection (generatedResourceNames) — Azure DevOps' built-in per-organization document store for extensions. No external database needed. |
| Store results |
Saved to Extension Data by default. Optionally also creates a work item (via WorkItemTrackingRestClient) so the request flows through your board, and/or commits a JSON registry file to a repo (via GitRestClient.createPush, see adoService.ts::commitRegistryFile — wire this into the UI if you want a versioned file instead of/alongside Extension Data). |
| Trigger pipelines |
Optionally queues a build (via BuildRestClient.queueBuild) after a name is saved, passing the generated name and its components as pipeline parameters. |
| Organization name setting |
An in-app "Set/Edit organization name" control lets any org using the extension label the header with their own name (e.g. "Contoso"), stored in the same shared Extension Data scope. Blank by default so it's neutral out of the box. |
Project layout
vss-extension.json Extension manifest — the hub contribution, scopes, icons
src/namingData.ts Resource type / workload / environment / region tables
(kept in sync with the naming convention document)
src/generator.ts Pure name-generation + validation logic (unit-testable, no SDK deps)
src/adoService.ts All Azure DevOps SDK/REST integration:
Extension Data (storage + duplicate check), work items, git push, pipelines
src/Hub.tsx / Hub.html / Hub.css The React form + host page
webpack.config.js / tsconfig.json Build config
img/ Extension + hub icons (placeholders — replace with your own branding)
Prerequisites
- Node.js 18+ and npm
- An Azure DevOps organization where you have permission to upload/install extensions
(for a private org-only install you don't need a Marketplace publisher account, just
"Manage Extensions" permission)
tfx-cli (included as a dev dependency; also installable globally with npm i -g tfx-cli)
Build
npm install
npm run build # webpack → dist/Hub.js, dist/Hub.html
Package and publish (public Marketplace listing)
The manifest already has "public": true and "publisher": "JinThakur".
- Replace
img/extension-icon.png and img/hub-icon.png with real branded icons
(128×128 and roughly 32×32 respectively) — the placeholders are not publish-ready.
- Package it:
npm install
npm run build
npx tfx extension create --manifest-globs vss-extension.json
This produces JinThakur.azure-naming-convention-1.0.0.vsix.
- Go to
https://marketplace.visualstudio.com/manage/publishers/JinThakur,
select New extension → Azure DevOps, and upload the .vsix. It uploads as
private by default regardless of the manifest's public flag — visible only to
you until you explicitly publish it.
- To make it publicly listed: select the extension, choose Publish, and confirm.
Microsoft runs a virus scan first; the listing goes live once that clears.
- Anyone can then find it on the Marketplace and click Get it free to install it
into their own Azure DevOps organization — no sharing step needed once it's public.
If you'd rather keep it private to a specific org instead, skip step 4 and use
Share/Unshare to grant that org access, same as before.
Required permissions
The manifest requests these scopes (vss-extension.json → scopes):
vso.extension.data_write — read/write the naming registry (Extension Data). Low risk: sandboxed to this extension's own storage, not flagged by Azure DevOps' install-time warning.
vso.work_write — create the optional tracking work item. Moderate: can create/edit work items, but can't touch source or execution.
vso.build_execute — queue a pipeline after saving. High privilege — Azure DevOps flags this because it can run pipelines anywhere in the org, and pipelines often hold secrets/service connections. Remove this scope and the "Queue a pipeline after saving" code path in Hub.tsx/adoService.ts if you don't need that feature; it's the single biggest thing you can cut to shrink the install warning. See "Why the pipeline permission is needed" below for the full justification.
Why the pipeline permission is needed
The "Queue a pipeline after saving" checkbox is what actually uses vso.build_execute.
It exists to close the gap between naming a resource and provisioning it:
- Without it, generating and saving a name is the end of the tool's job — someone
still has to go start the deployment pipeline by hand, manually pasting the
generated name into whatever "resource name" parameter that pipeline expects.
- With it,
adoService.ts::queuePipeline() calls BuildRestClient.queueBuild()
immediately after a successful save, passing the generated name(s) and project
name through as pipeline parameters (resourceNames, project). A Bicep/
Terraform/ARM pipeline that accepts those as inputs can provision the resource
under the exact name the standard just generated, with no manual copy-paste step
and no chance of the deployed name drifting from the registered one.
That capability is inherently broad: Azure DevOps has no "queue this one specific
pipeline" scope, only vso.build_execute, which grants queue/read rights across
every pipeline in every project the extension is installed into. That's why it's
flagged as high-privilege on install — it's not a sign of unnecessary or excessive
access, it's the smallest scope Azure DevOps offers for "let this extension start a
build," and this extension only calls it when the checkbox is explicitly ticked and
a pipeline ID is explicitly entered. It never queues anything on its own.
If your workflow doesn't chain naming directly into provisioning — e.g. someone
reviews and starts deployments manually, or your pipelines are triggered by a
different mechanism (a merge to a repo, a scheduled run) — this feature adds risk
without adding value for you, and removing it is the right call. See "Minimizing
the high privilege install warning" below for exactly what to remove.
vso.code_write is intentionally not requested by default — the only thing that
would use it, commitRegistryFile() in adoService.ts, isn't called from the UI.
Only add it back if you actually wire that function in; requesting a scope nothing
uses just adds risk surface for no benefit.
Minimizing the "high privilege" install warning
Azure DevOps shows that warning whenever a manifest requests a broad, org-wide
execute or write scope — it's standard for any extension in this category, not a
sign of a problem with the code. To get rid of it entirely, remove vso.build_execute
and disable pipeline-triggering (the only feature that needs it); what's left
(vso.extension.data_write, vso.work_write) doesn't trigger the warning. If you
want to keep pipeline-triggering, the warning is unavoidable — narrow it only by
being deliberate about which orgs/projects you install into, and by keeping this
README's scope table current so an installing admin can see exactly why each scope
is requested rather than trusting a blanket "trust the publisher" prompt.
Keeping this in sync with the naming convention document
src/namingData.ts is a direct transcription of Sections 3, 4.1–4.3, and 5 of the
Azure Resource Naming Convention document. When that document is revised (new
resource type, region, or workload code), update this file to match.
Extending
- Repo-based registry instead of/alongside Extension Data: call
commitRegistryFile() from Hub.tsx after saveToRegistry() to also version the
registry as a JSON file in a repo — useful if a Bicep/Terraform pipeline should read
the approved name list as an input.
- Custom work item type:
createTrackingWorkItem() defaults to Task; pass a
process-specific type (e.g. an "EA Intake" type) as the second argument.
- Org-wide vs. project-scoped registry:
getDataManager() currently scopes to the
extension at the org (user) level via the default Extension Data behavior. If you
need duplicate-checking to span multiple organizations, move the registry to an
external store (e.g. Azure Table Storage) behind a small API and swap the calls in
adoService.ts — generator.ts and the UI don't need to change.
Verified
npm install && npm run build was run against this exact project during creation and
compiles cleanly (only non-blocking "bundle size" performance warnings from webpack,
expected given the bundled Azure DevOps REST clients).