How to write SEO requirements developers actually ship
An SEO requirement gets shipped when an engineer can estimate it, a product manager can rank it against features, and QA can verify it without asking what you meant. In ticket terms, that means five sections (the issue, a description, the level of effort, the expected impact and a priority score) plus acceptance criteria written as checks anyone on the team can run.
Those five sections are what an SEO ticket I'd be proud of contains. In what follows, the first-person parts describe how I work, and the rest is general guidance, with Google's documentation linked wherever it sets the rule.
A ticket in five sections
Here is a complete ticket for a common fix. The URLs, counts and scores are hypothetical.
ISSUE
Product pages declare a relative canonical (href="/p/blue-widget"),
and URLs with sort or color parameters declare themselves canonical.
DESCRIPTION
Where: product template, every URL under /p/
Why: Google's canonical guide asks for absolute URLs and only
accepts rel="canonical" in the head. Parameter variants that point
to themselves compete with the clean product URL.
Change: output one absolute canonical, pointing to the
parameter-free production URL, on every /p/ page.
Out of scope: category and search templates.
Acceptance criteria: AC1 to AC6 below.
LEVEL OF EFFORT
Engineering estimate: S, about one week (one shared head component)
EXPECTED IMPACT
About 12,000 product URLs (hypothetical). Signals consolidate on one
URL per product, and parameter duplicates should leave the index
over the following weeks. Confidence: high.
PRIORITY SCORE
Impact 3 x Confidence 1.0 / Effort 1 = 3.0Issue
One or two sentences that state what is wrong and where, in terms anyone can observe. Keep the solution out of it. An issue that says "add canonicals" invites a debate about the fix before anyone agrees there's a problem.
Description
The description carries the scope (templates and URL patterns), the reason, the change, what is out of scope, and the acceptance criteria. Link the primary source for the reason, so the engineer reads the rule rather than taking your word for it. For canonicals, that's Google's guide to specifying a canonical URL, which asks for absolute URLs, only accepts the link element inside the head, and warns against declaring different canonicals for the same page through different methods. If the team needs background on how Google weighs those signals, this post on how Google treats canonical tags goes deeper.
Level of effort
The estimate belongs to engineering. Your part is making the work estimable: name the templates, give example URLs, and say whether the change touches a shared component or a single page type. A ticket that says "all pages" is hard to estimate, and work that can't be estimated is hard to schedule.
Expected impact
State which URLs are affected, through which mechanism (crawling, indexing, rendering or how results display), and how confident you are. Avoid promising rankings or traffic figures. A range with its reasoning holds up in a planning meeting far better than a single number nobody can check.
Priority score
Last comes a number the backlog can sort by, so the ticket competes with feature work on the same terms. The scoring section below shows one simple way to build it.
Acceptance criteria QA can run without you
I write acceptance criteria that explain exactly what needs to be looked for: on which environment, whether that's a staging site or not, with which testing parameters, and where in the HTML. The aim is that someone who has never spoken to you can pass or fail the change from the text alone.
In general terms, a criterion that meets that bar names five things:
The environment and the URL, plus whatever is needed to reach the change, such as the staging login or a query parameter that switches the feature on.
The view to check. The raw HTML response (view-source or a command-line fetch) and the rendered DOM (the browser's Elements panel, or the tested page in URL Inspection) differ on JavaScript-heavy pages.
The element and its exact expected value.
What must not appear, alongside what must.
A sample that covers edge cases: parameter URLs, paginated pages, empty or out-of-stock states.
Naming the view matters because raw and rendered HTML can disagree on exactly the tags SEO depends on. Google's JavaScript SEO basics note that when the original page code contains noindex, Google may skip rendering, so JavaScript written to remove that tag may never run. A criterion about noindex therefore has to be checked in the raw response. The same guide says Google picks up a canonical injected by JavaScript at render time but doesn't recommend that approach, which is one more reason to name the view in each check.
A second trap sits in robots.txt. The robots meta tag documentation explains that rules on a page disallowed in robots.txt are never found, so they are ignored. Any criterion that asks for noindex on a URL needs a partner criterion confirming the URL is crawlable. For the rules on what robots.txt can and can't do, see this guide to robots.txt.
Staging creates its own limits. Google's guidance for keeping private content out of Search is password protection, which is right for a staging site, but it also locks out Google's testing tools. The URL Inspection help page says a live test needs a page reachable from the internet without any login, and suggests a tunnel for pages behind a firewall. The Rich Results Test accepts pasted code, which works for checking markup taken from a protected staging page.
Here are the criteria for the canonical ticket above. The hostnames and the ff_canonical parameter are hypothetical.
AC1. On staging, open https://staging.example.com/p/blue-widget?ff_canonical=on after the staging login and view the raw HTML (view-source, not the Elements panel). The head contains exactly one link element with rel="canonical".
AC2. Its href is absolute and equals https://www.example.com/p/blue-widget, with the production host, https, no query string and no trailing slash, matching the production URL pattern.
AC3. The same page with ?sort=price&color=blue added returns the same canonical href as AC2.
AC4. After the page finishes loading, the Elements panel still shows one canonical, with the same href. No second canonical appears in the body, and the response headers carry no canonical in an HTTP Link header.
AC5. The raw HTML has no meta robots tag containing noindex, and the response headers have no X-Robots-Tag containing noindex.
AC6. After release, AC1 to AC5 pass on www.example.com for five named product URLs, including one out-of-stock product and one URL carrying a long parameter string.
Checks by type of change
Different changes call for different evidence. This table gives a starting point for the most common SEO tickets, each with where to look and what counts as a pass.
| Change | Where to look | Pass condition |
|---|---|---|
| Remove noindex from a template | Raw HTML and response headers | No noindex in meta robots or X-Robots-Tag, and the URL is not disallowed in robots.txt |
| Server-render content that loaded client-side | Raw HTML from a plain fetch | The target text is present before any JavaScript runs |
| Add or fix structured data | Rich Results Test (code on staging, URL on production) | Item detected with no errors, and marked-up values match the visible page |
| Return a real 404 for removed products | HTTP status of the response | 404 or 410, never 200 with "not found" text |
| JavaScript navigation or filters | Rendered DOM | Links are a elements with an href to a crawlable URL |
The last two rows come straight from Google's JavaScript guide, which asks for meaningful status codes and says Google can only discover links that are a elements with an href attribute. This post on JavaScript SEO covers what else tends to break on client-rendered sites, and the overview of schema markup types helps decide which structured data tickets deserve a slot at all.
Getting SEO work prioritized against features
I make the case with data, and with how visibility in search and AI surfaces is critical to the product's success.
The data side works best in the units a product manager already uses. Name the templates involved, their share of organic entrances or sign-ups from your own analytics, the mechanism by which the fix changes that, and what waiting costs. A hypothetical line such as "Product pages bring in most of our organic sign-ups, and their canonicals send mixed signals" lands better than a list of crawl warnings.
The AI side has a solid footing in Google's own documentation. Its page on AI features and your website says there are no extra requirements or special optimizations for AI Overviews or AI Mode. To appear as a supporting link, a page must be indexed and eligible to be shown in Google Search with a snippet. The crawling, indexing and rendering tickets in an SEO backlog are therefore prerequisites for both surfaces at once. One caveat for reporting: the same page says traffic from these features is included in Search Console's overall Web search figures, so don't promise a separate AI number from that report.
Scoring priority
A priority score turns that case into something a backlog can sort. A simple version is impact times confidence, divided by effort. It is a trimmed-down cousin of the RICE score described by Intercom, which also multiplies by reach. Here, reach is folded into impact. A hypothetical backlog might score like this:
| Ticket (hypothetical) | Impact (1 to 3) | Confidence | Effort (weeks) | Score |
|---|---|---|---|---|
| Absolute canonicals on product pages | 3 | 100% | 1 | 3.0 |
| Width and height on article images to reduce CLS | 1 | 80% | 0.5 | 1.6 |
| Server-render category descriptions | 2 | 80% | 3 | 0.53 |
| Rebuild faceted URLs as clean paths | 3 | 50% | 8 | 0.19 |
Small, well-understood fixes rise to the top, and large uncertain bets sink until something raises confidence, such as a trial on one section. The score only compares tickets scored by the same people in the same backlog. It is a sorting tool, and it makes no forecast.
Release QA
After a release, I do a full QA of exactly the change that was expected. In general terms, that means rerunning every acceptance criterion on production, then checking what the change could have broken by accident, then confirming Google can see the result.
Rerun AC1 onward on the production URLs named in the ticket, in the raw HTML or the rendered DOM as each criterion specifies, then on a few URLs the ticket didn't name.
Fetch robots.txt and confirm it matches the pre-release version, unless the ticket changed it.
Check status codes on a sample from each affected template: 200 where expected, a single 301 to the right target for redirected URLs, 404 or 410 for removed ones.
Confirm staging settings didn't ship: no noindex, no login wall, and no staging hostnames in canonicals, hreflang or sitemaps.
Run a URL Inspection live test on two or three changed URLs and read the rendered HTML. The help page is clear that a passing live test confirms Google can access the page, not that it will be indexed.
Validate structured data on production URLs with the Rich Results Test if markup changed.
Write down the release date and time, so a later dip in monitoring can be matched to it.
The indexed version catches up over the following days and weeks, as Google recrawls the changed URLs and URL Inspection starts showing the Google-selected canonical for each. An agent that inspects a fixed sample of URLs every day, as described in automating SEO monitoring with AI agents, turns that wait into a trend you can watch. For releases that move URLs or change hosts, the site migration checklist adds the redirect and monitoring steps.
Before you write the next ticket, take one you already filed and rewrite its acceptance criteria so that someone who has never spoken to you could pass or fail it from the text alone.