In this tutorial we’ll learn how to create a custom widget that outputs your business information, such as its official name, address, phone number and e-mail. But we’ll take it one step further by outputting it with Microdata or schema markup, which will allow search engines to understand your content.

Widgets are dynamic blocks that can be put in available areas in your theme. Usually your theme would have as a minimum one sidebar and one or more areas in footer. Having your business information as a widget in the footer is common and pretty smart – as the footer appears on all pages in your site.

Microdata is additional HTML attributes that explains what specific HTML tag contains so that a machine can understand what they are (e.g. business name, phone number, business address, business e-mail address, etc). This makes it possible for search engines such as Google to extract information about your business from your HTML.

What we’ll create

Our widget will output information using Microdata schema LocalBusiness which is perfect for an organization or business. It’s fully up to you what properties you wish to output, just click the link above to read all possible properties within LocalBusiness (including inherited). Your business or your country might prefer different kind of information.

This tutorial’s widget will output the following optional information:

  • Company name (Microdata property: legalName)
  • Vat ID or organization number (Microdata property: vatID)
  • Postal address (Microdata tag: PostalAddress with properties for streetAddress, postalCode, and addressLocality)
  • Company’s e-mail address (Microdata property: email)
  • Phone number (Microdata property: telephone)

This tutorial won’t guide you through the styling of the widget, as this should be pretty straightforward. In frontend our widget will look like a normal text widget. But of course, under the hood, it has schema markup that helps Google.

The Basics of Creating a Custom Widget

You can put your code in your theme’s functions.php or create a custom plugin. Just remember that if you keep it in a plugin, you’ll lose the widget when deactivating the plugin; and similarly keeping it in the theme will make you lose the widget if you switch to another theme. In this example I’ll add the code in the theme’s functions.php.

Creating a widget is done with object-oriented PHP code. You write a PHP class that extends WordPress’ widget class, and initialize it by calling register_widget() and providing your class name. In this tutorial I have named my widget class LocalBusiness.

Let’s start by calling register_widget() inside a function hooked to the action widgets_init.

add_action('widgets_init', function() {
	register_widget('LocalBusiness');
});

Let’s quickly look over the skeleton of a custom widget class:

class LocalBusiness extends WP_Widget {

	// Initialize your widget in the class constructor
	public function __construct() { }

	// Responsible for outputting the widget in frontend
	public function widget($args, $instance) { }

	// Responsible for outputting the widget settings in admin
	public function form($instance) { }

	// Responsible for saving settings in admin
	public function update($new_instance, $old_instance) { }
	
}

As you can see from above you will need four functions inside your class. Let’s go through each function one by one and fill them in.

Building a LocalBusiness Microdata Widget

The most logical place to start is in the constructor, which is responsible for setting up your widget.

The __construct() function

Inside the constructor you need to set up some variables, such as the widget’s name, and call the parent’s constructor function (the parent class is the one you extend; WP_Widget). Read more about possible options in constructor here. I will provide a base ID, a title and a description, like so:

// Initialize your widget in the class constructor
public function __construct() {
	$widget_ops = [
		'description' => __('Outputs business information with Microdata', 'txtdomain')
	];
	parent::__construct('local_business', __('Local Business Information', 'txtdomain'), $widget_ops);
}

You can do more in __construct method, such as enqueuing scripts or defining more widget settings. But the above is usually enough in most cases.

The form() function

The next step is building all settings and inputs your widget accepts in admin. For outputting settings to Widget admin we use the function form() which gives you one parameter; an array that holds all your possibly saved widget options. It’s important that you output the corresponding saved setting in all your inputs so that the data is retained. (We’ll look at how to save the settings in the next step).

There’s a lot of to keep track in the form function, so let’s just add one input, for the legal name, first and make sure we understand what we need to do:

// Responsible for outputting the widget settings in admin
public function form($instance) { 
	?>
	<p>
		<label for="<?php echo $this->get_field_id('legal_name'); ?>"><?php _e('Legal name:', 'txtdomain'); ?></label>
		<input 
			type="text" 
			class="widefat" 
			id="<?php echo esc_attr($this->get_field_id('legal_name')); ?>"
			name="<?php echo esc_attr($this->get_field_name('legal_name')); ?>"
			value="<?php echo esc_attr($instance['legal_name']); ?>"
		/>
	</p>
	<?php
}

First of all the above code outputs some HTML wrappers and classes in the form that WordPress outputs their widget forms – we do this so the form looks nice.

There are two functions you need to get familiar with; get_field_id() and get_field_name() and they both are functions within WP_Widget (which is why you call “$this->” in front – whereas $this refers to the class’ instance). The functions returns a provided field’s ID and name, respectively, for use in your input’s id and name attributes. It’s very important to not forget adding a name attribute on your input, otherwise you will never get ahold of its value when saving.

