SEO Proposal Template: Use Audit Data to Win More Clients
An SEO proposal template built on audit data: findings, impact, scope, pricing and timeline, with an example from a real scan and a script that builds it.
A strong SEO proposal template follows five sections in order: findings → impact → scope → pricing → timeline. Each finding is backed by a measurement from the prospect's own site, each price is traceable to estimated hours, and each timeline item has a "done when" that a re-audit can verify. The audit does the persuading. The proposal turns it into a decision.
Most proposals fail in the opposite direction: a generic list of services ("on-page optimisation, technical SEO, link building") with a monthly fee and no evidence. The prospect can't tell your proposal from the next agency's, so they compare on price. A proposal built from their data — "20 tracking cookies fire before your visitors consent", "your homepage needs 20 seconds to render on a mid-range phone" — can't be compared line for line, because nobody else has written it.
This page gives you the template, a version filled in from a real (anonymised) scan of a pet-marketplace homepage we measured in October 2026, and a script that generates the scope, pricing and timeline sections from a findings spreadsheet.
The Template
Copy this structure. The bracketed parts come from the audit; everything else is your positioning.
# Website growth proposal for [Client] — [date]
## 0. Summary (one paragraph)
[Client]'s site [one strength]. Three issues hold it back: [finding 1], [finding 2], [finding 3].
We propose a [N]-week project ([investment]) to fix them, followed by optional monthly monitoring.
## 1. What we found ← from the audit, in plain English, with the measured evidence
## 2. Why it matters ← revenue, risk, cost: tie each finding to a business outcome
## 3. Scope and investment ← phases, hours, price; what is explicitly NOT included
## 4. Timeline ← week by week, with owners and "done when" criteria
## 5. How we'll prove it ← re-audit date, the metrics you'll compare before/after
## 6. Next step ← one action: "approve Phase 1 by [date]"
Appendix: full audit report (white-label PDF)Six rules make it work:
- Evidence, not adjectives. "Slow site" is an opinion. "LCP 20.0 s on a throttled mobile load" is a fact the prospect can check against Google's LCP thresholds.
- Group by cause. Five findings with a clear owner beat fifty URLs.
- Prices trace to hours. If a prospect asks "why $3,870?", you point at the line items.
- Name what's out of scope. It protects your margin and makes the scope credible.
- Promise a measurement, not a ranking. You control fixes and re-audits; nobody controls Google, and Google's guide on whether you need an SEO tells site owners to be wary of anyone who guarantees rankings.
- One next step. "Approve Phase 1" converts better than "let us know your thoughts".
A Filled-In Example From a Real Scan
The prospect below is a pet-adoption marketplace homepage from our random sample of 219 sites (Tranco list 94GG2, ranks 1,001–50,000). Every number was measured between 3 and 10 October 2026: axe-core accessibility checks, a throttled mobile lab load (412×823, 150 ms RTT, 1.6 Mbps, 4× CPU slowdown), a pre-consent cookie scan, a 390 px mobile pass, internal link checks and a security-header grade using BugViso's rules. We removed the name; the findings are unedited.
What the measurements showed
| Area | Measurement | Value |
|---|---|---|
| Performance | Largest Contentful Paint, throttled mobile (lab) | 20.0 s |
| Performance | Total Blocking Time (lab) | 9.6 s |
| Performance | JavaScript shipped / unused on load | 9.3 MB / 49% |
| Privacy | Tracking cookies before any consent (no banner shown) | 20 |
| Accessibility | Unnamed buttons / unlabelled icons / unnamed links | 11 / 13 / 4 |
| Mobile | Footer links failing WCAG 2.5.8 at 390 px | 11 |
| SEO | Internal links returning 404 / redirecting | 1 / 29 |
| Security | Header grade (BugViso rules), legacy TLS 1.0 accepted | F (40/100), yes |
Note the strengths, too: content was server-rendered (345 words in the raw HTML, title, H1, meta description and canonical all present), so this is a fix-and-speed project, not a rebuild. A good proposal says that.
The generated proposal sections
We put those findings into a seven-line CSV (impact and reach scored 1–3, effort estimated in hours) and ran the script further down with an example rate of $90/hour, 12 billable hours a week and a $49/month tool cost. The rate is an input, not a recommendation; use your own.
## 1. What we found on Pet marketplace's site
| # | Finding | Evidence (measured) | Why it matters |
| 1 | Tracking fires before cookie consent (Privacy) | 20 tracking cookies (GA4, Meta, Bing, Hotjar, LinkedIn) on first load; no consent banner | Revenue, indexing or legal exposure |
| 2 | Mobile load blocked by JavaScript (Performance) | Lab LCP 20.0 s and TBT 9.6 s on throttled mobile; 9.3 MB JS, 49% unused | Revenue, indexing or legal exposure |
| 3 | Undersized footer links on phones (Mobile) | 11 footer links fail WCAG 2.5.8 at 390px | Rankings / conversion |
| 4 | Security headers grade F (Security) | No HSTS, no CSP, no Permissions-Policy; server still accepts TLS 1.0 | Rankings / conversion |
| 5 | Broken and redirecting internal links (SEO) | 1 internal 404, 29 internal links that redirect | Rankings / conversion |
| 6 | Icon buttons and links without names (Accessibility) | 11 unnamed buttons, 13 unlabelled icons, 4 unnamed links (axe-core) | Rankings / conversion |
| 7 | Images without alt attribute (Accessibility) | 2 images with no alt attribute | Hygiene |
## 2. Scope and investment
| Phase | Work included | Hours | Investment |
| **Phase 1** | Tracking fires before cookie consent; Mobile load blocked by JavaScript; Undersized footer links on phones; Security headers grade F; Broken and redirecting internal links | 43 | $3,870 |
| **Phase 2** | Icon buttons and links without names | 6 | $540 |
| **Re-audit and report** | Same scan settings, before/after comparison | 2 | $180 |
| **Total project** | | **51** | **$4,590** |
_Not included (backlog, quoted on request): Images without alt attribute._
**Optional monthly monitoring:** scheduled re-audit, regression check and a one-page report, 4 h/month + $49 tooling: **$409/month**.
## 3. Timeline
| Week | Work | Owner |
| 1 | Tracking fires before cookie consent | Agency + developer |
| 1–3 | Mobile load blocked by JavaScript | Developer |
| 3 | Undersized footer links on phones | Front-end |
| 4 | Security headers grade F | Developer |
| 4 | Broken and redirecting internal links | Content |
| 4–5 | Icon buttons and links without names | Front-end |
| 6 | Re-audit with identical settings; results meeting | Agency |Two choices in that output are deliberate. The consent problem is first, ahead of the bigger performance job, because it carries regulatory exposure and takes days, not weeks. And the two missing alt attributes are in the backlog: listing them as excluded shows the prospect you've seen everything while keeping the price focused on what moves outcomes.
Section by Section: What Goes Where
Findings: three to seven, never fifty
Pick the findings with the highest impact × reach, grouped by cause. In our 219-site sample the median homepage's accessibility failures came from just three rules, and one rule held two-thirds of the failing elements — so the honest list is usually short. Put the full report in the appendix; our guide to presenting audit results to clients covers turning element counts into causes.
Impact: the prospect's language
Translate each finding into revenue, risk, cost or reputation. "Twenty tracking cookies before consent" becomes "your analytics and ad platforms collect data before visitors agree, which EU and UK cookie rules require them not to". Avoid legal conclusions; say what was measured and what the rules require (the UK regulator's cookie guidance is a citable summary), and recommend they confirm with counsel.
Scope: phases with an exit criterion
Each phase needs a "done when" that a scan can verify: zero tracking cookies on a clean load, mobile LCP under a target, zero WCAG 2.5.8 failures on the mobile pass. That makes the re-audit the proof of delivery and keeps "is it finished?" from becoming a negotiation.
Pricing: hours × rate, shown
Clients push back on round numbers and accept itemised ones. Show hours per phase, your rate and what's excluded. For ongoing work, price monitoring separately from fixes — our post on pricing SEO audit services covers models, and turning one-off audits into monthly retainers covers the follow-on.
Timeline: capacity-based, not wishful
Pack the work into weeks by the hours you actually have for this client. A timeline that slips in week two damages trust more than a longer one that holds.
Proof: book the re-audit in the proposal
Name the date and the metrics. "On week 6 we re-run the identical audit and show before/after for each finding" is the sentence that turns a one-off project into a relationship.
Generate the Scope, Pricing and Timeline (Script)
The script reads a findings CSV, ranks each line by impact × reach against an effort band, puts anything with impact 3 (plus quick wins under four hours) in Phase 1, prices phases at your rate, and packs work into weeks by your capacity. Standard library only.
#!/usr/bin/env python3
"""proposal_builder.py: turn audit findings into the findings -> impact -> scope -> pricing -> timeline
sections of an SEO proposal.
Usage:
python3 proposal_builder.py findings.csv --client "Acme Ltd" --rate 90 --capacity 12 \
--monitoring-hours 4 --tool-cost 49 > proposal.md
findings.csv columns (header row required):
finding,area,evidence,impact,reach,effort_hours,owner
impact and reach are 1-3 (3 = blocks revenue/indexing/legal; 3 = site-wide).
Standard library only. Phase 1 = anything with impact 3 plus quick wins (score >= 4, <= 4 h); score = impact x reach /
effort band. Items are packed into weeks by
--capacity (your billable hours per week on this client). Prices are your rate x estimated hours,
so every number in the output can be traced back to a line in the CSV.
"""
import argparse, csv, math, sys
def band(hours):
return 1 if hours <= 4 else 2 if hours <= 16 else 3
def main():
ap = argparse.ArgumentParser()
ap.add_argument("csv")
ap.add_argument("--client", default="the client")
ap.add_argument("--rate", type=float, required=True, help="your hourly rate")
ap.add_argument("--capacity", type=float, default=10, help="billable hours per week on this project")
ap.add_argument("--monitoring-hours", type=float, default=4, help="monthly hours for the optional retainer")
ap.add_argument("--tool-cost", type=float, default=0, help="monthly tooling cost passed through in the retainer")
ap.add_argument("--currency", default="$")
a = ap.parse_args()
rows = list(csv.DictReader(open(a.csv)))
for r in rows:
r["impact"], r["reach"], r["effort_hours"] = int(r["impact"]), int(r["reach"]), float(r["effort_hours"])
r["score"] = r["impact"] * r["reach"] / band(r["effort_hours"])
for r in rows:
quick_win = band(r["effort_hours"]) == 1 and r["score"] >= 4
r["tier"] = "Phase 1" if r["impact"] == 3 or quick_win else "Phase 2" if r["score"] >= 2 else "Backlog"
order = {"Phase 1": 0, "Phase 2": 1, "Backlog": 2}
rows.sort(key=lambda r: (order[r["tier"]], -r["impact"], -r["score"], r["effort_hours"]))
c = a.currency
money = lambda h: f"{c}{h * a.rate:,.0f}"
print(f"## 1. What we found on {a.client}'s site\n")
print("| # | Finding | Evidence (measured) | Why it matters |\n| :---: | :--- | :--- | :--- |")
for i, r in enumerate(rows, 1):
why = {3: "Revenue, indexing or legal exposure", 2: "Rankings / conversion", 1: "Hygiene"}[r["impact"]]
print(f"| {i} | {r['finding']} ({r['area']}) | {r['evidence']} | {why} |")
print("\n## 2. Scope and investment\n")
print("| Phase | Work included | Hours | Investment |\n| :--- | :--- | ---: | ---: |")
total = 0.0
for tier in ("Phase 1", "Phase 2"):
items = [r for r in rows if r["tier"] == tier]
if not items:
continue
h = sum(r["effort_hours"] for r in items)
total += h
print(f"| **{tier}** | {'; '.join(r['finding'] for r in items)} | {h:g} | {money(h)} |")
print(f"| **Re-audit and report** | Same scan settings, before/after comparison | 2 | {money(2)} |")
total += 2
print(f"| **Total project** | | **{total:g}** | **{money(total)}** |")
back = [r for r in rows if r["tier"] == "Backlog"]
if back:
print(f"\n_Not included (backlog, quoted on request): {'; '.join(r['finding'] for r in back)}._")
retainer = a.monitoring_hours * a.rate + a.tool_cost
print(f"\n**Optional monthly monitoring:** scheduled re-audit, regression check and a one-page report, "
f"{a.monitoring_hours:g} h/month{f' + {c}{a.tool_cost:,.0f} tooling' if a.tool_cost else ''}: **{c}{retainer:,.0f}/month**.")
print("\n## 3. Timeline\n")
print("| Week | Work | Owner |\n| :---: | :--- | :--- |")
done = 0.0 # billable hours already scheduled
for r in [r for r in rows if r["tier"] != "Backlog"]:
first, done = int(done // a.capacity) + 1, done + r["effort_hours"]
last = max(first, math.ceil(done / a.capacity))
print(f"| {first if first == last else f'{first}–{last}'} | {r['finding']} | {r['owner']} |")
print(f"| {math.ceil(done / a.capacity) + 1} | Re-audit with identical settings; results meeting | Agency |")
return 0
if __name__ == "__main__":
sys.exit(main())The CSV behind the example (copy it as a starting point):
finding,area,evidence,impact,reach,effort_hours,owner
Tracking fires before cookie consent,Privacy,"20 tracking cookies (GA4, Meta, Bing, Hotjar, LinkedIn) on first load; no consent banner",3,3,10,Agency + developer
Mobile load blocked by JavaScript,Performance,"Lab LCP 20.0 s and TBT 9.6 s on throttled mobile; 9.3 MB JS, 49% unused",3,3,24,Developer
Icon buttons and links without names,Accessibility,"11 unnamed buttons, 13 unlabelled icons, 4 unnamed links (axe-core)",2,3,6,Front-end
Undersized footer links on phones,Mobile,"11 footer links fail WCAG 2.5.8 at 390px",2,3,2,Front-end
Broken and redirecting internal links,SEO,"1 internal 404, 29 internal links that redirect",2,2,3,Content
Security headers grade F,Security,"No HSTS, no CSP, no Permissions-Policy; server still accepts TLS 1.0",2,3,4,Developer
Images without alt attribute,Accessibility,"2 images with no alt attribute",1,1,1,ContentThe effort hours are our estimates for a typical developer; adjust them to your team. That's the point of the script — you change an assumption and every price and week updates consistently.
How BugViso Feeds the Proposal
The "What we found" section is only as good as the audit behind it. A BugViso scan of the prospect's site gives you the evidence in one pass:
- Performance: Core Web Vitals in a real Chromium render, a Slow/Fast 3G throttled-network LCP simulation, JavaScript and CSS code coverage (unused bytes per file), and long-task attribution.
- Privacy: the pre-consent audit — tracking cookies and tracker hosts on the bare, un-consented load, consent-platform detection and Google Consent Mode v2 state.
- Accessibility and mobile: axe-core WCAG A/AA checks, plus a mobile pass for tap targets (WCAG 2.5.8), viewport and horizontal overflow.
- SEO, links and security: broken and redirecting links, canonical and structured-data checks, security-header grade, TLS certificate inspection.
Attach the white-label PDF — your name, logo and brand colour — as the proposal's appendix, so the evidence is in your branding, with copy-paste fixes the prospect's developers can verify. When you win the work, the scan sits in your audit records, and a one-click re-audit with the same settings produces the before/after you promised in section 5. Scheduled weekly or monthly scans cover the optional monitoring line.
You can scan a prospect's site with BugViso before the discovery call, or run the same audit as an inbound website audit lead magnet. Branding fields and report contents are listed on the site crawl and white-label reports page.
Proposal Mistakes That Lose Deals
- Scaring instead of informing. Leading with "your site has 1,247 errors" makes the prospect defensive. Lead with one strength and three causes.
- Guaranteeing rankings. You can guarantee fixes and measurements, not positions. Promising rankings invites the wrong client.
- Hiding the method. Say how you measured (device, network profile, date). It makes the numbers credible and repeatable.
- Pricing without hours. A single number invites haggling; itemised hours invite a scope conversation.
- No expiry date. Findings age. "Valid for 30 days, then we re-scan before starting" keeps the evidence fresh.
FAQ
What should an SEO proposal include?
A short summary, the audit findings with measured evidence, why each matters to the business, scope and investment by phase (with exclusions), a week-by-week timeline, how results will be proven (re-audit date and metrics), and a single next step.
How do you price an SEO proposal?
Estimate hours per finding, multiply by your rate, group into phases and show the line items. Price ongoing monitoring separately from one-off fixes. The script in this guide does the arithmetic so every number traces back to a finding.
Should I include a full audit with the proposal?
Include it as an appendix, ideally as a white-labelled PDF. The proposal itself should carry three to seven findings; the appendix shows you did the thorough work.
How long should an SEO proposal be?
Two to four pages plus the audit appendix. If the summary and section 1 don't make the case, more pages won't.
Conclusion
Proposals built on the prospect's own measurements win because nobody else can copy them: show three to seven measured findings, price them by the hour, schedule them by capacity and promise a re-audit. A BugViso scan gives you that evidence — and the branded appendix — before the first call.
See where your site stands
Run a free BugViso audit for SEO, speed, accessibility and AI search readiness — with fixes you can ship today.