Schema markup for vacation rentals: a technical SEO guide few managers implement
There's an SEO strategy that can increase your Google CTR by 20% to 40%, that the vast majority of vacation rental managers don't use, and that Google explicitly rewards. It's called schema markup (structured data), and this article explains exactly what it is, why it matters, and how to implement it with real code you can copy and adapt.
If you manage vacation rentals and have your own website, this is probably the most technical and most profitable article you'll read today.
What is Schema.org and why Google loves it
Schema.org is a standardized vocabulary jointly created by Google, Bing, Yahoo, and Yandex so that website owners can describe their page content in a way that search engines understand without ambiguity.
When you publish a page about a vacation apartment, Google sees text, images, and links. It can infer that it's an accommodation, but it isn't sure. With schema markup, you explicitly tell it: "this is a VacationRental, it has 2 bedrooms, costs EUR 120/night, is located in Calella de Palafrugell, and has an average rating of 4.7 out of 5 based on 43 reviews."
Google uses this information to:
- Display rich snippets in search results: star ratings, price ranges, location.
- Better understand your content to rank it for relevant searches.
- Feed Google Travel and Google Maps with your property data.
- Answer direct questions in search results (if you have FAQPage schema).
The visible result for you: your Google results are larger, more eye-catching, and more clickable than those of your competitors who don't use schema.
Relevant Schema types for vacation rentals
LodgingBusiness: your business
This schema describes your vacation rental management company as an entity. It's the top-level schema that identifies your business.
{
"@context": "https://schema.org",
"@type": "LodgingBusiness",
"name": "Costa Brava Vacation Homes",
"description": "Professional vacation rental management on the Costa Brava. Charming apartments and houses on the beachfront.",
"url": "https://www.yourdomain.com",
"telephone": "+34 972 123 456",
"email": "info@yourdomain.com",
"address": {
"@type": "PostalAddress",
"streetAddress": "Carrer del Mar, 15",
"addressLocality": "Calella de Palafrugell",
"addressRegion": "Girona",
"postalCode": "17210",
"addressCountry": "ES"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 41.8892,
"longitude": 3.1634
},
"image": "https://www.yourdomain.com/images/logo.jpg",
"priceRange": "EUR 80 - EUR 350 per night",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "156",
"bestRating": "5"
},
"sameAs": [
"https://www.instagram.com/yourinstagram",
"https://www.facebook.com/yourfacebook"
]
}VacationRental / Accommodation: each property
This is the most important schema for your individual property pages. Each apartment or house should have its own complete schema.
{
"@context": "https://schema.org",
"@type": "VacationRental",
"name": "Apartamento Mar Azul - Beachfront",
"description": "Renovated 2-bedroom apartment with direct sea views in Calella de Palafrugell. Private terrace, 50 meters from the beach.",
"url": "https://www.yourdomain.com/properties/apartamento-mar-azul",
"image": [
"https://www.yourdomain.com/images/mar-azul-living.jpg",
"https://www.yourdomain.com/images/mar-azul-terrace.jpg",
"https://www.yourdomain.com/images/mar-azul-bedroom.jpg",
"https://www.yourdomain.com/images/mar-azul-views.jpg"
],
"numberOfRooms": 2,
"numberOfBedrooms": 2,
"numberOfBathroomsTotal": 1,
"occupancy": {
"@type": "QuantitativeValue",
"maxValue": 4,
"unitText": "guests"
},
"floorSize": {
"@type": "QuantitativeValue",
"value": 75,
"unitCode": "MTK"
},
"petsAllowed": false,
"amenityFeature": [
{"@type": "LocationFeatureSpecification", "name": "WiFi", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Air conditioning", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Terrace", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Sea view", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Washing machine", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Dishwasher", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Parking", "value": true}
],
"address": {
"@type": "PostalAddress",
"addressLocality": "Calella de Palafrugell",
"addressRegion": "Girona",
"postalCode": "17210",
"addressCountry": "ES"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 41.8890,
"longitude": 3.1640
},
"checkinTime": "16:00",
"checkoutTime": "10:00",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "43",
"bestRating": "5"
},
"offers": {
"@type": "AggregateOffer",
"priceCurrency": "EUR",
"lowPrice": "90",
"highPrice": "220",
"offerCount": "365"
},
"containedInPlace": {
"@type": "LodgingBusiness",
"name": "Costa Brava Vacation Homes",
"url": "https://www.yourdomain.com"
}
}Review and AggregateRating: your ratings
Reviews are the schema that produces the most visually impactful result on Google: the golden stars. In addition to the aggregateRating we already included above, you can mark up individual reviews:
{
"@context": "https://schema.org",
"@type": "VacationRental",
"name": "Apartamento Mar Azul",
"url": "https://www.yourdomain.com/properties/apartamento-mar-azul",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "43",
"bestRating": "5"
},
"review": [
{
"@type": "Review",
"author": {
"@type": "Person",
"name": "María García"
},
"datePublished": "2026-02-15",
"reviewBody": "Impeccable apartment with incredible views. The terrace is perfect for breakfast while watching the sea. We'll definitely be back.",
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5"
}
},
{
"@type": "Review",
"author": {
"@type": "Person",
"name": "Jean Dupont"
},
"datePublished": "2026-01-20",
"reviewBody": "Très bel appartement, bien situé. La plage est à deux pas. Propriétaire très réactif.",
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5"
}
}
]
}Important note about reviews in schema: Google has strict guidelines. Reviews must be from real guests who stayed at your property. You cannot fabricate reviews or copy reviews from OTAs without having your own collection system. If Google detects fake reviews in schema, it can penalize you.
FAQPage: frequently asked questions
The FAQPage schema lets you appear on Google with a dropdown of questions and answers directly in the search results. It's an excellent way to take up more visual space on the SERP.
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What are the check-in and check-out times?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Check-in is from 4:00 PM and check-out is before 10:00 AM. We offer flexible check-in at no extra cost for direct bookings, subject to availability."
}
},
{
"@type": "Question",
"name": "Are pets allowed?",
"acceptedAnswer": {
"@type": "Answer",
"text": "It depends on the property. Some of our homes allow pets with a supplement of EUR 15/night. Check each property listing to see if pets are accepted."
}
},
{
"@type": "Question",
"name": "What is the cancellation policy?",
"acceptedAnswer": {
"@type": "Answer",
"text": "For direct bookings, we offer free cancellation up to 14 days before check-in. Between 14 and 7 days prior, 50% of the deposit is retained. Less than 7 days, no refund. High-season bookings (July-August) have a special policy."
}
},
{
"@type": "Question",
"name": "How do I get to the properties?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Our properties are in Calella de Palafrugell (Costa Brava). The nearest airport is Barcelona-El Prat (1h 45min). You can also arrive from Girona-Costa Brava airport (45min). We provide detailed directions and GPS coordinates upon booking confirmation."
}
}
]
}BreadcrumbList: breadcrumbs
Breadcrumbs help Google understand your website structure and are displayed in search results. Instead of showing the full URL, Google shows a navigable path.
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://www.yourdomain.com"
},
{
"@type": "ListItem",
"position": 2,
"name": "Properties",
"item": "https://www.yourdomain.com/properties"
},
{
"@type": "ListItem",
"position": 3,
"name": "Calella de Palafrugell",
"item": "https://www.yourdomain.com/properties/calella-de-palafrugell"
},
{
"@type": "ListItem",
"position": 4,
"name": "Apartamento Mar Azul",
"item": "https://www.yourdomain.com/properties/apartamento-mar-azul"
}
]
}Step-by-step implementation with JSON-LD
JSON-LD (JavaScript Object Notation for Linked Data) is the format Google recommends for implementing schema markup. It's inserted as a <script> block in your page's HTML without affecting the visible content.
Step 1: Identify which pages need schema
- Homepage: LodgingBusiness + BreadcrumbList.
- Listing pages (by area/type): BreadcrumbList.
- Individual property pages: VacationRental + AggregateRating + Review + BreadcrumbList + FAQPage.
- Contact page: LodgingBusiness (simplified) + BreadcrumbList.
- Blog posts: Article + BreadcrumbList.
Step 2: Insert the JSON-LD in the HTML
The JSON-LD block is placed inside the <head> of the page (or at the end of the <body> — both work, but <head> is the recommended convention):
<head>
<title>Apartamento Mar Azul - Costa Brava Vacation Homes</title>
<!-- ... other meta tags ... -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "VacationRental",
"name": "Apartamento Mar Azul - Beachfront",
...
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
...
}
</script>
</head>You can have multiple <script type="application/ld+json"> blocks on the same page. Each with a different schema type.
Step 3: Validate your implementation
Before publishing, validate your schema with these tools:
- Google Rich Results Test (https://search.google.com/test/rich-results): tells you if your schema is eligible for rich results and detects errors.
- Schema Markup Validator (https://validator.schema.org/): more comprehensive validation against the Schema.org specification.
Both are free. Paste your page URL or the JSON-LD code directly.
Complete code example for a property page
Here's a complete example with all the relevant schemas for an individual property page. You can copy this and adapt it:
<head>
<title>Apartamento Mar Azul | 2 bedrooms, sea views | Calella de Palafrugell</title>
<meta name="description" content="Renovated apartment with private terrace and direct Mediterranean views. 2 bedrooms, 4 guests. From EUR 90/night. Direct booking with no commissions.">
<!-- Schema: VacationRental with Reviews -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "VacationRental",
"name": "Apartamento Mar Azul - Beachfront",
"description": "Renovated 2-bedroom apartment with direct sea views in Calella de Palafrugell. Private terrace, WiFi, air conditioning, parking included.",
"url": "https://www.yourdomain.com/properties/apartamento-mar-azul",
"image": [
"https://www.yourdomain.com/images/mar-azul-living.jpg",
"https://www.yourdomain.com/images/mar-azul-terrace.jpg",
"https://www.yourdomain.com/images/mar-azul-bedroom.jpg"
],
"numberOfRooms": 3,
"numberOfBedrooms": 2,
"numberOfBathroomsTotal": 1,
"occupancy": {
"@type": "QuantitativeValue",
"maxValue": 4
},
"floorSize": {
"@type": "QuantitativeValue",
"value": 75,
"unitCode": "MTK"
},
"petsAllowed": false,
"amenityFeature": [
{"@type": "LocationFeatureSpecification", "name": "WiFi", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Air conditioning", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Sea view terrace", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Free parking", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Washing machine", "value": true}
],
"address": {
"@type": "PostalAddress",
"addressLocality": "Calella de Palafrugell",
"addressRegion": "Girona",
"postalCode": "17210",
"addressCountry": "ES"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 41.8890,
"longitude": 3.1640
},
"checkinTime": "16:00",
"checkoutTime": "10:00",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "43",
"bestRating": "5"
},
"review": [
{
"@type": "Review",
"author": {"@type": "Person", "name": "María G."},
"datePublished": "2026-02-15",
"reviewBody": "Impeccable apartment with incredible views. The terrace is perfect for breakfast while watching the sea.",
"reviewRating": {"@type": "Rating", "ratingValue": "5", "bestRating": "5"}
},
{
"@type": "Review",
"author": {"@type": "Person", "name": "Thomas K."},
"datePublished": "2026-01-28",
"reviewBody": "Great location, very clean apartment. The host was very responsive and helpful.",
"reviewRating": {"@type": "Rating", "ratingValue": "5", "bestRating": "5"}
},
{
"@type": "Review",
"author": {"@type": "Person", "name": "Laura P."},
"datePublished": "2025-12-10",
"reviewBody": "Great location, 50 meters from the beach. The apartment has everything you need. We'll be back.",
"reviewRating": {"@type": "Rating", "ratingValue": "4", "bestRating": "5"}
}
],
"offers": {
"@type": "AggregateOffer",
"priceCurrency": "EUR",
"lowPrice": "90",
"highPrice": "220"
},
"containedInPlace": {
"@type": "LodgingBusiness",
"name": "Costa Brava Vacation Homes",
"url": "https://www.yourdomain.com"
}
}
</script>
<!-- Schema: BreadcrumbList -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://www.yourdomain.com"},
{"@type": "ListItem", "position": 2, "name": "Costa Brava", "item": "https://www.yourdomain.com/properties/costa-brava"},
{"@type": "ListItem", "position": 3, "name": "Apartamento Mar Azul", "item": "https://www.yourdomain.com/properties/apartamento-mar-azul"}
]
}
</script>
<!-- Schema: FAQPage -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How far is the beach from Apartamento Mar Azul?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The apartment is 50 meters from Calella de Palafrugell beach, with direct walking access in less than 1 minute."
}
},
{
"@type": "Question",
"name": "Does Apartamento Mar Azul have parking?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes, the apartment includes a private parking spot at no additional cost."
}
},
{
"@type": "Question",
"name": "How much does Apartamento Mar Azul cost per night?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The price varies by season, from EUR 90/night in low season to EUR 220/night in August. Booking directly on our website guarantees you the best price and exclusive extras."
}
}
]
}
</script>
</head>Rich snippets you can achieve
With schema implemented correctly, these are the rich results Google may display:
Star ratings
The golden stars next to your Google result. These are triggered by the AggregateRating schema. It's the rich snippet with the greatest impact on CTR: according to a Search Engine Land study, results with stars have a CTR 25% to 35% higher than those without.
Price range
Google can display "From EUR 90/night" next to your result. This is triggered by the AggregateOffer schema. Highly relevant because it automatically filters users by budget, attracting only clicks from people willing to pay your price.
Expandable FAQs
The expandable questions and answers below your result take up enormous visual space on the SERP, pushing competitors further down. These are triggered by the FAQPage schema.
Navigable breadcrumbs
Instead of showing your page's long URL, Google displays a navigable path like "Home > Costa Brava > Apartamento Mar Azul." Triggered by BreadcrumbList.
Real impact on CTR
Industry data confirms the impact of structured data on SEO performance:
- Rich results have on average 58% higher CTR than standard results (according to data from Google and Ahrefs).
- Star ratings increase CTR by 20% to 35% according to the Search Engine Land study cited above.
- Expandable FAQs can double the visual space of your result, which translates to more visibility and more clicks.
- Pages with well-implemented schema have a higher probability of appearing in featured positions (featured snippets, knowledge panels).
For a vacation rental manager with 10 properties receiving 500 monthly visits from Google, a 25% CTR increase means 125 additional visits per month. With a 2% conversion rate, that's 2.5 extra bookings per month just from implementing schema.
Common schema mistakes for vacation rentals
Mistake 1: Fake or imported reviews
Google detects and penalizes fabricated or OTA-copied reviews. If you don't have your own review collection system, don't include Review schema. Use only AggregateRating with real, verifiable data.
Mistake 2: Outdated prices
If your schema says "from EUR 90" but the actual minimum price on your website is EUR 120, Google may consider this misleading content. Keep your schema data synchronized with your website.
Mistake 3: Same schema on all pages without adaptation
Each page should have its own adapted schema. Don't put the same VacationRental schema on every page of your website. Each property has its own schema with its specific data.
Mistake 4: Not validating after changes
Every time you modify your website or schema, validate again. An HTML structure change can break your schema without you noticing.
Mistake 5: Forgetting schema on new pages
When you add a new property to your website, make sure to generate and add its corresponding schema. It's easy to forget and lose the rich results benefits for that property.
Automation: generating dynamic schema from your PMS
If you have 20+ properties, manually generating and maintaining schema for each one is unfeasible. The solution is to generate it dynamically from your PMS or database data.
The flow is:
- Your PMS/database holds the information for each property: name, description, photos, amenities, prices, location, reviews.
- Your website automatically generates the JSON-LD from this data when rendering each page.
- When you update data in your PMS (new prices, new reviews), the schema updates automatically on the next render.
Next.js: how to implement it with generateMetadata
If your website is built with Next.js (App Router), the implementation is especially clean using generateMetadata and a schema component:
// app/properties/[slug]/page.tsx
import { Metadata } from 'next';
import { getProperty } from '@/lib/properties';
// Generates dynamic metadata including Open Graph
export async function generateMetadata({ params }): Promise<Metadata> {
const property = await getProperty(params.slug);
return {
title: `${property.name} | Your Brand`,
description: property.shortDescription,
openGraph: {
title: property.name,
description: property.shortDescription,
images: property.images.map(img => ({
url: img.url,
width: 1200,
height: 630,
alt: img.alt,
})),
},
};
}
// Schema JSON-LD Component
function PropertySchema({ property }) {
const schema = {
"@context": "https://schema.org",
"@type": "VacationRental",
"name": property.name,
"description": property.description,
"url": `https://www.yourdomain.com/properties/${property.slug}`,
"image": property.images.map(img => img.url),
"numberOfBedrooms": property.bedrooms,
"numberOfBathroomsTotal": property.bathrooms,
"occupancy": {
"@type": "QuantitativeValue",
"maxValue": property.maxGuests,
},
"floorSize": {
"@type": "QuantitativeValue",
"value": property.squareMeters,
"unitCode": "MTK",
},
"petsAllowed": property.petsAllowed,
"amenityFeature": property.amenities.map(amenity => ({
"@type": "LocationFeatureSpecification",
"name": amenity,
"value": true,
})),
"address": {
"@type": "PostalAddress",
"addressLocality": property.city,
"addressRegion": property.region,
"postalCode": property.postalCode,
"addressCountry": "ES",
},
"geo": {
"@type": "GeoCoordinates",
"latitude": property.latitude,
"longitude": property.longitude,
},
"checkinTime": property.checkinTime,
"checkoutTime": property.checkoutTime,
"aggregateRating": property.reviewCount > 0 ? {
"@type": "AggregateRating",
"ratingValue": property.averageRating.toString(),
"reviewCount": property.reviewCount.toString(),
"bestRating": "5",
} : undefined,
"offers": {
"@type": "AggregateOffer",
"priceCurrency": "EUR",
"lowPrice": property.minPrice.toString(),
"highPrice": property.maxPrice.toString(),
},
};
// Remove undefined fields
const cleanSchema = JSON.parse(JSON.stringify(schema));
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(cleanSchema) }}
/>
);
}
// FAQ Schema Component
function FAQSchema({ faqs }) {
const schema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqs.map(faq => ({
"@type": "Question",
"name": faq.question,
"acceptedAnswer": {
"@type": "Answer",
"text": faq.answer,
},
})),
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Breadcrumb Schema Component
function BreadcrumbSchema({ items }) {
const schema = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": items.map((item, index) => ({
"@type": "ListItem",
"position": index + 1,
"name": item.name,
"item": item.url,
})),
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Property page
export default async function PropertyPage({ params }) {
const property = await getProperty(params.slug);
const breadcrumbs = [
{ name: "Home", url: "https://www.yourdomain.com" },
{ name: property.region, url: `https://www.yourdomain.com/properties/${property.regionSlug}` },
{ name: property.name, url: `https://www.yourdomain.com/properties/${property.slug}` },
];
return (
<>
<PropertySchema property={property} />
<BreadcrumbSchema items={breadcrumbs} />
{property.faqs && <FAQSchema faqs={property.faqs} />}
{/* Rest of the page content */}
<h1>{property.name}</h1>
{/* ... */}
</>
);
}This approach has several advantages:
- Schema is generated automatically from each property's data.
- It updates itself when you change data in your backend.
- It's type-safe if you use TypeScript.
- You don't need to manually maintain JSON-LD for each property.
Conclusion
Schema markup is one of the SEO tactics with the best return on investment for vacation rentals. It requires an initial implementation effort, but once configured (especially if you automate it with Next.js or your preferred framework), it runs without maintenance and continuously improves your Google visibility.
Few vacation rental managers implement it correctly, which means you have a real competitive advantage if you do. Start with your main property pages (VacationRental + AggregateRating + FAQPage), validate with Google's tools, and measure the CTR impact through Google Search Console over the next 3 months.
Structured data isn't magic. It's the way to speak the language Google understands. And when Google understands you, it ranks you better.