And finally we output the current saved value as value to our input by referending the passed argument $instance. Without doing this for your value, the input will never be populated with whatever is saved in database, and will appear blank every time, which can confuse users.

If you want different form inputs, such as checkboxes or dropdowns, you should be able to easily add them following the rules mentioned above. Let’s add the rest of our widget settings. They are all text inputs, so it’s the same code as above repeated, except their field IDs. Our form() method ends up looking like this:

// Responsible for outputting the widget settings in admin
public function form($instance) { 
	?>
	<p>
		<label for="<?php echo $this->get_field_id('legal_name'); ?>"><?php _e('Legal name:', 'txtdomain'); ?></label>
		<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('legal_name')); ?>" name="<?php echo esc_attr($this->get_field_name('legal_name')); ?>" value="<?php echo esc_attr($instance['legal_name']); ?>" />
	</p>
	<p>
		<label for="<?php echo $this->get_field_id('vat_id'); ?>"><?php _e('Vat ID/Organization number:', 'txtdomain'); ?></label>
		<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('vat_id')); ?>" name="<?php echo esc_attr($this->get_field_name('vat_id')); ?>" value="<?php echo esc_attr($instance['vat_id']); ?>" />
	</p>
	<p>
		<label for="<?php echo $this->get_field_id('street_address'); ?>"><?php _e('Street address:', 'txtdomain'); ?></label>
		<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('street_address')); ?>" name="<?php echo esc_attr($this->get_field_name('street_address')); ?>" value="<?php echo esc_attr($instance['street_address']); ?>" />
	</p>
	<p>
		<label for="<?php echo $this->get_field_id('postal_code'); ?>"><?php _e('Postal code:', 'txtdomain'); ?></label>
		<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('postal_code')); ?>" name="<?php echo esc_attr($this->get_field_name('postal_code')); ?>" value="<?php echo esc_attr($instance['postal_code']); ?>" />
	</p>
	<p>
		<label for="<?php echo $this->get_field_id('postal_city'); ?>"><?php _e('City:', 'txtdomain'); ?></label>
		<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('postal_city')); ?>" name="<?php echo esc_attr($this->get_field_name('postal_city')); ?>" value="<?php echo esc_attr($instance['postal_city']); ?>" />
	</p>
	<p>
		<label for="<?php echo $this->get_field_id('email_address'); ?>"><?php _e('E-mail address:', 'txtdomain'); ?></label>
		<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('email_address')); ?>" name="<?php echo esc_attr($this->get_field_name('email_address')); ?>" value="<?php echo esc_attr($instance['email_address']); ?>" />
	</p>
	<p>
		<label for="<?php echo $this->get_field_id('phone_number'); ?>"><?php _e('Phone number:', 'txtdomain'); ?></label>
		<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('phone_number')); ?>" name="<?php echo esc_attr($this->get_field_name('phone_number')); ?>" value="<?php echo esc_attr($instance['phone_number']); ?>" />
	</p>
	<?php
}

If you add your widget into an available widget area, it should look like this:

The update() function

The update() function is responsible for actually saving your inputted values in admin. Unfortunately WordPress is not doing this automatically for you. Provided in this function are two parameters; usually named $new_instance and $old_instance. Inside the first parameter, $new_instance, you’ll find all values that were submitted, and within the second, $old_instance, you’ll find the values that are currently saved in the database. This allows you to do some smart comparisons if needed.

Usually you would simply just make a new array of all your widget settings and save everything that is set inside $new_instance. We take care of some sanitizing as well. Finally we simply return this array, which will tell WordPress what to save.

// Responsible for saving settings in admin
public function update($new_instance, $old_instance) {
	$instance = [];
	$instance['legal_name'] = (!empty($new_instance['legal_name'])) ? strip_tags($new_instance['legal_name']) : '';
	$instance['vat_id'] = (!empty($new_instance['vat_id'])) ? strip_tags($new_instance['vat_id']) : '';
	$instance['street_address'] = (!empty($new_instance['street_address'])) ? strip_tags($new_instance['street_address']) : '';
	$instance['postal_code'] = (!empty($new_instance['postal_code'])) ? strip_tags($new_instance['postal_code']) : '';
	$instance['postal_city'] = (!empty($new_instance['postal_city'])) ? strip_tags($new_instance['postal_city']) : '';
	$instance['email_address'] = (!empty($new_instance['email_address'])) ? strip_tags($new_instance['email_address']) : '';
	$instance['phone_number'] = (!empty($new_instance['phone_number'])) ? strip_tags($new_instance['phone_number']) : '';
	return $instance;
}

