Skip to content

Schema.org JSON-LD Event markup on single-event page (+ Organization/Place embedding) #328

@Salem874

Description

@Salem874

ELI5

Right now our event pages have no machine-readable "this is an event" tag, so Google can't show them in its events carousel. We add a hidden JSON block that tells Google "here's the title, date, venue, organiser" — and our events start appearing in rich search results.

Detailed proposal

Add a Schema.org JSON-LD emitter — either a Portal\Core\Schema\EventLdBuilder helper or a _core/templates/event-jsonld.php partial — invoked from event.php for any single-event view. Build a @type=Event payload from tblEvents (name, description, startDate, endDate, heroImage), with:

  • eventStatus mapped from our existing status column (Scheduled/Cancelled/Postponedhttps://schema.org/Event{Scheduled,Cancelled,Postponed}).
  • eventAttendanceMode derived from tblEventLinksOfflineEventAttendanceMode default; OnlineEventAttendanceMode when a Zoom/livestream link is present and no physical venue; MixedEventAttendanceMode for hybrid.
  • location as a Place with nested PostalAddress populated from the event's location fields (fall back to the site's address from Site::branding() when the event inherits venue).
  • organizer as an Organization built from hostOrgName plus any partnerOrgs, using Site::productName() and brand URL as the publisher fallback.
  • image as an absolute URL (resolve via Site::absoluteUrl()), url as the canonical event URL.
  • Optional offers block when tblEvents.priceText / paid flag is set (free events → price: 0, availability: InStock).

Series-aware: when an event belongs to tblEventSeries, add superEvent pointing at the series page; recurring instances from tblEventRecurrence each get their own JSON-LD on their own occurrence URL (no aggregation hack). Categories from tblEventCategories / tblEventTypes populate keywords. Wire it through AppRegistry so calendar opt-in/opt-out toggles it; gate behind a seo.jsonld.events setting (default on). Validate output against Google's Rich Results Test before merge.

Pros

  • Pure additive output — no schema migration, no UI change, no auth surface.
  • Unlocks Google events carousel eligibility (high CTR premium on public event pages).
  • Reuses data already captured; brand-aware via Site::branding() for ChurchMS / SchoolMS variants.
  • Foundation for later Course, Organization, LocalBusiness markup on other pages.

Cons

  • Google rich-result eligibility is best-effort; not guaranteed indexing.
  • Invalid markup can trigger Search Console warnings — requires validator step in CI ideally.
  • Hybrid/recurring edge cases need careful mapping to avoid duplicate-event penalties.

How necessary?

Medium — high for public-facing church/community/school installs whose event discoverability matters; low for closed internal portals.

Acceptance criteria

  • event.php emits a single <script type="application/ld+json"> block for the rendered event.
  • eventStatus, eventAttendanceMode, location, organizer, image, url all populated correctly from existing tables.
  • Series events emit superEvent; cancelled/postponed events reflect status accurately.
  • Output validates clean in Google's Rich Results Test for at least three sample event types (in-person, online, hybrid).
  • Setting seo.jsonld.events (default on) toggles emission; respected by AppRegistry.
  • No PII (RSVP names, internal notes) leaks into the public JSON-LD.

Estimated effort

4-6 hours (one helper class + partial + admin toggle + manual validator pass).


Filed during The Events Calendar competitive analysis on 2026-06-16; decision pending.

Metadata

Metadata

Assignees

No one assigned

    Labels

    for considerationIdea parked for an owner decision before active workpriority: mediumNormal prioritytype: enhancementImprovement to existing functionality

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions