Pages

▼

07 October, 2026

What Is Structured Data? A Sitecore Developer's Guide to JSON-LD and Schema.org

Recently I worked on adding structured data to a corporate homepage on a Sitecore XP project. The site belongs to a brand with one corporate site and a long list of location sites, in this case hospitals. The homepage was the easy part. The real question was how to do this well across hundreds of locations.

While working on it, I realized structured data is one of those topics everyone has heard of but few teams fully understand. So I am starting a short series. I will keep it simple, start from the basics, and build up to a real Sitecore implementation.

Here is the plan for the series:

  1. What is structured data? (this post)
  2. Modelling a multi-location brand: corporate site and location sites
  3. Implementing structured data on the corporate homepage in Sitecore
  4. Describing a membership program with MemberProgram
  5. Generating structured data for hundreds of location pages
  6. Validating, deploying and monitoring your schema
  7. Measuring the impact: what you can and cannot expect

What is structured data?

Structured data is a small block of code on your page that tells search engines exactly what the page is about. It does not change what visitors see. It is written for machines.

Think of it this way. A person reading your homepage can tell that "Shine Hospitals" is a company, that the number in the footer is a phone number, and that the logo is a logo. A search engine has to guess. Structured data removes the guessing by labelling each piece of information.

Three terms you will see again and again:

  • Schema.org is the shared vocabulary. It defines types like Organization, LocalBusiness and Event, and properties like name, address and telephone. Google, Bing and others all use it.
  • JSON-LD is the format Google recommends for writing it. It sits in a <script type="application/ld+json"> tag, separate from your HTML markup. That makes it much easier to manage from a CMS like Sitecore.
  • Microdata and RDFa are older formats that add attributes directly to your HTML. They still work, but I will use JSON-LD throughout this series.

A simple example

Here is a simplified version of what a corporate homepage schema looks like in practice. I have used a made-up brand, Shine Hospitals, but the structure mirrors a real production implementation.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebSite",
      "@id": "https://www.shinehospitals.com/#website",
      "url": "https://www.shinehospitals.com/",
      "name": "Shine Hospitals",
      "publisher": { "@id": "https://www.shinehospitals.com/#organization" }
    },
    {
      "@type": "WebPage",
      "@id": "https://www.shinehospitals.com/#webpage",
      "url": "https://www.shinehospitals.com/",
      "name": "Animal Hospitals & Emergency Vets | Shine Hospitals",
      "isPartOf": { "@id": "https://www.shinehospitals.com/#website" },
      "mainEntity": { "@id": "https://www.shinehospitals.com/#organization" }
    },
    {
      "@type": "Corporation",
      "@id": "https://www.shinehospitals.com/#organization",
      "name": "Shine Hospitals",
      "url": "https://www.shinehospitals.com/",
      "logo": "https://www.shinehospitals.com/images/logo.png",
      "contactPoint": {
        "@type": "ContactPoint",
        "telephone": "+1-800-555-0100",
        "contactType": "customer service"
      },
      "sameAs": [
        "https://www.facebook.com/shinehospitals",
        "https://www.linkedin.com/company/shinehospitals"
      ],
      "hasMemberProgram": {
        "@type": "MemberProgram",
        "@id": "https://www.shinehospitals.com/careplus#memberprogram",
        "name": "Shine CarePlus",
        "url": "https://www.shinehospitals.com/careplus"
      }
    }
  ]
}
</script>

This looks like a lot, but it is just three things described together:

  • @graph holds several entities in one script block instead of writing three separate scripts.
  • WebSite describes the site as a whole.
  • WebPage describes this specific page, and says its main topic is the organization.
  • Corporation (a more specific type of Organization) describes the company: name, logo, contact details and official social profiles in sameAs.
  • hasMemberProgram describes the brand's membership or wellness plan program using the MemberProgram type. More on this in its own post.

The important part is @id. Each entity gets a unique identifier, and the entities refer to each other by it instead of repeating details. The website is published by the organization, the page is part of the website, and the page is about the organization. Later, every location page will point back to the same organization @id.

No visitor ever sees this. But a search engine now knows exactly who the company is, what the site is, and how they relate.

Why does it matter?

Structured data helps search engines understand your content with confidence. That understanding shows up in a few ways:

  • Rich results. Some schema types can make your listing stand out in search, for example with ratings, hours or breadcrumbs.
  • Knowledge panel and brand identity. Organization schema helps Google connect your logo, website and social profiles as one entity.
  • Local search. For businesses with physical locations, accurate address, hours and phone details support map and local results.
  • AI-powered search. Search features and AI assistants that answer questions directly rely on clear, machine-readable facts. Structured data is one of the cleanest ways to provide them.