You can now test your widget if you want, and check that your inputted values are being saved. And if you remember to set the value attribute correctly in form(), when you save and hit refresh, the values should retain. Great! Now on to the last, and admittely most fun, step – outputting the frontend bit.

The widget() function

The widget() function is responsible for outputting your widget in frontend. We’ll get two arguments to the function; firstly an array with some useful information such as the theme’s defined widget area wrappers, and secondly your saved widget setting values.

The output of you widget should always start with echoing $args['before_widget'] and always end with echoing $args['after_widget']. This makes sure your widget gets wrapped in the same proper widget HTML wrappers as defined by the theme. Along the same tracks, you can echo $args['before_title'] and $args['after_title'] for outputting correct HTML wrappers around the widget title. We don’t have an actual widget title per se, but we’ll wrap the business legal name as a widget title.

Otherwise you get ahold of your saved values by referencing the second argument, $instance, by your key names set in form() and update(). It’s good practice to output only the settings that were set – and ignore the empty ones.

Because we are also outputting Microdata, we need to add the corresponding properties following the rules of schema.org.

It’s fully up to you how you want to output your widget; you might probably consider adding more HTML wrappers for easier styling.

// Responsible for outputting the widget in frontend
public function widget($args, $instance) {
	echo $args['before_widget'];

	?><div itemscope itemtype="https://schema.org/LocalBusiness"><?php

	if (!empty($instance['legal_name'])) {
		echo $args['before_title'];
		
		?><span itemprop="legalName"><?php echo $instance['legal_name']; ?></span><?php

		echo $args['after_title'];
	}

	if (!empty($instance['vat_id'])) {
		?><span itemprop="vatID" class="business-vatid"><?php printf(__('Vat: %s', 'txtdomain'), $instance['vat_id']); ?></span><?php
	}

	if (!empty($instance['street_address'])) {
		?><div itemprop="address" itemscope itemtype="https://schema.org/PostalAddress" class="business-address"><?php

			?><span itemprop="streetAddress"><?php echo $instance['street_address']; ?></span><?php

			if (!empty($instance['postal_code'])) {
				?><span itemprop="postalCode"><?php echo $instance['postal_code']; ?></span><?php
			}
			
			if (!empty($instance['postal_city'])) {
				?><span itemprop="addressLocality"><?php echo $instance['postal_city']; ?></span><?php
			}				

		?></div><?php
	}

	if (!empty($instance['email_address'])) {
		?><a href="mailto:<?php echo $instance['email_address']; ?>" title="<?php _e('Send email', 'txtdomain'); ?>" class="business-email">
			<span itemprop="email"><?php echo $instance['email_address']; ?></span>
		</a><?php
	}

	if (!empty($instance['phone_number'])) {
		?><a href="tel:<?php echo $instance['phone_number']; ?>" title="<?php _e('Call us', 'txtdomain'); ?>" class="business-phone">
			<span itemprop="telephone"><?php echo $instance['phone_number']; ?></span>
		</a><?php
	}

	?></div><?php

	echo $args['after_widget'];
}

Customize the output, add some styling and that’s it!

FYI: Your widget will get the wrapping class name “widget_<base ID>” (base ID is what you provided in constructor). In our case our widget will get the class ” widget_local_business“. This might help you add some targeted styling.

Wrapping up and final code

In this tutorial we learned how to create a custom widget, and how to render a Microdata formatted output from its settings. You should be able to create your own widgets, by following the basics of a widget class!

For reference, here is the full code, all together.

add_action('widgets_init', function() {
	register_widget('LocalBusiness');
});

class LocalBusiness extends WP_Widget {

	// Initialize your widget in the class constructor
	public function __construct() {
		$widget_ops = [
			'description' => __('Outputs business information with Microdata', 'txtdomain')
		];
		parent::__construct('local_business', __('Local Business Information', 'txtdomain'), $widget_ops);
	}

