Writing schema implementation tickets that do not require a follow-up meeting

TL;DR: This article walks through the schema implementation ticket structure and writing approach I have developed over 15 years of SEO consulting: parent tickets for global schemas, child tickets per page type, and within each ticket a consistent anatomy of Detection, Current Implementation, Required Changes, Complete Implementation, Notes, and Placement. The core principle throughout: do the research, make the decisions, and hand over a specification that respects the dev team’s time and expertise. Specify what the schema should look like, never how to build it.


As an SEO specialist, when you work on schema.org implementation, your deliverable is normally not the actual implementation. It is the ticket you hand to the dev team. Whether you are in-house, at an agency, or freelancing, you write the specification and hand it over to the people who push code to production. What happens after that is beyond your full control, but the quality of the ticket has a direct influence on the outcome: a ticket that leaves room for interpretation will eventually generate a meeting, a delay, or a rework cycle that could have been avoided.

Here is how I write schema implementation tickets: how they are structured, what goes into each section, and which decisions matter most. I use a real example throughout: a set of tickets for newbalance.com covering their shared global schemas and their product detail pages.

I am not affiliated with New Balance in any way. I picked this example because the Fresh Foam 1080 is my favourite running shoe. Their product pages already have solid schema coverage, but like most websites (including the ones I work on for my clients), there is room for improvement.

Make the dev team’s life easier

The single most useful rule I have found for writing schema tickets is this: my job is to make the dev team’s life easier. They have limited time and competing priorities. My schema ticket sits in a backlog alongside feature requests, bug fixes, and infrastructure work. If the ticket is clear and complete, it gets picked up, implemented, and closed. If it creates questions, it creates meetings, and meetings in this context are a sign that the ticket did not do its job.

This means doing some important work upfront. Every decision about which schema type to use, which properties to include, how entities reference each other: that is my job. The dev team should never have to open the schema.org website to understand my ticket, and they should never have to choose between two approaches I could not decide on. The ticket should contain answers, not questions.

A caveat before I go further: the approach I describe here is based on the teams I have worked with over 15 years of SEO consulting. Different organisations work differently, and some dev teams are more involved in schema design decisions than others. What I describe is a model that has worked well for me, not a universal standard.

Structure the work: parent and child tickets

I structure schema implementation as a set of tickets: one parent ticket for schemas that are identical on every page, and one child ticket per page type or group of page types that receive the same schema implementation.

The parent ticket typically covers two or three schemas. Organization is almost always one of them: the global entity that describes the company, with an @id that child tickets can reference. BreadcrumbList is another common candidate, since breadcrumb markup follows the same structure everywhere it appears. WebSite with SearchAction goes in the parent ticket too, since it describes the site as a whole rather than any specific page type and it saves me from creating a separate child ticket for the home page.

Child tickets cover everything that is specific to a page type. A product detail page gets Product. An article page gets NewsArticle or BlogPosting. An event page gets Event. Each child ticket is self-contained: a developer should be able to pick it up and implement it without reading the other child tickets. The only dependency is the parent ticket, and even that dependency is limited to knowing the @id values for shared entities like Organization.

I will walk through each section of the ticket using a child ticket as the main example, since that is where most of the interesting decisions happen. After that, I will cover what makes the parent ticket different.

Anatomy of a ticket

Every child ticket follows the same structure. Consistency matters: once the dev team has read one ticket, they know how to read all of them.

If your organisation already has an established ticket template or conventions for how developer tasks should be structured, the content I describe below should be adapted to fit. The sections and decisions are what matter, not the exact format.

Detection

Each ticket opens with a detection rule that tells the dev team which pages the schema applies to:

Detection: URL pattern /pd/{slug}/{productGroupID}.html

This can be a URL pattern, a body class, a dataLayer value, an internally established page type name, or a combination. The important thing is to speak the dev team’s language and use identifiers that they will understand without ambiguity. “All listing pages” forces them to figure out what “listing page” means in their system: category pages? filtered views? search results? sale pages? A specific URL pattern or page type identifier gives them something they can map directly to their routing or template logic.

Current Implementation

This section shows exactly what schema exists on the page today. It is a copy of the live JSON-LD, extracted from the page source and formatted for readability.

Accuracy here is critical because everything that follows depends on it. The Required Changes section is essentially a diff against the Current Implementation. If the current state has copy/paste errors, missing properties, or accidental edits from formatting, the diff will be wrong, and the dev team either implements something incorrect or comes back with questions.

