Azure Boards

Azure DevOps Marketplace extension.

BranchDeploy / Docs / Release notes

Release notes

BranchDeploy already records everything you deploy. Release notes turn that record into customer-facing copy — written and approved by a person, then published to either a hosted page you can link to, or a read-only JSON feed your own app renders.

Nothing reaches customers until someone clicks Publish. Deployment records, branch names, work item IDs, pipeline details and requester names never appear in public output.


How it works

Deployments  →  candidate list  →  draft  →  review  →  publish  →  hosted page
                                                                 └→ public JSON → your app
  1. You deploy as normal. BranchDeploy records the work item title and ticket type alongside each deployment.
  2. On the Release notes tab you write a note, pulling in those deployments as source material.
  3. You preview the exact JSON customers would receive.
  4. You publish. Only then does anything become public.

Writing your first note

  1. Open your account page and go to the Release notes tab.
  2. Click + New.
  3. Give the note a title — this is the headline customers see.
  4. Under Candidate deployments, click Add on anything that shipped. Each one copies its work item title in as a starting point and preselects a type from the ticket type: a Bug becomes Fixed, an Epic or Feature becomes New, a Task becomes Maintenance.
  5. Rewrite each item for customers. The work item title is a prompt, not publishable copy — see writing for customers.
  6. Click Preview JSON to see exactly what customers would get.
  7. Click Publish and confirm. Up to that point nothing is public, and you can Save draft as often as you like.

The release date defaults to today and is required, since the feed is ordered by it. Adding a candidate moves it to that deployment’s date, and it stops following them once you set it yourself.

Item types

TypeShows asUse for
featureNewSomething customers could not do before
improvementImprovedSomething that already worked, now better
fixFixedA bug customers noticed
securitySecuritySecurity-relevant changes — keep wording vague
maintenanceMaintenanceDependencies, certificates, housekeeping
otherUpdateAnything else

Option 1 — the hosted page

Every feed gets a page BranchDeploy hosts for you. Nothing to build:

https://branch-deploy.dev/release-notes/rnf_8f2c…

Link it from your app, your footer, or a “What’s new” menu item. It renders your notes as a timeline, works without JavaScript, and is safe to share publicly.

Branding it

Under Release notes → Feed settings you can set:

With no logo set the page carries BranchDeploy’s mark, and your feed name is still the heading.


Option 2 — the JSON feed

If you would rather render the notes in your own app, fetch the feed. No authentication, no SDK, no webhook, no database:

const FEED = "https://api.branch-deploy.dev/api/public/release-notes/rnf_8f2c…";

const { notes } = await fetch(FEED).then((r) => r.json());

Response

{
  "feed": {
    "id": "rnf_8f2c…",
    "name": "Release Log",
    "updatedAt": "2026-09-28T14:05:09.000Z"
  },
  "notes": [
    {
      "id": "rn_123",
      "version": "2026.08.27",
      "title": "What's new this week",
      "summary": "This update improves checkout and account setup.",
      "releaseDate": "2026-08-27",
      "publishedAt": "2026-08-27T10:30:00Z",
      "items": [
        { "type": "feature", "title": "Faster checkout", "body": "Fewer steps to pay." },
        { "type": "fix", "title": "More reliable sign-up" }
      ]
    }
  ],
  "nextPage": null
}

version, summary and an item’s body are omitted when empty rather than returned as null. releaseDate is always present.

Notes are ordered by releaseDate, newest first — not by when you clicked publish. Back-dating a note files it in the right place in the timeline.

Types

type ReleaseNoteItem = {
  type: "feature" | "improvement" | "fix" | "security" | "maintenance" | "other";
  title: string;
  body?: string;
};

type ReleaseNote = {
  id: string;
  version?: string;
  title: string;
  summary?: string;
  releaseDate?: string;
  publishedAt: string;
  items: ReleaseNoteItem[];
};

Query parameters

ParameterDefaultNotes
limit10Maximum 100
cursor—The value of nextPage from the previous response, to fetch older notes. Treat it as opaque — it is not a page number.

nextPage is how you reach older entries. Each response carries the newest notes; if more exist beyond that page, nextPage is a token you pass back as cursor. When it is null there is nothing older — most feeds will never return anything else, and a “What’s new” panel showing the latest few can ignore it entirely.

A “What’s new” badge wants ?limit=1. A full changelog page wants ?limit=100 — that covers a year of weekly releases in one request. Beyond that, follow nextPage:

let url = `${FEED}?limit=100`;
const all = [];

while (url) {
  const { notes, nextPage } = await fetch(url).then((r) => r.json());
  all.push(...notes);
  url = nextPage ? `${FEED}?limit=100&cursor=${encodeURIComponent(nextPage)}` : null;
}

A single note

GET /api/public/release-notes/{publicFeedId}/{noteId}

Returns { feed, note }. Useful for deep-linking one release.

Knowing when it changed

feed.updatedAt moves whenever anything in the response changes — a note published, a live note edited, one archived or deleted, or the feed renamed. Saving a draft does not move it, because nothing public changed.