It is also important to be honest about what it does not do:

  • It is not a direct ranking boost. Adding schema will not move you from page five to page one.
  • Valid markup does not guarantee a rich result. Google decides when to show them.
  • Google has reduced or retired some rich result types over the years, like FAQ and HowTo. Always check Google's current list of supported types before promising a specific result to stakeholders.

So I see structured data as hygiene: low effort, low risk, and it makes everything else in SEO work a little better.

Corporate site vs location sites

This is where it gets interesting for multi-location brands. Think of a veterinary or healthcare network: one corporate site, plus a page or microsite for every hospital.

Each level describes a different thing:

  • The corporate homepage describes the brand itself, using Organization.
  • Each location page describes one physical place people visit, using LocalBusiness or a more specific type like VeterinaryCare or MedicalClinic. It carries the address, opening hours, phone number and map coordinates for that location.

The key is to link them. Each location points back to the brand using parentOrganization and the brand's @id. Search engines then see one company with many branches, not hundreds of unrelated businesses.

Corporate site and location sites — one brand, many locations

The corporate schema is written once. The location schema has to work for hundreds of pages, so it should come from content rather than being typed by hand. That difference shapes the whole Sitecore implementation, which I will cover in parts 3 and 5.

Wrapping up

To summarize:

  • Structured data labels your content so search engines don't have to guess.
  • Use schema.org types, written as JSON-LD.
  • It supports rich results, brand identity, local search and AI-powered answers, but it is not a ranking shortcut.
  • Multi-location brands need two levels: Organization for the brand, and LocalBusiness (or a subtype) for each location, linked together.

In the next post, I will go deeper into modelling a multi-location brand: which properties matter for the corporate site and for each location, and how to connect them properly.

If you are working on something similar, I would love to hear how you approached it. Feel free to reach out.



06 October, 2026

Sitecore Scheduled Publish (for XM/XP) 10.5: Shipping Without Package Designer (Items as Resources, NuGet, Docker)

If you have been following my Sitecore Scheduled Publish module, you know it has been around for a while. The module lets content editors schedule a publish or unpublish of an item for a future date and time, with notifications. It was originally built by Hedgehog Development (Apache-2.0), and I maintain the actively developed version, with releases for Sitecore 10.2, 10.3, 10.4, 10.4.1, and now 10.5.

This post is about the 10.5 release — not just because it adds Sitecore 10.5 support, but because it required a completely different approach to packaging and distribution.

The Problem: No More Package Designer

Sitecore 10.5 disables Package Designer by default, and Sitecore recommends keeping it disabled. If you are a module author, this is a big deal. Sitecore modules have traditionally been built in Package Designer and installed with the Installation Wizard. That entire workflow is gone now.

So the question becomes: how do you ship a Sitecore module when the tool everyone used to build and install packages no longer exists?

The Answer: Items as Resources (IAR)

For this release, I moved entirely to Items as Resources (IAR). Instead of a Sitecore package that writes items into the database during installation, all module items now ship as .dat resource files. Installing the module means copying 6 files into your webroot. No package, no Installation Wizard, no database writes.

The module ships 57 items in master and 11 in core — templates, settings, the scheduled task definition, and the core ribbon, gutter, and field type registrations. These are the files:

bin/ScheduledPublish.dll
App_Config/Include/ZZ_ScheduledPublish/ZZ_ScheduledPublishControl.config
App_Data/items/master/items.master.schedule.publish.dat
App_Data/items/core/items.core.schedule.publish.dat
sitecore/shell/Applications/Content Manager/Dialogs/Schedule Publish/Schedule Publish.xml
sitecore/shell/Applications/Content Manager/Dialogs/Edit Scheduled Publish/Edit Scheduled Publish.xml

How to Install — Four Options

I wanted to make sure teams can install this however works best for their setup. Pick one of the following.

Option 1: NuGet (Recommended for CI/CD Pipelines)

Add the package to your Sitecore web project:

dotnet add package SCScheduledPublish

To stay on Sitecore 10.5 and pick up module updates automatically, use Version="10.5.0.*" in your PackageReference.

The DLL is referenced as usual. The config, IAR, and dialog files are automatically added to the web project's publish output at the correct paths. Deploy the way you normally do — Web Deploy, PaaS pipeline, or Docker build. The package is on nuget.org.

