# Schema.org ImageObject Structured Data - Comprehensive Analysis & Implementation Guide
## Executive Summary
This document provides comprehensive technical specifications and implementation guidance for Schema.org ImageObject structured data within WordPress SEO environments. The analysis covers core properties, best practices, WordPress integration patterns, and practical code examples for implementing a robust image SEO system.
## 1. Core ImageObject Properties
### 1.1 Required Properties
```json
{
"@context": "https://schema.org",
"@type": "ImageObject",
"contentUrl": "https://example.com/image.jpg"
}
```
**contentUrl** (URL): The actual URL of the image file. This is the only required property for a valid ImageObject.
### 1.2 Essential Recommended Properties
```json
{
"@context": "https://schema.org",
"@type": "ImageObject",
"contentUrl": "https://example.com/image.jpg",
"name": "Descriptive image title",
"description": "Detailed description of the image content",
"width": 1200,
"height": 800,
"encodingFormat": "image/jpeg",
"uploadDate": "2024-01-15T10:30:00+00:00",
"author": {
"@type": "Person",
"name": "Photographer Name"
},
"copyrightHolder": {
"@type": "Organization",
"name": "Copyright Owner"
}
}
```
### 1.3 Complete Property Specifications
#### Basic Image Properties
- **contentUrl** (URL): Direct link to the image file
- **name** (Text): Human-readable title/caption
- **description** (Text): Detailed description of image content
- **alternateName** (Text): Alternative name for the image
- **caption** (Text): Caption text displayed with the image
- **text** (Text): Text content embedded in or extracted from the image
#### Technical Properties
- **width** (Integer): Image width in pixels
- **height** (Integer): Image height in pixels
- **encodingFormat** (Text): MIME type (e.g., "image/jpeg", "image/png")
- **fileFormat** (Text): File format (e.g., "JPEG", "PNG", "WebP")
- **contentSize** (Text): File size (e.g., "2.5 MB")
- **bitrate** (Text): Bitrate for animated images
#### Publishing & Dates
- **uploadDate** (DateTime): When image was uploaded
- **datePublished** (DateTime): When image was first published
- **dateModified** (DateTime): When image was last modified
- **dateCreated** (DateTime): When image was originally created/taken
#### People & Organizations
- **author** (Person/Organization): Image creator/photographer
- **creator** (Person/Organization): Same as author
- **copyrightHolder** (Person/Organization): Copyright owner
- **photographer** (Person): Photographer (specific role)
- **contributor** (Person/Organization): Additional contributors
#### Copyright & Licensing
- **license** (URL/CreativeWork): License under which image is published
- **copyrightYear** (Number): Year of copyright
- **copyrightNotice** (Text): Copyright notice text
- **usageInfo** (URL/CreativeWork): Usage information/terms
- **acquireLicensePage** (URL): Where to acquire license
#### Location & Context
- **contentLocation** (Place): Where image content was captured
- **locationCreated** (Place): Where image was created
- **about** (Thing): Subject matter of the image
- **depicts** (Thing): Things depicted in the image
- **keywords** (Text): Comma-separated keywords
#### Technical Metadata
- **exifData** (PropertyValue[]): EXIF metadata as structured data
- **representativeOfPage** (Boolean): Whether image represents the page
- **thumbnail** (ImageObject): Thumbnail version of the image
- **embeddedTextCaption** (Text): Text caption embedded in image file
#### Accessibility
- **accessibilityFeature** (Text): Accessibility features present
- **accessibilityHazard** (Text): Potential accessibility hazards
- **accessibilityAPI** (Text): Accessibility APIs supported
- **accessibilityControl** (Text): Accessibility controls available
## 2. Best Practices for Image SEO
### 2.1 Search Engine Optimization
#### Google Image Search Optimization
- Use descriptive, keyword-rich names and descriptions
- Include relevant technical metadata (dimensions, format)
- Provide context through about/depicts properties
- Use structured captions and alt text
#### Core Web Vitals Impact
- Include width/height for CLS prevention
- Use appropriate encodingFormat for performance
- Implement responsive image markup with structured data
### 2.2 Content Quality Guidelines
#### Descriptive Metadata
```json
{
"name": "Red brick Victorian house with white trim and garden",
"description": "A well-maintained Victorian-era house featuring red brick construction, white decorative trim, bay windows, and a front garden with seasonal flowers",
"keywords": "Victorian house, red brick, architecture, residential, garden, historic home",
"about": {
"@type": "ArchitecturalStructure",
"name": "Victorian House",
"architecturalStyle": "Victorian"
}
}
```
#### Technical Accuracy
- Always include accurate width/height dimensions
- Use correct MIME types for encodingFormat
- Provide realistic file sizes in contentSize
- Include upload/creation dates when available
### 2.3 Copyright & Legal Compliance
#### License Information
```json
{
"license": "https://creativecommons.org/licenses/by/4.0/",
"copyrightHolder": {
"@type": "Person",
"name": "John Photographer",
"sameAs": "https://johnsportfolio.com"
},
"copyrightYear": 2024,
"usageInfo": "https://example.com/image-usage-terms",
"acquireLicensePage": "https://example.com/license-purchase"
}
```
## 3. WordPress Implementation Patterns
### 3.1 Hook Integration Strategy
```php
/**
* WordPress hooks for ImageObject implementation
*/
class TigerStyleSEO_ImageObject {
public function __construct() {
// Frontend hooks
add_action('wp_head', array($this, 'inject_image_structured_data'), 5);
// Admin hooks for image metadata
add_filter('attachment_fields_to_edit', array($this, 'add_image_schema_fields'), 10, 2);
add_filter('attachment_fields_to_save', array($this, 'save_image_schema_fields'), 10, 2);
// AJAX for dynamic image detection
add_action('wp_ajax_detect_page_images', array($this, 'ajax_detect_page_images'));
add_action('wp_ajax_nopriv_detect_page_images', array($this, 'ajax_detect_page_images'));
}
}
```
### 3.2 Media Library Integration
```php
/**
* Add Schema.org metadata fields to WordPress media library
*/
public function add_image_schema_fields($form_fields, $post) {
// Only for images
if (!wp_attachment_is_image($post->ID)) {
return $form_fields;
}
$schema_fields = array(
'schema_name' => array(
'label' => __('Schema Name', 'tigerstyle-heat'),
'input' => 'text',
'value' => get_post_meta($post->ID, '_schema_name', true),
'helps' => __('Descriptive name for search engines', 'tigerstyle-heat')
),
'schema_description' => array(
'label' => __('Schema Description', 'tigerstyle-heat'),
'input' => 'textarea',
'value' => get_post_meta($post->ID, '_schema_description', true),
'helps' => __('Detailed description of image content', 'tigerstyle-heat')
),
'schema_keywords' => array(
'label' => __('Schema Keywords', 'tigerstyle-heat'),
'input' => 'text',
'value' => get_post_meta($post->ID, '_schema_keywords', true),
'helps' => __('Comma-separated keywords', 'tigerstyle-heat')
),
'schema_copyright_holder' => array(
'label' => __('Copyright Holder', 'tigerstyle-heat'),
'input' => 'text',
'value' => get_post_meta($post->ID, '_schema_copyright_holder', true)
),
'schema_license_url' => array(
'label' => __('License URL', 'tigerstyle-heat'),
'input' => 'text',
'value' => get_post_meta($post->ID, '_schema_license_url', true),
'helps' => __('URL to license information', 'tigerstyle-heat')
),
'schema_representative_of_page' => array(
'label' => __('Representative of Page', 'tigerstyle-heat'),
'input' => 'html',
'html' => sprintf(
'',
$post->ID,
checked(get_post_meta($post->ID, '_schema_representative_of_page', true), '1', false)
)
)
);
return array_merge($form_fields, $schema_fields);
}
```
### 3.3 EXIF Data Integration
```php
/**
* Extract and structure EXIF data for Schema.org
*/
public function get_exif_structured_data($attachment_id) {
$exif_data = wp_get_attachment_metadata($attachment_id);
if (empty($exif_data['image_meta'])) {
return array();
}
$image_meta = $exif_data['image_meta'];
$structured_exif = array();
// Camera information
if (!empty($image_meta['camera'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'camera',
'value' => $image_meta['camera']
);
}
// Focal length
if (!empty($image_meta['focal_length'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'focalLength',
'value' => $image_meta['focal_length'] . 'mm'
);
}
// Aperture
if (!empty($image_meta['aperture'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'aperture',
'value' => 'f/' . $image_meta['aperture']
);
}
// ISO
if (!empty($image_meta['iso'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'iso',
'value' => $image_meta['iso']
);
}
// Shutter speed
if (!empty($image_meta['shutter_speed'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'shutterSpeed',
'value' => $image_meta['shutter_speed'] . 's'
);
}
// Creation timestamp
if (!empty($image_meta['created_timestamp'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'dateTimeOriginal',
'value' => date('c', $image_meta['created_timestamp'])
);
}
// GPS coordinates if available
if (!empty($image_meta['latitude']) && !empty($image_meta['longitude'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'gpsCoordinates',
'value' => array(
'@type' => 'GeoCoordinates',
'latitude' => $image_meta['latitude'],
'longitude' => $image_meta['longitude']
)
);
}
return $structured_exif;
}
```
### 3.4 Automatic Image Detection
```php
/**
* Detect and analyze images in post content
*/
public function detect_content_images($post_id) {
$post = get_post($post_id);
if (!$post) {
return array();
}
$images = array();
$content = $post->post_content;
// Find all img tags
preg_match_all('/]+>/i', $content, $img_tags);
foreach ($img_tags[0] as $img_tag) {
$image_data = $this->parse_img_tag($img_tag);
if ($image_data) {
$images[] = $image_data;
}
}
// Find WordPress gallery shortcodes
preg_match_all('/\[gallery[^\]]*\]/', $content, $gallery_shortcodes);
foreach ($gallery_shortcodes[0] as $shortcode) {
$gallery_images = $this->parse_gallery_shortcode($shortcode);
$images = array_merge($images, $gallery_images);
}
// Find featured image
$featured_image_id = get_post_thumbnail_id($post_id);
if ($featured_image_id) {
$featured_data = $this->get_attachment_image_data($featured_image_id);
if ($featured_data) {
$featured_data['representativeOfPage'] = true;
array_unshift($images, $featured_data);
}
}
return $images;
}
private function parse_img_tag($img_tag) {
// Extract src, alt, width, height, class from img tag
$attributes = array();
if (preg_match('/src=["\']([^"\']+)["\']/', $img_tag, $matches)) {
$attributes['src'] = $matches[1];
}
if (preg_match('/alt=["\']([^"\']*)["\']/', $img_tag, $matches)) {
$attributes['alt'] = $matches[1];
}
if (preg_match('/width=["\']?(\d+)["\']?/', $img_tag, $matches)) {
$attributes['width'] = intval($matches[1]);
}
if (preg_match('/height=["\']?(\d+)["\']?/', $img_tag, $matches)) {
$attributes['height'] = intval($matches[1]);
}
if (preg_match('/class=["\']([^"\']+)["\']/', $img_tag, $matches)) {
$attributes['class'] = $matches[1];
}
if (empty($attributes['src'])) {
return null;
}
// Try to find WordPress attachment ID
$attachment_id = attachment_url_to_postid($attributes['src']);
if ($attachment_id) {
return $this->get_attachment_image_data($attachment_id, $attributes);
} else {
return $this->get_external_image_data($attributes);
}
}
```
## 4. Media Library & EXIF Integration
### 4.1 Extended EXIF Processing
```php
/**
* Comprehensive EXIF data extraction and formatting
*/
public function process_image_exif($attachment_id) {
$file_path = get_attached_file($attachment_id);
if (!$file_path || !function_exists('exif_read_data')) {
return array();
}
$exif = @exif_read_data($file_path);
if (!$exif) {
return array();
}
$processed_exif = array();
// Basic camera settings
$exif_mappings = array(
'Make' => 'cameraMake',
'Model' => 'cameraModel',
'FocalLength' => 'focalLength',
'FNumber' => 'aperture',
'ISOSpeedRatings' => 'iso',
'ExposureTime' => 'shutterSpeed',
'Flash' => 'flash',
'WhiteBalance' => 'whiteBalance',
'ExposureProgram' => 'exposureProgram',
'MeteringMode' => 'meteringMode'
);
foreach ($exif_mappings as $exif_key => $schema_key) {
if (isset($exif[$exif_key])) {
$processed_exif[] = array(
'@type' => 'PropertyValue',
'name' => $schema_key,
'value' => $this->format_exif_value($exif_key, $exif[$exif_key])
);
}
}
// GPS coordinates
if (isset($exif['GPSLatitude']) && isset($exif['GPSLongitude'])) {
$lat = $this->gps_to_decimal($exif['GPSLatitude'], $exif['GPSLatitudeRef']);
$lng = $this->gps_to_decimal($exif['GPSLongitude'], $exif['GPSLongitudeRef']);
if ($lat && $lng) {
$processed_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'geoLocation',
'value' => array(
'@type' => 'GeoCoordinates',
'latitude' => $lat,
'longitude' => $lng
)
);
}
}
// Date taken
if (isset($exif['DateTimeOriginal'])) {
$processed_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'dateTimeOriginal',
'value' => date('c', strtotime($exif['DateTimeOriginal']))
);
}
return $processed_exif;
}
private function format_exif_value($key, $value) {
switch ($key) {
case 'FocalLength':
if (strpos($value, '/') !== false) {
list($num, $den) = explode('/', $value);
return round($num / $den, 1) . 'mm';
}
return $value . 'mm';
case 'FNumber':
if (strpos($value, '/') !== false) {
list($num, $den) = explode('/', $value);
return 'f/' . round($num / $den, 1);
}
return 'f/' . $value;
case 'ExposureTime':
if (strpos($value, '/') !== false) {
list($num, $den) = explode('/', $value);
if ($num == 1) {
return '1/' . $den . 's';
} else {
return round($num / $den, 2) . 's';
}
}
return $value . 's';
default:
return $value;
}
}
private function gps_to_decimal($coordinate, $hemisphere) {
if (!is_array($coordinate) || count($coordinate) != 3) {
return false;
}
$degrees = $this->fraction_to_decimal($coordinate[0]);
$minutes = $this->fraction_to_decimal($coordinate[1]);
$seconds = $this->fraction_to_decimal($coordinate[2]);
$decimal = $degrees + ($minutes / 60) + ($seconds / 3600);
if ($hemisphere == 'S' || $hemisphere == 'W') {
$decimal = -$decimal;
}
return $decimal;
}
private function fraction_to_decimal($fraction) {
if (strpos($fraction, '/') !== false) {
list($num, $den) = explode('/', $fraction);
return $num / $den;
}
return floatval($fraction);
}
```
### 4.2 Media Library Enhancement
```php
/**
* Enhanced media library fields for comprehensive image metadata
*/
public function add_comprehensive_image_fields($form_fields, $post) {
if (!wp_attachment_is_image($post->ID)) {
return $form_fields;
}
// Get existing metadata
$schema_meta = get_post_meta($post->ID, '_schema_imageobject', true);
if (!is_array($schema_meta)) {
$schema_meta = array();
}
// Basic information section
$form_fields['schema_section_basic'] = array(
'tr' => '