Tutorials

Better SEO, Part 2: Open Graph, Canonical URLs, and Structured Data without Plugins

A meta description covers one line of a search snippet. Three more fields tend to matter just as much: how a page looks when it’s shared, whether search engines see one canonical URL for it, and whether Google can build a rich result from it. All three can be added the same way as the meta description — a custom field and a few lines in header.php, no plugin.

1. Open Graph tags

These control how a page appears when it’s shared on Twitter, Facebook, LinkedIn, or Slack. Reuse the description custom field from the earlier post, and pull the featured image automatically:

<?php if ( is_singular() ) : ?>
<meta property="og:type" content="article" />
<meta property="og:title" content="<?php echo esc_attr( get_the_title() ); ?>" />
<meta property="og:url" content="<?php echo esc_url( get_permalink() ); ?>" />
<?php $og_description = get_post_meta( get_the_ID(), 'description', true ); ?>
<?php if ( $og_description ) : ?>
<meta property="og:description" content="<?php echo esc_attr( $og_description ); ?>" />
<?php endif; ?>
<?php if ( has_post_thumbnail() ) : ?>
<meta property="og:image" content="<?php echo esc_url( get_the_post_thumbnail_url( get_the_ID(), 'large' ) ); ?>" />
<?php endif; ?>
<?php endif; ?>

Place this inside <head>, right after the meta description tag from the earlier post.

2. Canonical URL

WordPress already outputs this one — every singular page gets a <link rel="canonical"> tag from core, no setup needed. The only case worth a custom field is when a page’s real canonical points somewhere else entirely, like a syndicated post:

<?php
add_action( 'wp_head', function () {
	$canonical = get_post_meta( get_the_ID(), 'canonical_url', true );
	if ( $canonical ) {
		remove_action( 'wp_head', 'rel_canonical' );
		echo '<link rel="canonical" href="' . esc_url( $canonical ) . '" />' . "n";
	}
}, 5 );

3. Structured data

A basic Article schema is what lets Google show a headline and date directly in search results. Build it from data already on the post — no new fields required:

<?php if ( is_singular( 'post' ) ) : ?>
<script type="application/ld+json"><?php
	echo wp_json_encode( array(
		'@context'      => 'https://schema.org',
		'@type'         => 'Article',
		'headline'      => get_the_title(),
		'datePublished' => get_the_date( 'c' ),
		'dateModified'  => get_the_modified_date( 'c' ),
		'author'        => array(
			'@type' => 'Person',
			'name'  => get_the_author(),
		),
	) );
?></script>
<?php endif; ?>

Three tags, one script block, zero plugins. Test the result with Google’s Rich Results Test and Facebook’s Sharing Debugger before calling it done.