Structured data tells search engines exactly what your page content is. For video pages, the VideoObject schema type enables rich search results with video thumbnails, duration, and publication dates. Here's how I implemented it for every video page on DailyWatch.
What VideoObject Structured Data Looks Like
Google expects JSON-LD format in a <script> tag:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "VideoObject",
"name": "Video Title Here",
"description": "A description of the video content...",
"thumbnailUrl": "https://i.ytimg.com/vi/VIDEO_ID/mqdefault.jpg",
"uploadDate": "2026-02-15T10:30:00Z",
"duration": "PT4M32S",
"embedUrl": "https://www.youtube.com/embed/VIDEO_ID",
"interactionStatistic": {
"@type": "InteractionCounter",
"interactionType": "https://schema.org/WatchAction",
"userInteractionCount": 1500000
}
}
</script>
PHP Implementation
class VideoStructuredData {
public function __construct(
private readonly string $siteUrl,
) {}
public function generate(array $video): string {
$data = [
'@context' => 'https://schema.org',
'@type' => 'VideoObject',
'name' => $video['title'],
'description' => $this->getDescription($video),
'thumbnailUrl' => $this->getThumbnail($video),
'uploadDate' => $this->formatDate($video['published_at'] ?? ''),
'embedUrl' => 'https://www.youtube.com/embed/' . $video['video_id'],
'contentUrl' => $this->siteUrl . '/watch/' . $video['video_id'],
];
// Duration in ISO 8601 format
if (!empty($video['duration'])) {
$data['duration'] = $video['duration']; // Already ISO 8601 from YouTube
}
// View count
if (!empty($video['view_count'])) {
$data['interactionStatistic'] = [
'@type' => 'InteractionCounter',
'interactionType' => 'https://schema.org/WatchAction',
'userInteractionCount' => (int)$video['view_count'],
];
}
// Channel as publisher
if (!empty($video['channel_title'])) {
$data['author'] = [
'@type' => 'Person',
'name' => $video['channel_title'],
];
}
return '<script type="application/ld+json">' . "\n"
. json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
. "\n" . '</script>';
}
private function getDescription(array $video): string {
$desc = $video['description'] ?? '';
if (empty($desc)) {
$desc = "Watch {$video['title']} on DailyWatch.";
}
return mb_substr($desc, 0, 300);
}
private function getThumbnail(array $video): string {
$thumb = $video['thumbnail_url'] ?? '';
if (empty($thumb) && !empty($video['video_id'])) {
return 'https://i.ytimg.com/vi/' . $video['video_id'] . '/mqdefault.jpg';
}
return $thumb;
}
private function formatDate(string $date): string {
if (empty($date)) return date('c');
try {
return (new \DateTimeImmutable($date))->format('c');
} catch (\Exception) {
return date('c');
}
}
}
Usage in Templates
// In your watch page template
$structuredData = new VideoStructuredData(siteUrl: 'https://dailywatch.video');
?>
<head>
<title><?= htmlspecialchars($video['title']) ?> | DailyWatch</title>
<?= $structuredData->generate($video) ?>
</head>
Adding WebSite Schema to Home Page
function generateWebsiteSchema(): string {
$data = [
'@context' => 'https://schema.org',
'@type' => 'WebSite',
'name' => 'DailyWatch',
'url' => 'https://dailywatch.video',
'description' => 'Your daily dose of trending videos from around the world.',
'potentialAction' => [
'@type' => 'SearchAction',
'target' => 'https://dailywatch.video/search?q={search_term_string}',
'query-input' => 'required name=search_term_string',
],
];
return '<script type="application/ld+json">' . "\n"
. json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
. "\n" . '</script>';
}
BreadcrumbList for Navigation
function generateBreadcrumbs(array $items): string {
$list = [
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => [],
];
foreach ($items as $position => $item) {
$list['itemListElement'][] = [
'@type' => 'ListItem',
'position' => $position + 1,
'name' => $item['name'],
'item' => $item['url'],
];
}
return '<script type="application/ld+json">' . "\n"
. json_encode($list, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
. "\n" . '</script>';
}
// Usage on a watch page:
echo generateBreadcrumbs([
['name' => 'Home', 'url' => 'https://dailywatch.video'],
['name' => 'Music', 'url' => 'https://dailywatch.video/category/music'],
['name' => $video['title'], 'url' => 'https://dailywatch.video/watch/' . $video['video_id']],
]);
Validating Your Structured Data
Always validate using Google's Rich Results Test: https://search.google.com/test/rich-results
After implementing structured data on dailywatch.video, Google Search Console began showing rich result impressions within 2 weeks. Video pages now display with thumbnails in search results, significantly improving click-through rates.
Top comments (0)