I've built physician directory sites for multi-location health systems, solo dermatology practices, and everything in between. And the single biggest missed opportunity I see across healthcare websites is structured data. Not the generic LocalBusiness kind -- I'm talking about the specific Physician and MedicalSpecialty schema types that tell Google exactly what a provider does, where they practice, and why patients should trust them.

Most medical practice websites ship with zero schema markup or, worse, incorrect markup generated by some WordPress plugin that treats a cardiology practice the same as a pizza shop. This guide gives you production-ready JSON-LD for real medical specialties -- cardiology, pediatrics, urology, orthopedics, and more -- along with the reasoning behind every property choice.

Table of Contents

Medical Practice Schema Markup: JSON-LD for Every Physician Specialty

Why Medical Schema Markup Matters in 2026

Google's AI Overviews now dominate healthcare queries. When a patient searches "cardiologist accepting new patients near me," Google doesn't just scan your page text anymore -- it reads your structured data to build entity relationships, populate knowledge panels, and feed AI-generated answers.

Here's what proper medical schema markup can trigger:

Rich Result Type Triggered By Impact
Review snippets AggregateRating on Physician Star ratings in SERPs
Knowledge panel Physician + sameAs links Brand presence for named doctors
Local pack enhancement MedicalClinic + openingHours Map listing details
FAQ rich results FAQPage schema (healthcare sites still eligible) Expanded SERP real estate
AI Overview citations Complete entity graph Cited as source in AI answers

A 2025 BrightLocal study found that healthcare listings with review snippets saw 37% higher click-through rates than those without. And Schema App's research confirmed that physician pages with complete structured data were 2.8x more likely to appear in AI Overview citations.

The bottom line: if your physician pages don't have proper schema markup, you're leaving visibility on the table.

Schema.org Types for Medical Practices

Before we write any code, let's understand the type hierarchy. Schema.org gives us several types that are relevant to healthcare, and picking the right one matters.

Thing
└── Organization
    └── LocalBusiness
        └── MedicalBusiness
            ├── MedicalClinic
            ├── Optician
            ├── Pharmacy
            └── Physician

Physician is a subtype of MedicalBusiness, which is itself a subtype of LocalBusiness. This is important because it means a Physician entity inherits all the local business properties (address, hours, geo coordinates) plus medical-specific properties like medicalSpecialty and hospitalAffiliation.

For a solo practitioner's website, Physician is your primary type. For a multi-provider clinic, you'll want a MedicalClinic or MedicalOrganization as the parent entity with individual Physician entries nested or linked via member or referenced on separate pages.

When to Use What

Your Setup Primary Type Secondary Types
Solo doctor, one location Physician MedicalOrganization (optional)
Group practice, single location MedicalClinic Physician per provider page
Health system, multiple locations MedicalOrganization MedicalClinic per location, Physician per provider
Hospital Hospital Physician, MedicalClinic (departments)

Understanding MedicalSpecialty Enumeration Values

The medicalSpecialty property accepts values from Schema.org's MedicalSpecialty enumeration. These aren't free-text fields -- they're specific, predefined values. Here are the ones you'll use most often:

Specialty Schema.org Value Notes
Cardiology Cardiovascular Covers cardiology and cardiovascular surgery
Pediatrics Pediatric General pediatrics
Urology Urologic Includes pediatric urology
Dermatology Dermatology Also DermatologyLaserSurgery variant
Orthopedics Musculoskeletal Orthopedic surgery falls here
Neurology Neurological Distinct from Psychiatric
Obstetrics & Gynecology Obstetric and/or Gynecologic Can specify both
Emergency Medicine Emergency ER physicians
Family Medicine PrimaryCare General practice, family medicine
Oncology Oncologic Cancer treatment
Pulmonology Pulmonary Lung and respiratory
Gastroenterology Gastroenterologic GI specialists
Endocrinology Endocrine Diabetes, thyroid, etc.
Ophthalmology Optometric Eye care
Psychiatry Psychiatric Mental health physicians

A full list is available at schema.org/MedicalSpecialty. Note that some specialties map to broader categories than you might expect -- there's no separate "Interventional Cardiology" value, for instance. You'd use Cardiovascular and describe the subspecialty in the physician's description or knowsAbout properties.

Medical Practice Schema Markup: JSON-LD for Every Physician Specialty - architecture

Base Physician JSON-LD Template