SCScheduledPublish NuGet package on nuget.org

Option 2: File-Drop Zip

Download the IAR zip from the latest GitHub release and extract it into your CM (and CD) webroot. On Azure PaaS, use Kudu or the zip deploy API. In a Docker image:

COPY ./scheduled-publish/ C:/inetpub/wwwroot/

Recycle the app pool (or restart the container) afterwards.

Sitecore Scheduled Publish 10.5 GitHub release page with IAR zip and NuGet artifacts

Option 3: Docker Module Asset Image

Pre-built module asset images are available on Docker Hub. The images follow the standard Sitecore module asset image layout (\module\cm\content). Use the tag that matches your CM image's Windows base: 10.5-ltsc2025 for Windows Server 2025, 10.5-ltsc2022 for 2022.

Copy the module files into your CM image using a multi-stage Dockerfile:

ARG BASE_IMAGE
ARG SCHEDULED_PUBLISH_IMAGE=nehemiah/sitecore-scheduled-publish:10.5-ltsc2022

FROM ${SCHEDULED_PUBLISH_IMAGE} AS scheduledpublish

FROM ${BASE_IMAGE}
...
COPY --from=scheduledpublish \module\cm\content .\

Option 4: From Source

Clone the Sitecore Scheduled Publish repo on GitHub and add the project to your solution. Deploy the files, then either push items to the database with the Sitecore CLI:

dotnet sitecore ser push -i ScheduledPublish

Or generate the IAR files yourself:

dotnet sitecore itemres create -i ScheduledPublish -o <webroot>/App_Data/items/schedule.publish

Then move each items.<db>.schedule.publish.dat into App_Data/items/<db>/.

Verify the Install

After installing through any of these methods, you can verify it worked:

  • The Content Editor Publish ribbon shows the Scheduled Publish strip.
  • /sitecore/system/Tasks/Schedules/ScheduledPublishTask and /sitecore/system/Modules/Scheduled Publish exist.
  • /sitecore/admin/showconfig.aspx contains the ZZ_ScheduledPublish settings.
Sitecore Content Editor Publish ribbon with Scheduled Publish strip

Versioning

Starting with 10.5, versions follow <Sitecore version>.<module revision>. So 10.5.0 was the first release for Sitecore 10.5, and 10.5.0.1, 10.5.0.2, etc. are module updates that do not require a new Sitecore version. This leaves 10.5.1 free for a future Sitecore 10.5.1 release. NuGet sorts these correctly.

On Docker Hub, the 10.5-<base> floating tags always point to the newest build for Sitecore 10.5, while exact tags like 10.5.0.1-ltsc2025 never change.

Available Docker Images

Here is the full list of Sitecore Scheduled Publish Docker images:

TagSitecore VersionWindows Base
10.5-ltsc202510.5Windows Server 2025
10.5-ltsc202210.5Windows Server 2022
10.5.0.1-ltsc202510.5Windows Server 2025
10.5.0.1-ltsc202210.5Windows Server 2022
latest10.5same as 10.5-ltsc2022
10.4.1-ltsc202210.4.1Windows Server 2022
10.4.1-180910.4.1Windows Server 2019
10.4-ltsc202210.4Windows Server 2022
10.4-180910.4Windows Server 2019
10.3-180910.3Windows Server 2019
10.2-180910.2Windows Server 2019

Sitecore Scheduled Publish Docker images on Docker Hub with ltsc2025 and ltsc2022 tags

Sitecore 10.5 adds support for Windows Server 2025 containers and drops 1809 / ltsc2019, so the 10.5 images follow the same pattern. The 10.5 images include OCI labels for version, source commit, build date, and license.

While working on the Docker images, I found that the existing 10.4.1-1809 tag actually contained a Windows Server 2022 image — which would fail on Windows Server 2019 hosts. I rebuilt it on a proper 1809 base and verified the files byte-for-byte.

IAR Upgrade Guidance: What to Clean Up and What to Keep

This is the part I think the community will find most useful, because the IAR upgrade behavior is not obvious.

The key thing to understand: database copies take precedence over IAR files. If an item from an IAR .dat file also exists in the database (because it was installed via the Installation Wizard or ser push in a previous version), the database copy wins. That means module updates shipped through IAR will stay hidden for those items.

So the question is: when do you need to clean up those database copies?

