A QBR template you can send as one link

A quarterly business review does not have to be a deck. Put results, risks and the plan for next quarter on one page, and send a single link.

A QBR template that works on every screen is one page with five blocks: the quarter in one line, results against target, risks with owners, the plan for next quarter, and the decision you need.

Build it as a web page and you send one link instead of a file.

The pain it removes is small fixes. One blog post about AI-made slide decks lists, among its complaints, that the author couldn't "tweak one bullet without regenerating the whole thing". A page that you edit in place avoids that.

Here is a finished sample, with made-up numbers. Scroll it, then copy the structure.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Q3 2026 business review</title>
<style>
  * { box-sizing: border-box; }
  body { margin: 0; background: #f3f4f7; font-family: system-ui, "Segoe UI", sans-serif; color: #1d2330; line-height: 1.5; }
  .page { max-width: 760px; margin: 0 auto; padding: 20px 16px 28px; }
  header { margin-bottom: 14px; }
  .eyebrow { font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #5b6475; }
  h1 { margin: 4px 0 6px; font-size: 26px; line-height: 1.2; }
  .sub { margin: 0; color: #5b6475; font-size: 14px; }
  section { background: #fff; border: 1px solid #e1e4ea; border-radius: 12px; padding: 16px 18px; margin-top: 12px; }
  h2 { margin: 0 0 10px; font-size: 16px; }
  .headline { border-left: 5px solid #1f7a4d; }
  .headline p { margin: 0; font-size: 16px; }
  .tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
  .tile { border: 1px solid #e1e4ea; border-radius: 10px; padding: 10px 12px; }
  .tile .v { font-size: 22px; font-weight: 700; }
  .tile .l { font-size: 12px; color: #5b6475; }
  .tile .d { font-size: 12px; font-weight: 600; margin-top: 2px; }
  .up { color: #17663f; } .down { color: #b4410f; }
  table { width: 100%; border-collapse: collapse; font-size: 14px; }
  th, td { text-align: left; padding: 8px 8px; border-bottom: 1px solid #edf0f4; vertical-align: top; }
  th { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: #5b6475; }
  .chip { display: inline-block; padding: 1px 9px; border-radius: 999px; font-size: 12px; font-weight: 700; white-space: nowrap; }
  .hit { background: #d9f2e3; color: #11583a; }
  .miss { background: #fde3d3; color: #8f3306; }
  .near { background: #fff0c2; color: #6b4a00; }
  .chart svg { width: 100%; max-width: 440px; height: auto; display: block; margin: 0 auto; }
  .plan { margin: 0; padding-left: 20px; }
  .plan li { margin-bottom: 8px; }
  .plan small { color: #5b6475; }
  .ask { background: #fff9e6; border-color: #f0dc9a; }
  .ask p { margin: 0; }
  @media (max-width: 560px) {
    .tiles { grid-template-columns: repeat(2, 1fr); }
    h1 { font-size: 22px; }
    .cut { display: none; }
  }
</style>
</head>
<body>
<div class="page">
  <header>
    <div class="eyebrow">Quarterly business review · Q3 2026 (Jul to Sep)</div>
    <h1>Harbor Lane Software: Q3 review</h1>
    <p class="sub">Sample page with made-up numbers. Prepared for the leadership meeting on 8 October.</p>
  </header>

  <section class="headline">
    <h2>The quarter in one line</h2>
    <p>Revenue beat target by 6% and churn came in under goal, but the new onboarding flow shipped three weeks late.</p>
  </section>

  <section>
    <h2>Results against target</h2>
    <div class="tiles">
      <div class="tile"><div class="v">$412k</div><div class="l">Revenue (target $390k)</div><div class="d up">+6% vs target</div></div>
      <div class="tile"><div class="v">1.9%</div><div class="l">Monthly churn (goal 2.5%)</div><div class="d up">0.6 pts better</div></div>
      <div class="tile"><div class="v">38</div><div class="l">New customers (target 45)</div><div class="d down">7 short</div></div>
      <div class="tile"><div class="v">92%</div><div class="l">Support answered in 1 day (goal 90%)</div><div class="d up">+2 pts</div></div>
    </div>
    <div class="chart" style="margin-top:14px">
      <svg viewBox="0 0 400 170" role="img" aria-label="Monthly revenue against the monthly target">
        <rect x="50" y="51.4" width="70" height="88.6" rx="4" fill="#9db4d6"/>
        <rect x="165" y="49.3" width="70" height="90.7" rx="4" fill="#9db4d6"/>
        <rect x="280" y="34.1" width="70" height="105.9" rx="4" fill="#2f6fbf"/>
        <line x1="10" y1="50" x2="390" y2="50" stroke="#5b6475" stroke-dasharray="5 4"/>
        <text x="10" y="40" font-size="13" fill="#5b6475">Monthly target $130k</text>
        <text x="85" y="160" font-size="13" text-anchor="middle" fill="#1d2330">Jul $128k</text>
        <text x="200" y="160" font-size="13" text-anchor="middle" fill="#1d2330">Aug $131k</text>
        <text x="315" y="160" font-size="13" text-anchor="middle" fill="#1d2330">Sep $153k</text>
      </svg>
    </div>
    <table style="margin-top:8px">
      <tr><th>Goal</th><th>Result</th><th class="cut">Note</th></tr>
      <tr><td>Revenue $390k</td><td><span class="chip hit">Hit</span></td><td class="cut">September carried the quarter</td></tr>
      <tr><td>45 new customers</td><td><span class="chip miss">Missed</span></td><td class="cut">Trials were fine, conversions were slow</td></tr>
      <tr><td>Onboarding flow live by 15 Aug</td><td><span class="chip near">Late</span></td><td class="cut">Shipped 5 September</td></tr>
    </table>
  </section>

  <section>
    <h2>Risks</h2>
    <table>
      <tr><th>Risk</th><th>Owner</th><th>Status</th></tr>
      <tr><td>Two large accounts renew in November</td><td>Dana</td><td><span class="chip near">Watch</span></td></tr>
      <tr><td>Conversion from trial is below plan</td><td>Marcus</td><td><span class="chip miss">Open</span></td></tr>
      <tr><td>Hosting contract ends in December</td><td>Priya</td><td><span class="chip hit">Handled</span></td></tr>
    </table>
  </section>

  <section>
    <h2>Plan for Q4</h2>
    <ol class="plan">
      <li><strong>Lift trial-to-paid conversion to 22%.</strong><br><small>Owner: Marcus · checkpoint 15 November</small></li>
      <li><strong>Renew both large accounts.</strong><br><small>Owner: Dana · done by 30 November</small></li>
      <li><strong>Launch the mobile view of reports.</strong><br><small>Owner: Priya · release 12 December</small></li>
    </ol>
  </section>

  <section class="ask">
    <h2>Decision needed</h2>
    <p>Approve one extra support hire for Q4, so onboarding calls stay within a day. Answer by 15 October.</p>
  </section>
</div>
</body>
</html>
A complete sample QBR page: headline, results, risks, plan and one decision.

What goes on a QBR page

Readers of a QBR want to know three things: how the quarter went, what could go wrong, and what happens next. Each block answers one of them, in the order a busy reader scans.

Block Question it answers What to put in it
The quarter in one line Was it a good quarter? One sentence with the biggest win and the biggest miss
Results against target Did we hit the goals? Each target next to its result, with Hit, Close or Missed
Risks What could go wrong? A short list, each with an owner and a status
Plan for next quarter What happens next? Three goals, each with an owner and a date
Decision needed What do you need from the reader? One ask and the day you need the answer

Keep every block short. A long explanation belongs on its own page, linked from the QBR.

Make it with AI

Any of ChatGPT, Claude or Gemini can turn your numbers into this page. The AI cannot know your figures, so paste them in. Copy this prompt and fill the brackets.

Make one self-contained HTML page for a quarterly business
review of [team or company], for [quarter and year].
Sections in this order: the quarter in one line, results
against target (a tile per metric plus a small table with
Hit, Close or Missed), risks (owner and status), plan for
next quarter (three goals, each with an owner and a date),
and one decision needed.
Use only the numbers below. If something is missing, write
TODO instead of guessing.
One column on a phone, readable on a laptop. Draw any chart
as inline SVG. No external images, scripts or fonts.
Numbers: [paste targets, results, risks, plan]
From an AI chat to one link: ask, check, paste into NOS, send.
From an AI chat to one link: ask, check, paste into NOS, send.

Before you send it, check the result:

  • Every figure. Compare each number, name and date with your source. AI tools sometimes fill a gap with a plausible value.
  • Any TODO. Replace it or delete the line.
  • The statuses. A target of 45 and a result of 38 must read Missed, not Close.
  • Your phone. Open the page on a phone and scroll to the end.

Keep the numbers honest

Status words are where a review quietly goes wrong, because someone has to decide what counts as Close. Fix the rule once.

In the scorecard below, 100% of target or better is Hit, 90% to 99% is Close, and anything lower is Missed. For a goal where smaller is better, such as churn, the rule flips.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>QBR scorecard</title>
<style>
  * { box-sizing: border-box; }
  body { margin: 0; padding: 14px; background: #f3f4f7; font-family: system-ui, "Segoe UI", sans-serif; color: #1d2330; }
  .card { max-width: 720px; margin: 0 auto; background: #fff; border: 1px solid #e1e4ea; border-radius: 12px; padding: 16px 18px; }
  h1 { margin: 0 0 4px; font-size: 18px; }
  .hint { margin: 0 0 12px; font-size: 13px; color: #5b6475; }
  .summary { margin: 0 0 12px; padding: 10px 12px; border-radius: 8px; background: #eef3fb; font-weight: 600; font-size: 15px; }
  .row { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1.1fr; gap: 8px; align-items: center; padding: 8px 0; border-top: 1px solid #edf0f4; }
  .row.head { border-top: 0; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: #5b6475; padding-bottom: 2px; }
  .name small { display: block; color: #5b6475; font-size: 12px; }
  input { width: 100%; font: inherit; padding: 6px 8px; border: 1px solid #c9cfd9; border-radius: 6px; }
  .out { font-size: 14px; }
  .chip { display: inline-block; padding: 1px 9px; border-radius: 999px; font-size: 12px; font-weight: 700; margin-left: 4px; }
  .hit { background: #d9f2e3; color: #11583a; }
  .near { background: #fff0c2; color: #6b4a00; }
  .miss { background: #fde3d3; color: #8f3306; }
  button { margin-top: 12px; font: inherit; padding: 7px 14px; border: 1px solid #c9cfd9; border-radius: 8px; background: #fff; cursor: pointer; }
  @media (max-width: 520px) {
    .row { grid-template-columns: 1fr 1fr; }
    .row.head { display: none; }
    .name { grid-column: 1 / -1; font-weight: 600; }
    .out { grid-column: 1 / -1; }
  }
</style>
</head>
<body>
<div class="card">
  <h1>Q3 scorecard</h1>
  <p class="hint">Change a target or a result. The percent, the status and the summary line update.</p>
  <p class="summary" id="summary"></p>

  <div class="row head"><div>Goal</div><div>Target</div><div>Result</div><div>Status</div></div>
  <div id="rows"></div>

  <button id="reset" type="button">Reset to sample numbers</button>
</div>

<script>
  // lower = true means a smaller result is better (churn, cost, days)
  const SAMPLE = [
    { name: 'Revenue ($k)',        unit: 'higher is better', target: 390, actual: 412, lower: false },
    { name: 'Monthly churn (%)',   unit: 'lower is better',  target: 2.5, actual: 1.9, lower: true },
    { name: 'New customers',       unit: 'higher is better', target: 45,  actual: 38,  lower: false },
    { name: 'Support in 1 day (%)', unit: 'higher is better', target: 90,  actual: 92,  lower: false }
  ];
  let goals = [];
  const rows = document.getElementById('rows');

  function status(g) {
    if (g.lower && g.actual === 0) return { ratio: 1, cls: 'hit', label: 'Hit' };
    const ratio = g.lower ? g.target / g.actual : g.actual / g.target;
    if (!isFinite(ratio)) return { ratio: 0, cls: 'miss', label: 'Missed' };
    if (ratio >= 1)   return { ratio, cls: 'hit',  label: 'Hit' };
    if (ratio >= 0.9) return { ratio, cls: 'near', label: 'Close' };
    return { ratio, cls: 'miss', label: 'Missed' };
  }

  function render() {
    let hit = 0, near = 0, miss = 0;
    goals.forEach((g, i) => {
      const s = status(g);
      if (s.cls === 'hit') hit++; else if (s.cls === 'near') near++; else miss++;
      const out = rows.children[i].querySelector('.out');
      out.innerHTML = Math.round(s.ratio * 100) + '% <span class="chip ' + s.cls + '">' + s.label + '</span>';
    });
    document.getElementById('summary').textContent =
      hit + ' of ' + goals.length + ' goals hit, ' + near + ' close, ' + miss + ' missed.';
  }

  function build() {
    goals = SAMPLE.map((g) => Object.assign({}, g));
    rows.innerHTML = '';
    goals.forEach((g, i) => {
      const row = document.createElement('div');
      row.className = 'row';
      row.innerHTML =
        '<div class="name">' + g.name + '<small>' + g.unit + '</small></div>' +
        '<div><input type="number" step="any" aria-label="Target for ' + g.name + '" value="' + g.target + '" data-i="' + i + '" data-f="target"></div>' +
        '<div><input type="number" step="any" aria-label="Result for ' + g.name + '" value="' + g.actual + '" data-i="' + i + '" data-f="actual"></div>' +
        '<div class="out"></div>';
      rows.appendChild(row);
    });
    render();
  }

  rows.addEventListener('input', (e) => {
    const el = e.target;
    if (!el.dataset.f) return;
    goals[el.dataset.i][el.dataset.f] = parseFloat(el.value) || 0;
    render();
  });
  document.getElementById('reset').addEventListener('click', build);
  build();
</script>
</body>
</html>
Change a target or a result and the percent, the status and the summary line update. Churn counts lower as better.

When a number changes after you send the page, ask the AI to change only that part: "Change only the Q4 plan, item 2. Keep everything else exactly as it is."

Then compare the rest of the page with the version you had. Or edit that one line yourself in the code.

Paste the finished HTML into a NOS document and choose Create share link. The page renders as written, with its scripts running. HTML to link walks through it.

The person who gets the link opens it in a browser, on a computer or a phone. They need no account and cannot edit the page.

If you change the code later, the same link shows the new version, so there is no second email to send.

Three files for one review, against one link that always shows the newest version.
Three files for one review, against one link that always shows the newest version.

How you pass the link on depends on where your team talks:

  • Email in Outlook. Select the text, choose Insert, then Link, and enter the address in the Address box. Typing a web address and pressing Space or Enter also turns it into a link. As of October 2026.
  • Slack. Slack says that when you share a link, you see a preview of the content below your message, and that most links to websites expand into one. Whether a given page gets a preview is up to Slack. See sending a page in Slack.
  • A meeting invite or a chat. A link is plain text, so you can paste it anywhere you can type.

If your review is a slide deck today, turning a deck into a link covers that case. A report that repeats every quarter fits the report to link guide.

Ways to send a QBR compared

The same review reaches readers differently depending on how you send it.

Way to send What the reader gets After you fix a number
Slide deck as an attachment A file to download and open in a presentation app Send a new file
PDF export A fixed copy in a PDF viewer Export it again and resend
Pasted into the email body Text and maybe a picture in the message Send a correction
Link to a page A page that opens in a browser, phone included Edit the page, same link
A wide slide on a phone, against a page that reflows into one column.
A wide slide on a phone, against a page that reflows into one column.

An attachment is fine when the review is final and nobody needs a phone. A link is better when the review gets fixed or read on the go. Opening an HTML file on a phone explains why a file struggles there.

When it does not work

What you see Cause Fix
A number you never gave the AI The prompt had no source figures Paste your numbers and ask for TODO where data is missing
A chart or logo is missing The page loads an image from another site, which is blocked Ask for inline SVG or a CSS shape instead
The page runs off the side on a phone A fixed-width table or slide layout Ask for one column and a viewport meta tag
A Send button does nothing Sending a form to a server is blocked Keep a reply address on the page and ask readers to email you
A reader quotes old numbers They kept an earlier attachment or PDF Send the link and tell them to use it from now on
Slack shows no preview card Slack decides which links get one Paste the link with one line of context

Questions people ask

How long should a QBR page be?

Short enough to read in a few minutes. Keep the headline, results, risks, plan and decision on the page, and give any long detail its own page that the QBR links to.

Can I still present it in a meeting?

Yes. Open the link in a browser, share your screen and scroll through the sections in order. The people who miss the meeting open the same link later.

Is the page private?

Anyone who has the link can open it, and NOS has no password for a share link. Leave out anything you would not put in an email that might be forwarded.

Can a reader change the numbers on my page?

No. A share link opens a page the visitor cannot edit. If your page has inputs, such as the scorecard above, a visitor can type in them on their own screen, but nothing they change is saved.

Does the same template work for a customer QBR?

The shape is the same. Replace internal targets with the outcomes the customer cares about, and leave out risks that are only yours to manage.

Keep reading