Here's a solid starting template for any physician page. I'll annotate the key decisions.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Physician",
  "@id": "https://example.com/doctors/jane-smith#physician",
  "name": "Dr. Jane Smith, MD, FACC",
  "image": "https://example.com/images/dr-jane-smith.jpg",
  "url": "https://example.com/doctors/jane-smith",
  "telephone": "+1-555-867-5309",
  "description": "Board-certified cardiologist specializing in interventional cardiology and structural heart disease with 15 years of clinical experience.",
  "medicalSpecialty": "https://schema.org/Cardiovascular",
  "isAcceptingNewPatients": true,
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "450 Heart Center Drive, Suite 200",
    "addressLocality": "Austin",
    "addressRegion": "TX",
    "postalCode": "78701",
    "addressCountry": "US"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": 30.2672,
    "longitude": -97.7431
  },
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
      "opens": "08:00",
      "closes": "17:00"
    }
  ],
  "sameAs": [
    "https://www.healthgrades.com/physician/dr-jane-smith",
    "https://www.doximity.com/pub/jane-smith-md",
    "https://www.linkedin.com/in/drjanesmith"
  ],
  "alumniOf": {
    "@type": "CollegeOrUniversity",
    "name": "Johns Hopkins University School of Medicine"
  },
  "hasCredential": [
    {
      "@type": "EducationalOccupationalCredential",
      "credentialCategory": "Board Certification",
      "name": "American Board of Internal Medicine - Cardiovascular Disease"
    }
  ],
  "hospitalAffiliation": {
    "@type": "Hospital",
    "name": "St. David's Medical Center",
    "url": "https://stdavids.com"
  },
  "worksFor": {
    "@type": "MedicalOrganization",
    "@id": "https://example.com/#organization",
    "name": "Austin Heart Specialists"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.8",
    "reviewCount": "142",
    "bestRating": "5"
  }
}
</script>

A few things I want to call out:

  • @id: This creates a unique identifier for this entity. Use the page URL plus a fragment. This becomes critical when you link physicians to organizations across pages.
  • medicalSpecialty: I'm using the full URL form (https://schema.org/Cardiovascular) rather than just the string. Both work, but the URL form is more explicit.
  • isAcceptingNewPatients: This is a newer property specific to Physician. Use it. Patients search for this constantly.
  • sameAs: Link to verified third-party profiles. This is how Google builds entity confidence and potentially triggers knowledge panels.
  • aggregateRating: Only include this if you display reviews visibly on the page. Google's guidelines are strict on this -- hidden ratings violate their policies.

Specialty-Specific JSON-LD Examples

Let's build out examples for the specialties people actually search for.

Cardiology (Cardiovascular)

{
  "@context": "https://schema.org",
  "@type": "Physician",
  "name": "Dr. Robert Chen, MD, FACC",
  "medicalSpecialty": "https://schema.org/Cardiovascular",
  "knowsAbout": [
    "Interventional Cardiology",
    "Cardiac Catheterization",
    "Coronary Artery Disease",
    "Heart Failure Management",
    "Echocardiography"
  ],
  "availableService": [
    {
      "@type": "MedicalProcedure",
      "name": "Cardiac Catheterization",
      "procedureType": "https://schema.org/PercutaneousProcedure"
    },
    {
      "@type": "MedicalTest",
      "name": "Stress Echocardiography"
    },
    {
      "@type": "MedicalTest",
      "name": "Electrocardiogram (ECG/EKG)"
    }
  ]
}

The availableService property is unique to Physician and lets you specify exact procedures and tests. For cardiology, this distinction between MedicalProcedure and MedicalTest maps naturally to the interventional vs. diagnostic split.

Pediatrics

{
  "@context": "https://schema.org",
  "@type": "Physician",
  "name": "Dr. Maria Gonzalez, MD, FAAP",
  "medicalSpecialty": "https://schema.org/Pediatric",
  "knowsAbout": [
    "Well-Child Visits",
    "Childhood Immunizations",
    "Developmental Screening",
    "Adolescent Medicine",
    "Newborn Care"
  ],
  "availableService": [
    {
      "@type": "MedicalTherapy",
      "name": "Pediatric Vaccination Program"
    },
    {
      "@type": "MedicalTest",
      "name": "Developmental Milestone Assessment"
    }
  ],
  "hasCredential": [
    {
      "@type": "EducationalOccupationalCredential",
      "credentialCategory": "Board Certification",
      "name": "American Board of Pediatrics"
    }
  ]
}

Pediatrics is interesting because the MedicalSpecialty value is just Pediatric -- there's no separate value for pediatric subspecialties like pediatric cardiology. For a pediatric cardiologist, you'd include both specialties:

"medicalSpecialty": [
  "https://schema.org/Pediatric",
  "https://schema.org/Cardiovascular"
]

Yes, medicalSpecialty accepts an array. Use it.

Urology