The New Balance product page has three separate JSON-LD blocks at the time of writing: a ProductGroup entity, a floating AggregateRating block, and a floating Reviews block. Here is the ProductGroup (shortened for this article):

{
  "@context": "http://schema.org/",
  "@type": "ProductGroup",
  "productGroupID": "M1080V15_RU-FTW-802829-PMG-NA",
  "name": "1080v15",
  "description": "We're in it for the long run. The 1080v15 is everything...",
  "url": "https://www.newbalance.com/pd/1080v15/M1080V15_RU-FTW-802829-PMG-NA.html",
  "brand": {
    "@type": "Organization",
    "name": "New Balance"
  },
  "hasVariant": [
    {
      "@type": "Product",
      "sku": "M10807E3-2E-07",
      "offers": {
        "availability": "http://schema.org/InStock",
        "@type": "Offer",
        "price": "169.99"
      }
    }
  ]
}

471 variants total; only the first is shown here.

A few things are visible in this excerpt. The @context uses http instead of https. The brand is an inline Organization object instead of a reference to a shared entity. The availability and itemCondition values use http URLs. These are all things that the ticket addresses in the Required Changes section, and they are visible here because the Current Implementation is a faithful copy of what is on the page, not a version with placeholders.

Required Changes

This is where the ticket tells the dev team what to do. I use four categories, always in this order: Keep, Update, Add, and Remove.

Keep comes first. It tells the dev team which properties are already correct and should not be changed. Without an explicit Keep list, someone might reasonably wonder whether the absence of a property from the Update or Add lists means it should be removed.

Update covers properties that exist but need changes. Add covers new properties. Within Add, I make a distinction that matters: “Add” means always required, while “Add (when [condition])” means required when the condition is met. I never use “Add (optional)” because it sounds skippable. If data is available on the page, the property should be in the schema. The condition is about data availability, not preference.

Remove is for properties or entire blocks that should go. In this example, the floating AggregateRating and Reviews blocks are removed because their data gets consolidated into the main ProductGroup entity.

Here is the Required Changes section from the product ticket:

  • Keep: All ProductGroup properties: productGroupID, description, name, weight, url, variesBy, and all variant properties (sku, color, size, offers, image per variant).
  • Update:
    • Change @context from http://schema.org/ to https://schema.org (remove trailing slash, use HTTPS).
    • Change brand from an inline Organization object to a reference: "brand": { "@id": "https://www.newbalance.com/#organization" }.
    • Change all variant offer availability values from http://schema.org/InStock to https://schema.org/InStock.
    • Change all variant offer itemCondition values from http://schema.org/NewCondition to https://schema.org/NewCondition.
  • Add:
    • image property at ProductGroup level.
    • aggregateRating object nested directly in ProductGroup.
    • review array nested directly in ProductGroup.
  • Remove:
    • Blocks 3 and 4 as separate JSON-LD entities. Their data is consolidated into the ProductGroup block.
    • Empty image: [] and video: [] arrays from individual reviews.

Every instruction is specific. Not “update the @context” but “change @context from http://schema.org/ to https://schema.org (remove trailing slash, use HTTPS).” The dev team should never have to figure out what the new value is supposed to be.

The brand update is worth highlighting: it changes from an inline Organization object to {"@id": "https://www.newbalance.com/#organization"}, which is a reference to the Organization entity defined in the parent ticket. When a shared entity has an @id, child tickets reference it instead of repeating the full definition. This prevents duplication and means that if the Organization entity ever needs to change, it changes in one place only.

Complete Implementation

This section provides the full target JSON-LD: the exact code that should be on the page after implementation, using real data from the actual page, never placeholders.

Real data matters more than it might seem at first. A code example with [insert-image-url] or "Description goes here" introduces ambiguity that the dev team has to resolve on their own. An example with the actual image URL, the actual product description, and the actual review text leaves nothing to interpretation. Every value in the code example should be something the dev team can verify against the live page.

Dynamic values that change per page get inline comments marking their source: // identical to H1, // identical to meta description. The comment format “identical to [source]” is deliberate: it tells the dev team where the value comes from without telling them how to get it in their code. They know their codebase and their data pipelines far better than I do.

Comments are minimal. Self-explanatory fields like @context or @type get no comment. The only fields that need annotation are the ones where the value changes per page and the source might not be immediately obvious.

Notes