Store it alongside whatever you cache and compare on your next fetch:

const { feed, notes } = await fetch(FEED).then((r) => r.json());

if (feed.updatedAt !== lastSeen) {
  render(notes);
  lastSeen = feed.updatedAt;
}

It only ever moves forward, so it is also safe for “New updates” badges.

Caching

Responses carry Cache-Control: public, max-age=60, stale-while-revalidate=300 and an ETag. Send If-None-Match and you will get a 304 when nothing has changed — cheaper than comparing updatedAt, and the right choice if you only want to avoid re-downloading.

Handling failure

Treat the feed as decorative. If it fails, hide the panel — never block your app on it:

async function loadReleaseNotes() {
  try {
    const res = await fetch(FEED, { headers: { Accept: "application/json" } });
    if (!res.ok) return [];
    return (await res.json()).notes ?? [];
  } catch {
    return [];
  }
}

Your feed URL never changes

The ID in your URL is random, generated once, and never regenerated. Renaming the feed does not change it. Publishing, archiving and editing do not change it. It is safe to hardcode in a client app.

Turning public access off in Feed settings makes both URLs return 404 immediately, without unpublishing anything — useful if something goes out wrong. Turning it back on restores them.


Editing, archiving and deleting

A note you have not published yet is a draft. Drafts are private — save as often as you like, nothing is public until you click Publish.

Once a note is live, Save changes updates it for customers straight away. There is no second publish step. Its original publication date is kept, so an edit does not push it back to the top of the feed.

Two ways to take a note down:

What happensReversible
ArchiveWithdrawn from the public feed immediately, including from anyone who linked to it. The wording is kept.Yes — publish it again
DeleteThe note and its items are removed permanently.No

Archive is the safe option. Delete is for notes published by mistake, or drafts you no longer want.


Writing for customers

The deployment record is written by developers for developers. Customers need something else:


With an AI assistant (MCP)

If you use AI deployment, your assistant can draft release notes from what shipped:

Draft release notes for this week’s UAT deployments. Keep it short and avoid anything internal.

The assistant reads your deployments, writes customer-facing copy, and saves a draft. It can then refine it on request. Publishing always requires you to confirm explicitly, against a preview of the exact content — if the note changes after that preview, the confirmation is void and it has to ask again.

Assistants can list existing notes, create and refine drafts, preview, publish, and archive. They cannot edit or delete a note that is already published — those go through the account page, so a person stays in the loop on anything customers are already reading.

Letting the assistant read your tickets

By default an assistant sees only what a deployment recorded: the work item ID, its title, type and state. Titles are often terse (“Fix #4102 regression”), which makes for a vague release note.

Turning on AI drafting under Release notes → Feed settings lets the assistant also read the description on the Azure DevOps ticket behind each deployment, so it can write from what the work actually was.

It is off until you turn it on, and worth a moment’s thought before you do:

You can turn it off again at any time, and the next request will be refused.

Writing notes in the account page is unaffected either way — the ticket panel under each item has always shown you the description, because that is you reading your own ticket in your own browser.


Who can do what

ActionOwnerAdminEditorViewer
View notes and the feed URLYesYesYesYes
Create and edit draftsYesYesYes—
Publish and archiveYesYesYes—
Delete permanentlyYesYesYes—
Feed settings and brandingYesYes——
Turn AI drafting on or offYesYes——

Feeds

A feed is created automatically for each project the first time it syncs from the extension — there is nothing to set up. It is named Release Log until you rename it under Feed settings; that name is the heading on the hosted page and the feed.name in the JSON.

One feed per project. If you have not synced a project yet, open Project Settings → BranchDeploy in Azure DevOps and save.


Troubleshooting

The public URL returns 404. Public access is off for the feed, or nothing has been published yet. Check Feed settings.

A candidate has no title. The deployment was recorded by an older version of the Azure DevOps extension, which did not send work item titles. New deployments will have them. Update the extension from the Marketplace.

My edits are not showing publicly. Saving a live note updates it immediately, but the feed is cached for 60 seconds — wait a moment and reload.

An item came out as the wrong type. The type is a suggestion from the Azure DevOps ticket type. Change the dropdown — it is only a starting point.

Install Free forever for one project.

Ready to deploy?

Install BranchDeploy from the Marketplace, open Project Settings, add your pipeline ID, and deploy from a work item in minutes.

$ az devops extension install --extension-id branchdeploy --publisher-id PixelFunnelLtd
Install free
Requirements
  • Azure Repos + Azure Pipelines.
  • Permission to queue the pipeline.
  • No BranchDeploy account needed (Free).
Setup
  • Install the extension.
  • Open Project Settings → BranchDeploy.
  • Enter your pipeline ID and save.
Free tier
  • One project, one environment.
  • Queues as your Azure DevOps session.
  • Completely free, forever.
Pro
BranchDeploy // © 2026 Pixel Funnel Ltd // Azure DevOps Marketplace extension // No clipboard. No tab switching. No branch-name guesswork.