{
  "@context": "https://schema.org",
  "@type": "Physician",
  "name": "Dr. Michael Torres, MD, FACS",
  "medicalSpecialty": "https://schema.org/Urologic",
  "knowsAbout": [
    "Robotic-Assisted Surgery",
    "Prostate Cancer Treatment",
    "Kidney Stone Management",
    "Urinary Incontinence",
    "Male Infertility"
  ],
  "availableService": [
    {
      "@type": "MedicalProcedure",
      "name": "Robot-Assisted Laparoscopic Prostatectomy",
      "procedureType": "https://schema.org/SurgicalProcedure"
    },
    {
      "@type": "MedicalProcedure",
      "name": "Extracorporeal Shock Wave Lithotripsy (ESWL)",
      "procedureType": "https://schema.org/NoninvasiveProcedure"
    },
    {
      "@type": "MedicalTest",
      "name": "Cystoscopy"
    }
  ]
}

Orthopedics

{
  "@context": "https://schema.org",
  "@type": "Physician",
  "name": "Dr. Sarah Kim, MD",
  "medicalSpecialty": "https://schema.org/Musculoskeletal",
  "knowsAbout": [
    "Total Joint Replacement",
    "Sports Medicine",
    "Arthroscopic Surgery",
    "Fracture Care",
    "Rotator Cuff Repair"
  ],
  "availableService": [
    {
      "@type": "MedicalProcedure",
      "name": "Total Knee Arthroplasty",
      "procedureType": "https://schema.org/SurgicalProcedure"
    },
    {
      "@type": "MedicalProcedure",
      "name": "Arthroscopic ACL Reconstruction",
      "procedureType": "https://schema.org/SurgicalProcedure"
    }
  ]
}

Notice the medicalSpecialty value is Musculoskeletal, not "Orthopedic." This trips people up constantly.

MedicalOrganization and MedicalClinic Schema

Your organization-level schema is the foundation. Every physician entity should reference back to it. Here's a complete MedicalClinic example:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "MedicalClinic",
  "@id": "https://example.com/#clinic",
  "name": "Austin Heart Specialists",
  "url": "https://example.com",
  "logo": "https://example.com/images/logo.png",
  "image": "https://example.com/images/clinic-exterior.jpg",
  "telephone": "+1-555-867-5309",
  "medicalSpecialty": "https://schema.org/Cardiovascular",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "450 Heart Center Drive",
    "addressLocality": "Austin",
    "addressRegion": "TX",
    "postalCode": "78701",
    "addressCountry": "US"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": 30.2672,
    "longitude": -97.7431
  },
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
      "opens": "07:30",
      "closes": "17:30"
    }
  ],
  "availableService": [
    {
      "@type": "MedicalTest",
      "name": "Cardiac Stress Testing"
    },
    {
      "@type": "MedicalProcedure",
      "name": "Cardiac Catheterization"
    }
  ],
  "member": [
    {
      "@type": "Physician",
      "@id": "https://example.com/doctors/jane-smith#physician"
    },
    {
      "@type": "Physician",
      "@id": "https://example.com/doctors/robert-chen#physician"
    }
  ],
  "sameAs": [
    "https://www.facebook.com/austinheartspecialists",
    "https://www.google.com/maps/place/?q=place_id:ChIJ_____"
  ]
}
</script>

The member array uses @id references to link to physician entities defined on their individual pages. This creates a connected graph that Google can traverse.

Connecting Physicians to Organizations

This is where most implementations fall apart. You need bidirectional references:

  1. Organization → Physician: Use member on the organization page
  2. Physician → Organization: Use worksFor on the physician page

Both should reference the same @id. Think of it like a foreign key in a database. If the IDs don't match, Google can't connect them.

// On the physician page:
"worksFor": {
  "@type": "MedicalClinic",
  "@id": "https://example.com/#clinic",
  "name": "Austin Heart Specialists"
}

// On the organization page:
"member": [
  {
    "@type": "Physician",
    "@id": "https://example.com/doctors/jane-smith#physician"
  }
]

For multi-location practices, each location should be a separate MedicalClinic entity with its own @id, and physicians who practice at multiple locations should use practicesAt (an array) instead of worksFor.

If you're building these sites on headless CMS platforms, this kind of relational data modeling maps perfectly to content references in systems like Sanity or Contentful. We handle this exact pattern in our headless CMS development work -- the CMS content model mirrors the schema.org entity graph.

Properties That Build E-E-A-T

Google's quality raters evaluate Experience, Expertise, Authoritativeness, and Trustworthiness for YMYL (Your Money or Your Life) content. Healthcare is the quintessential YMYL category. These schema properties directly support E-E-A-T signals:

Property Type E-E-A-T Signal
hasCredential EducationalOccupationalCredential Expertise
alumniOf CollegeOrUniversity Expertise
memberOf Organization Authoritativeness
hospitalAffiliation Hospital Authoritativeness, Trust
award Text Authoritativeness
knowsAbout Text or Thing Expertise
medicalSpecialty MedicalSpecialty Expertise
sameAs URL Trust (cross-reference verification)
aggregateRating AggregateRating Trust (social proof)