This is the hardest section to get right, because the instinct is to include more rather than less.

A good Notes section contains factual data points that help clarify the expected output: variant counts, data discrepancies, edge cases. It stays away from reasoning (“we chose X because…”), implementation guidance (“derive this from the lang attribute”), and research tasks (“please investigate whether…”).

From the product ticket:

  • 471 variants present for this product; the code example shows the first variant as a reference.
  • The review block currently contains 8 reviews; the complete set should be included in the implementation.
  • Empty image and video arrays on reviews should be omitted when no media is attached.
  • AggregateRating reviewCount in the current schema (168) differs from the visible review count on the page (858); please verify the correct count with the review data source.

The variant count tells the dev team not to worry about the truncated example, and the reviewCount discrepancy flags a genuine data question without speculating about the cause. None of these notes tell the dev team where in the HTML to find a value or how to structure their backend logic.

The temptation to add what feels like helpful context is strong. Things like “derive the ISO code from the lang attribute on the html tag” or “extract each contact’s name from the card component.” These sound helpful, but they tell the dev team things they already know about their own codebase, and they shift the ticket from specification to implementation guide. The ticket should specify what the schema should look like. How to produce it is the dev team’s domain, and they are better equipped to make those decisions than I am.

Placement

The final section tells the dev team where the schema goes:

Place this schema in a separate <script type="application/ld+json"> tag in the <head> section, alongside the Organization and BreadcrumbList schemas from the parent ticket.

Critical requirement: The schema must be present in the HTML returned from the server (visible in “View Page Source”), not dynamically injected by JavaScript.

This section is identical across all child tickets. The server-side rendering requirement gets its own line because the dev team may default to JavaScript injection depending on their technology stack, and schema that exists in the DOM but not in the initial page source can be missed by bots and crawlers processing the raw HTML.

What to leave out

Every word in a schema ticket should help the dev team implement. Learning what to leave out matters as much as knowing what to include.

The most important thing to leave out, in my experience, is implementation suggestions. The ticket specifies what the JSON-LD should look like, not how to build it. You are looking at rendered HTML in a browser. The dev team is working with templates, APIs, and data models. Specifying the output and trusting them to produce it is the single most effective way to respect their expertise. The moment a ticket starts prescribing how to traverse the DOM or where to pull values from, it oversteps its role and risks being wrong about things the dev team understands far better.

Reasoning does not belong in the ticket either. If you chose Article over BlogPosting, the ticket says Article. Not “we considered BlogPosting but chose Article because…” The decision happened during your analysis. In my experience, the dev team does not need the rationale to implement correctly, and including it adds noise to what should be a clean specification. That said, if you work with a team that wants to understand the reasoning, a separate analysis document is a better place for it than the ticket itself.

Alternatives do not belong in the ticket. Including a sentence like “you could also use X instead of Y” signals that you have not actually made the decision, and making schema.org design decisions is your job as the SEO specialist, not theirs. Presenting options to the dev team puts them in a position where they have to evaluate schema types without the context you had when you did the analysis.

Reference materials like schema.org property definitions, language code tables, links to Google’s documentation belong in your research notes, not in the ticket. If the ticket is specific enough, the dev team should not need to look anything up.

What remains is a ticket that reads like a clean specification: here is what exists, here is what should exist, here is the exact target code, and here are the facts you need to know.

The parent ticket: due diligence that pays off

The parent ticket follows the same structure as the child tickets, with slight differences: it covers multiple global schemas (typically Organization, BreadcrumbList, and WebSite) in a single ticket, and it includes an overview table that maps the entire project.

Here’s an excerpt from the New Balance example:

Page TypeCurrent SchemaAction Required
HomeNoneAdd Organization, WebSite
Product Detail (PDP)Organization, BreadcrumbList, ProductGroup, AggregateRating, ReviewsFix Organization, Fix BreadcrumbList (plus page-specific schemas in separate child ticket)
Category/Product Listing (PLP)BreadcrumbListFix BreadcrumbList, Add Organization (plus page-specific schemas in separate child ticket)
(additional page types omitted for brevity)

This table gives the dev team a picture of the full project, and it makes explicit which schemas are handled by the parent ticket versus which ones are covered in child tickets. Every page type appears in the full version, including the ones that do not need page-specific schemas.

Where the parent ticket becomes especially valuable is in the details. Schema that has been on a site for a long time tends to accumulate small issues that nobody has had a reason to review. The Current Implementation section surfaces these, which is one of its most useful functions.