	// Responsible for outputting the widget in frontend
	public function widget($args, $instance) {
		echo $args['before_widget'];

		?><div itemscope itemtype="https://schema.org/LocalBusiness"><?php

		if (!empty($instance['legal_name'])) {
			echo $args['before_title'];
			
			?><span itemprop="legalName"><?php echo $instance['legal_name']; ?></span><?php

			echo $args['after_title'];
		}

		if (!empty($instance['vat_id'])) {
			?><span itemprop="vatID" class="business-vatid"><?php printf(__('Vat: %s', 'txtdomain'), $instance['vat_id']); ?></span><?php
		}

		if (!empty($instance['street_address'])) {
			?><div itemprop="address" itemscope itemtype="https://schema.org/PostalAddress" class="business-address"><?php

				?><span itemprop="streetAddress"><?php echo $instance['street_address']; ?></span><?php

				if (!empty($instance['postal_code'])) {
					?><span itemprop="postalCode"><?php echo $instance['postal_code']; ?></span><?php
				}
				
				if (!empty($instance['postal_city'])) {
					?><span itemprop="addressLocality"><?php echo $instance['postal_city']; ?></span><?php
				}				

			?></div><?php
		}

		if (!empty($instance['email_address'])) {
			?><a href="mailto:<?php echo $instance['email_address']; ?>" title="<?php _e('Send email', 'txtdomain'); ?>" class="business-email">
				<span itemprop="email"><?php echo $instance['email_address']; ?></span>
			</a><?php
		}

		if (!empty($instance['phone_number'])) {
			?><a href="tel:<?php echo $instance['phone_number']; ?>" title="<?php _e('Call us', 'txtdomain'); ?>" class="business-phone">
				<span itemprop="telephone"><?php echo $instance['phone_number']; ?></span>
			</a><?php
		}
	
		?></div><?php

		echo $args['after_widget'];
	}

	// Responsible for outputting the widget settings in admin
	public function form($instance) { 
		?>
		<p>
			<label for="<?php echo $this->get_field_id('legal_name'); ?>"><?php _e('Legal name:', 'txtdomain'); ?></label>
			<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('legal_name')); ?>" name="<?php echo esc_attr($this->get_field_name('legal_name')); ?>" value="<?php echo esc_attr($instance['legal_name']); ?>" />
		</p>
		<p>
			<label for="<?php echo $this->get_field_id('vat_id'); ?>"><?php _e('Vat ID/Organization number:', 'txtdomain'); ?></label>
			<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('vat_id')); ?>" name="<?php echo esc_attr($this->get_field_name('vat_id')); ?>" value="<?php echo esc_attr($instance['vat_id']); ?>" />
		</p>
		<p>
			<label for="<?php echo $this->get_field_id('street_address'); ?>"><?php _e('Street address:', 'txtdomain'); ?></label>
			<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('street_address')); ?>" name="<?php echo esc_attr($this->get_field_name('street_address')); ?>" value="<?php echo esc_attr($instance['street_address']); ?>" />
		</p>
		<p>
			<label for="<?php echo $this->get_field_id('postal_code'); ?>"><?php _e('Postal code:', 'txtdomain'); ?></label>
			<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('postal_code')); ?>" name="<?php echo esc_attr($this->get_field_name('postal_code')); ?>" value="<?php echo esc_attr($instance['postal_code']); ?>" />
		</p>
		<p>
			<label for="<?php echo $this->get_field_id('postal_city'); ?>"><?php _e('City:', 'txtdomain'); ?></label>
			<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('postal_city')); ?>" name="<?php echo esc_attr($this->get_field_name('postal_city')); ?>" value="<?php echo esc_attr($instance['postal_city']); ?>" />
		</p>
		<p>
			<label for="<?php echo $this->get_field_id('email_address'); ?>"><?php _e('E-mail address:', 'txtdomain'); ?></label>
			<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('email_address')); ?>" name="<?php echo esc_attr($this->get_field_name('email_address')); ?>" value="<?php echo esc_attr($instance['email_address']); ?>" />
		</p>
		<p>
			<label for="<?php echo $this->get_field_id('phone_number'); ?>"><?php _e('Phone number:', 'txtdomain'); ?></label>
			<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('phone_number')); ?>" name="<?php echo esc_attr($this->get_field_name('phone_number')); ?>" value="<?php echo esc_attr($instance['phone_number']); ?>" />
		</p>
		<?php
	}

	// Responsible for saving settings in admin
	public function update($new_instance, $old_instance) {
		$instance = [];
		$instance['legal_name'] = (!empty($new_instance['legal_name'])) ? strip_tags($new_instance['legal_name']) : '';
		$instance['vat_id'] = (!empty($new_instance['vat_id'])) ? strip_tags($new_instance['vat_id']) : '';
		$instance['street_address'] = (!empty($new_instance['street_address'])) ? strip_tags($new_instance['street_address']) : '';
		$instance['postal_code'] = (!empty($new_instance['postal_code'])) ? strip_tags($new_instance['postal_code']) : '';
		$instance['postal_city'] = (!empty($new_instance['postal_city'])) ? strip_tags($new_instance['postal_city']) : '';
		$instance['email_address'] = (!empty($new_instance['email_address'])) ? strip_tags($new_instance['email_address']) : '';
		$instance['phone_number'] = (!empty($new_instance['phone_number'])) ? strip_tags($new_instance['phone_number']) : '';
		return $instance;
	}
}