Critical rule: you can only mark up content visible on the page. If you list board certifications in your schema but don't show them anywhere on the physician's profile page, you're violating Google's structured data guidelines. Add the content first, then mark it up.

Validation and Testing

Before you push anything to production:

  1. Google Rich Results Test (search.google.com/test/rich-results): Validates eligibility for rich results
  2. Schema Markup Validator (validator.schema.org): Checks syntax and conformance to Schema.org vocabulary
  3. Google Search Console: Monitor the "Enhancements" section after deployment for any errors

Here's my testing workflow:

# If you're generating schema programmatically (Next.js, Astro, etc.)
# Output the JSON to a file and validate locally first
node -e "const schema = require('./generate-schema.js'); console.log(JSON.stringify(schema, null, 2))" > schema-output.json

# Then paste into validator.schema.org or use their API

If you're building with Next.js or Astro, you can generate these JSON-LD blocks dynamically from your CMS data. Both frameworks handle <script type="application/ld+json"> injection cleanly -- Next.js via the <Script> component or next/head, Astro via its <head> slot.

Common Mistakes to Avoid

Using generic LocalBusiness instead of Physician. You lose all the medical-specific properties. A Physician is a LocalBusiness, so you get everything LocalBusiness offers plus more.

Free-text medicalSpecialty values. Writing "medicalSpecialty": "Cardiology" instead of using the proper enumeration value (Cardiovascular) means Google may not understand the specialty correctly.

Missing geo coordinates. Lat/long data helps Google associate your practice with location-based queries. Don't skip it.

Duplicate @id values across different entities. Every entity needs a unique @id. I've seen implementations where every physician shares the same @id suffix -- that completely breaks the entity graph.

Marking up content that isn't on the page. This bears repeating. Google's guidelines are explicit: everything in your structured data must correspond to visible content. Violations can result in manual actions.

Ignoring isAcceptingNewPatients. This is a high-intent signal. Patients filter by it. Include it and keep it updated.

FAQ

What's the difference between Physician and MedicalBusiness schema types?

Physician is a subtype of MedicalBusiness. Use Physician for individual provider pages and MedicalBusiness (or more specifically, MedicalClinic) for the practice itself. Physician inherits all properties from MedicalBusiness and adds physician-specific ones like hospitalAffiliation, availableService, and isAcceptingNewPatients.

Can I list multiple medical specialties for one physician?

Yes. The medicalSpecialty property accepts an array. A physician who is both a pediatric and cardiovascular specialist can list both: "medicalSpecialty": ["https://schema.org/Pediatric", "https://schema.org/Cardiovascular"]. This is common for dual-boarded physicians.

Does medical schema markup directly improve Google rankings?

Structured data is not a direct ranking factor. However, it enables rich results (star ratings, FAQ dropdowns, knowledge panels) that significantly improve click-through rates. It also feeds Google's entity understanding, which influences how your practice appears in AI Overviews and local pack results. The indirect SEO impact is substantial.

What schema type should I use for a hospital's physician directory?

Use Hospital as your top-level entity, MedicalClinic for individual departments (e.g., Department of Cardiology), and Physician for each provider page. Connect them with member, worksFor, and department properties. This creates a complete organizational graph.

Is JSON-LD the only format for medical schema markup?

No -- you can also use Microdata or RDFa embedded in your HTML. But Google explicitly recommends JSON-LD and it's the format they use in all their documentation examples. JSON-LD is also far easier to maintain because it's separate from your page layout. You can generate it programmatically from CMS data without touching templates.

How do I handle a physician who practices at multiple locations?

Use the practicesAt property with an array of MedicalClinic or Hospital entities, each with its own @id. Don't duplicate the physician entity across locations -- create one canonical Physician entity and link it to multiple practice locations. Each location's schema should also reference the physician via member.

Should I include patient reviews in my physician schema?

Only if the reviews are visibly displayed on the physician's page. Use the aggregateRating property on the Physician type and include ratingValue, reviewCount, and bestRating. Google takes this seriously -- marking up hidden or fake reviews can result in manual penalties. If you collect reviews through third-party platforms like Healthgrades, link to them via sameAs instead.

How often should I update medical practice schema markup?

Update your structured data whenever the underlying information changes: new office hours, a physician leaves or joins, a provider stops accepting new patients, or contact information changes. For practices using a headless CMS with dynamic schema generation, this happens automatically when content editors update the CMS. If you're hardcoding JSON-LD, set a quarterly review cadence at minimum. Stale structured data -- especially incorrect isAcceptingNewPatients or telephone values -- erodes trust with both Google and patients.