In the New Balance example, the Organization schema has production-web-newbalance.demandware.net as the url property at the time of writing. This is an internal Salesforce Commerce Cloud hostname rather than the canonical domain. The fix is straightforward (update to https://www.newbalance.com), and the ticket makes it visible by showing the current state in full.

The sameAs array has a similar pattern. It includes https://twitter.com/newbalance, which still works as a redirect but is no longer the canonical URL after the platform rebrand. The Instagram URL lacks the www. subdomain normalisation. YouTube and TikTok profiles are linked in the site footer but are not yet included in the schema. None of these are critical issues individually, but a thorough Current Implementation review catches all of them in one pass, before they get carried over into the target schema.

The BreadcrumbList has position numbering starting at 0 instead of 1, and item @id values are relative paths (/men/) rather than absolute URLs (https://www.newbalance.com/men/). Schema.org specifies that positions should start at 1. These are the kinds of issues that a Current Implementation review makes visible and that the Required Changes section then addresses with specific instructions.

These examples share a pattern. The value of the parent ticket is not just in specifying the correct target schema. It is in documenting exactly what exists today, surfacing what needs attention, and providing specific instructions for each change. A ticket that says “please add Organization schema to all pages” leaves a dozen decisions unmade. A ticket that says “the Organization schema currently exists on product pages with these specific issues, here are the required changes, and here is the complete target implementation for all page scenarios” gets implemented correctly the first time.

Adapting this approach to your situation

This level of formality is not always the right fit. Some organisations have their own ticket templates and conventions that take priority. Some dev teams prefer to be involved in schema design decisions rather than receiving finished specifications. The structure and level of detail should always be adjusted to the people and processes you are working with.

What I would argue is universal, regardless of the format, is the practice of documenting what exists today, specifying what should change, and providing a complete target implementation with real data. Whether that lives in a JIRA ticket, a Wrike task, a Notion page, or a shared document, the underlying work is the same.

Writing good schema tickets is a skill that improves with practice and with feedback from the people who implement them. Every question a dev team comes back with reveals something the ticket could have answered. Over time, these conversations shape the ticket structure and the rules you apply when writing.

Summary: My rules for schema ticket writing

  • The ticket is my deliverable. Its quality determines whether the implementation is successful.
  • I structure the work as one parent ticket for global schemas and one child ticket per page type.
  • I use a consistent ticket anatomy, e.g.: Detection, Current Implementation, Required Changes, Complete Implementation, Notes, and Placement.
  • Detection should use identifiers the dev team recognises, not vague page type descriptions.
  • Current Implementation must be an exact copy of the live schema, not a cleaned-up version.
  • Required Changes uses four categories in order: Keep, Update, Add, and Remove.
  • Every instruction should be specific enough that the dev team never has to look up the target value.
  • Complete Implementation uses real data from the actual page, never placeholders.
  • Dynamic values get inline comments with “identical to [source]” to mark where data comes from.
  • Notes contain factual data points only: no reasoning, no implementation guidance, no research tasks.
  • The parent ticket includes an overview table that maps the entire project and all page types.
  • I specify what the schema should look like, never how to build it.

The core principle is simple: I do the research, make the decisions, and hand over a specification that respects the dev team’s time. The structure I have described here is the vehicle I have used for that principle across 15 years of consulting. It is not the only way, but it works.

I am looking forward to hearing how others approach schema implementation handoffs. If you have a different process, or if something in this article was useful, please leave a comment or get in touch.

2 responses

  1. Very well done, and not only a great start for the human developer, this is also a solid instruction set for an AI based developer.

    While not defining how to do the development, there are some technical recommendations and considerations to include. For instance “the full schema should load in under “x” seconds without user intervention (scroll, click, etc.)”. You could suggest or require server side rendering, but this might conflict with the “let the developers develop”.

    A testing protocol would also be recommended, and would suggest both Google Markup Validator and Schema.org evaluations.

    Many would also benefit from examples of outside sites who have executed it well. This provides some level of concrete example.

    1. Thanks, Ted! The requirement to include the schema in the server-side HTML is already in the article. On the testing protocol: Yes, this is generally a good idea, but I normally prefer to do the testing myself, instead of writing instructions for the devs on how to test. Regarding examples from other sites: Perfect implementations are rare and very hard to find, so I normally avoid that.

Leave a Reply

Your email address will not be published. Required fields are marked *