SituationCleanup Needed?
Fresh installNo. Nothing is in the database.
Upgrade from Installation Wizard or ser push installYes. All module items are in the database and override the new IAR items.
Upgrade from a 10.4 / 10.4.1 IAR installUsually no. Only items written at runtime are in the database.

But you need to be careful about what you clean up. There are items you should keep in the database:

  • ScheduledPublishTask — Sitecore copies this to the database on every run to save the "Last run" timestamp. You will see migrated to head provider in the logs. This is expected.
  • Customized settings — If an admin has changed the module's settings under /sitecore/system/Modules/Scheduled Publish, a forced cleanup would reset them.
  • Editors' schedules — Active publish schedules created by content editors under the Publish Schedules folder. These are normal database items, not part of the IAR files.

The safe approach is to use dotnet sitecore itemres cleanup in stages. Log in to the CM with dotnet sitecore login first.

Step 1 — Preview what would be removed:

dotnet sitecore itemres cleanup --what-if

Step 2 — Remove copies that are identical to the IAR version. Customized items are skipped automatically:

dotnet sitecore itemres cleanup

Step 3 — Force-remove only the module definitions that nobody edits by hand, so the new template and ribbon versions take effect:

dotnet sitecore itemres cleanup -p "/sitecore/templates/Scheduled Publish" -r --force

The documentation lists all the core-database definition paths for Step 3.

Never use --force on /sitecore/system/Modules/Scheduled Publish. It would reset your email and section settings to the defaults.

Tested on Real Sitecore 10.5

IIS (XM, Sitecore Kernel 20.0.158): The zip installed cleanly. All 57 master and 11 core items loaded from IAR with 0 errors. I ran through the full scheduled publish cycle — create a schedule, wait for the ScheduledPublishTask to fire, confirm the item publishes to web, verify the schedule updates. One thing to know: the scheduled publish runs on the first Master_Database_Agent check after the scheduled time. That check runs every 10 minutes by default, so a publish can run up to about 10 minutes after the scheduled time. On IIS it ran about 9 minutes after. The interval is configurable — see Job Interval Configuration in the docs.

Docker (XM1, ltsc2025): Built the CM image from the published Docker Hub image layered onto scr.sitecore.com/sxp/sitecore-xm1-cm:10.5-ltsc2025. All containers came up healthy, all 68 IAR items loaded, 0 errors. The full scheduled publish cycle passed here too — schedule created, ScheduledPublishTask fired about 2 minutes later, published the item to web, schedule updated. Run took about 1.4 seconds.

Why This Matters

It is an example of how a community module can ship for Sitecore 10.5 without Package Designer. The approach — IAR files, NuGet with build targets, Docker asset images, and CI with Trusted Publishing — can serve as a reference for other module authors who need to move away from the Installation Wizard.

The module is also ready for Windows Server 2025 containers from day one, which is new in Sitecore 10.5.

Build and Release Automation (For Module Authors)

For anyone maintaining a Sitecore module, the build and release pipeline might be useful as a reference. The entire process is automated with GitHub Actions.

When I push a Sitecore_<version> tag, the workflow generates IAR files from serialized items using Sitecore CLI, builds the DLL, produces the file-drop zip, packs the NuGet package, publishes to nuget.org using NuGet Trusted Publishing (OIDC — no API key stored anywhere), and creates a GitHub release with all artifacts attached.

You can also build locally with PowerShell:

pwsh ./scripts/Build-Package.ps1 -Version 10.5.0.1

To also build the Docker images (the script lists every Docker tag to push, including the exact 10.5.0.1-* tags):

pwsh ./scripts/Build-Package.ps1 -Version 10.5.0.1 -DockerRepository nehemiah/sitecore-scheduled-publish
docker push nehemiah/sitecore-scheduled-publish:10.5-ltsc2025
docker push nehemiah/sitecore-scheduled-publish:10.5.0.1-ltsc2025
docker push nehemiah/sitecore-scheduled-publish:10.5-ltsc2022
docker push nehemiah/sitecore-scheduled-publish:10.5.0.1-ltsc2022

Getting the CI pipeline right took a few iterations — I had to add a repo-level NuGet.config with the Sitecore feed, fix .nupkg path resolution for Windows runners, and make the release step rerunnable. All fixes are in the repo as separate PRs (#24, #25, #26).

Links

If you are maintaining a Sitecore module and need to figure out the post-Package Designer distribution story, take a look at the repo. The build script, GitHub Actions workflow, NuGet targets, and Docker setup are all there. If you find it useful, a star on GitHub helps with visibility.