Moving to the Customizer API from the Settings API, using Options API

Theme developers using a custom implementation of Options APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways./Settings API, or who use one of the many excellent frameworks/libraries that use Options API/Settings API, to handle Theme options and create settings pages, will soon need to modify their Theme options to use of the CustomizerCustomizer Tool built into WordPress core that hooks into most modern themes. You can use it to preview and modify many of your site’s appearance settings. API. This post will discuss some of the issues potentially encountered with moving from Options API/Settings API to Options API/Customizer API.

Note: there are other tutorials that describe basic Customizer API implementation. My focus with this post is specifically porting from Options API/Settings API to Options API/Customizer API.

What API?

First, a note regarding APIs.

The Options API and the Theme Mods API are used to store/retrieve options to/from the database. The Settings API and the Customizer API are used to expose settings to the user to configure options. Either of the storage/retrieval APIs can be used with either of the user-configuration APIs.

The new Theme Review requirement stipulates use of the Customizer API in lieu of the Settings API, but does not require use of either the Options API or the Theme Mods API in lieu of the other. So, both Theme Mods API/Customizer API implementations and Options API/Customizer API implementations remain acceptable.

Now, for new Themes, it is certainly recommended – and easier – to use a Theme Mods API/Customizer API implementation. However, existing Themes with existing users, it is not feasible to move from an Options API/Settings API implementation to a Theme Mods API/Customizer API implementation. Fortunately, the Options API/Customizer API implementation is a feasible alternative.

Options Configuration Array

For the purposes of this post, I’ll assume that your existing Options API/Settings API implementation is built around a configuration array that defines each of the Theme options, and the various parameters for each option. Each setting will have certain standard parameters, including ID, title, description, field type (checkbox, radio, text, textarea, etc.), a sanitization/data type (HTMLHTML HTML is an acronym for Hyper Text Markup Language. It is a markup language that is used in the development of web pages and websites. text, no-HTML text, email, hex color, etc.), and default value. Additionally, each setting may have a defined settings page section, and, if applicable, settings page tab. Finally, for settings that have select-type fields (select, radio, etc.), the parameters will include an array of choices.

For the purposes of this post, we’ll assume that the configuration array looks like so:

$settings_parameters = array(
	'text_setting_1' => array(
		'id' => 'text_setting_1',
		'title' => __( 'Text Setting 1', 'theme-slug' ),
		'description' => __( 'Text setting 1 description', 'theme-slug' ),
		'field_type' => 'text',
		'sanitize' => 'html',
		'tab' => 'tab_1',
		'section' => 'section_1',
		'default' => __( 'Default Text Setting 1 text', 'theme-slug' )
	),
	'text_setting_2' => array(
		'id' => 'text_setting_2',
		'title' => __( 'Text Setting 2', 'theme-slug' ),
		'description' => __( 'Text setting 2 description', 'theme-slug' ),
		'field_type' => 'text',
		'sanitize' => 'email',
		'tab' => 'tab_1',
		'section' => 'section_2',
		'default' => __( 'noreply@example.com', 'theme-slug' )
	),
	'select_setting_1' => array(
		'id' => 'select_setting_1',
		'title' => __( 'Select Setting 1', 'theme-slug' ),
		'description' => __( 'Select setting 1 description', 'theme-slug' ),
		'field_type' => 'select',
		'sanitize' => 'select',
		'tab' => 'tab_2',
		'section' => 'section_1',
		'default' => 'blue',
		'choices' => array(
			'blue' => __( 'Blue', 'text-slug' ),
			'red' => __( 'Red', 'text-slug' ),
			'green' => __( 'Green', 'text-slug' ),
		)
	),
);

Assuming that your implementation has a tabbed settings page, with settings sections for each tab, you will have another array defined, perhaps like so:

$settings_page_tabs = array(
	'tab_1' => array(
		'id' => 'tab_1',
		'title' => __( 'Page Tab 1', 'theme-slug' ),
		'description' => __( 'Page tab 1 description', 'theme-slug' ),
		'sections' => array(
			'section_1' => array(
				'id' => 'section_1',
				'title' => __( 'Section 1 Title', 'theme-slug' ),
				'description' => __( 'Section 1 description', 'theme-slug' )
			),
			'section_2' => array(
				'id' => 'section_2',
				'title' => __( 'Section 2 Title', 'theme-slug' ),
				'description' => __( 'Section 2 description', 'theme-slug' )
			),
		),
	'tab_2' => array(
		'id' => 'tab_2',
		'title' => __( 'Page Tab 2', 'theme-slug' ),
		'description' => __( 'Page tab 2 description', 'theme-slug' ),
		'sections' => array(
			'section_1' => array(
				'id' => 'section_1',
				'title' => __( 'Section 1 Title', 'theme-slug' ),
				'description' => __( 'Section 1 description', 'theme-slug' )
			),
			'section_2' => array(
				'id' => 'section_2',
				'title' => __( 'Section 2 Title', 'theme-slug' ),
				'description' => __( 'Section 2 description', 'theme-slug' )
			),
		)
	)
);

Moving from Settings API to Customizer API

Settings Page Tabs => Customizer Panels

With the Settings API, you have to write your own code to implement settings page tabs. With the Customizer, tabs are built in, and they are called Panels. Using our settings page configuration above, setting up Panels is simple:

foreach ( $settings_page_tabs as $panel ) {
	$wp_customize->add_panel(
		'theme_slug_' . $panel['id'], 
		array(
			'priority' 	=> 10,
			'capability' 	=> 'edit_theme_options',
			'title' 	=> $panel['title'],
			'description' 	=> $panel['description'],
		) 
	);
}

Note here that you can sort your Theme’s panels, using the 'priority' parameter. Also, passing 'capability' => 'edit_theme_options', you’re taking care of the capability check that you would do elsewhere, via add_theme_page(), with the Settings API.

Settings Page Sections => Customizer Panel Sections

With the Settings API, you add a settings page section using add_settings_section():

foreach ( $settings_page_tabs as $tab ) {
	// Loop through tabs for sections
	foreach ( $tab['sections'] as $section ) {
		add_settings_section(
			// $id
			'theme_slug_' . $section['id'] . '_section',
			// $title
			$section['title'],
			// $callback
			'theme_slug_section_callback',
			// $page (menu slug)
			'theme-slug'
		);
	}
}

You would then have to define the callback for each section (normally used for the descriptive text for the section), and you would also have to take care to pass the correct string for $page. Then, in your settings page, you would have to call do_settings_section( $id ) for each settings section.

With the Customizer API, again, this process is simplified and the code is similar:

foreach ( $settings_page_tabs as $panel ) {
	// Loop through tabs for sections
	foreach ( $panel['sections'] as $section ) {
		$wp_customize->add_section(
			// $id
			'theme_slug_' . $section['id'],
			// parameters
			array(
				'title'		=> $section['title'],
				'description'	=> $section['description'],
				'panel'		=> 'theme_slug_' . $panel['id']
			)
		);
	}
}

Settings Page Settings => Customizer Settings

The next step is to register each of the settings fields. With the Settings API, you use add_settings_field() to define the setting, and you pass as a parameter a callback that defines the form field markup for that setting. Using the settings parameters array, you might be doing something dynamic, like so:

foreach ( $settings_parameters as $option ) {
	add_settings_field(
		// $settingid
		'them_slug_setting_' . $option['id'],
		// $title
		$option['title'],
		// $callback
		'theme_slug_setting_callback',
		// $pageid
		'theme_slug_' . $option['tab'] . '_tab',
		// $sectionid
		'theme_slug_' . $option['section'] . '_section',
		// $args
		$option
	);

The code for the Customizer API is similar:

foreach ( $settings_parameters as $option ) {
	$wp_customize->add_setting(
		// $id
		'theme_slug_options[' . $option['id'] . ']',
		// parameters array
		array(
			'default'		=> $option['default'],
			'type'			=> 'option',
			'sanitize_callback'	=> 'theme_slug_sanitize_' . $option['sanitize']

		)
	);

A couple things to note here:

First, 'type' => 'option' tells the Customizer to use the Options API, rather than the Theme Mods API, to retrieve/store settings.

Second, the 'sanitize_callback' is required. We will address why later.

Settings API Settings Page Setting Field Callback => Customizer Control

With the Settings API, when you call add_settings_field(), you might have defined a single setting callback that switches through output based on field type, or you may have separate callbacks for each field type. Either way, with the Customizer API, this process is much simpler for field types defined by the API (text, checkbox, radio, select, dropdown_pages, textarea):

foreach ( $settings_parameters as $option ) {
	// Add setting control
	$wp_customize->add_control(
		// $id
		'theme_slug_options[' . $option['id'] . ']',
		// parameters array
		array(
			'label'		=> $option['title'],
			'section'	=> 'theme_slug_' . $option['section'],
			'settings'	=> 'theme_slug_options['. $option['id'] . ']',
			'type'		=> $option['field_type'],
			'label'		=> $option['title'],
			'description'   => $option['description'], // If applicable (select, radio, etc.) 'choices' => $option['choices'] ) ); }

Something very important to note here: the value you pass for the 'settings' parameter must be the same as the option ID you define in register_setting(). Also, assuming that you are properly using a single options array, the structure 'theme_slug_options[id]' is important, since that is what gets passed to the Settings API settings page form fields, so you want to ensure that you pass the same structure to the Customizer. That way, the Customizer will properly retrieve and save the user’s current settings.

For simplicity, I use the same structure 'theme_slug_options['. $option['id'] . ']' for the ID of the setting, the ID of the control, and the value of the 'settings' key passed to the control.

Also, if you need to add custom controls, you can loopLoop The Loop is PHP code used by WordPress to display posts. Using The Loop, WordPress processes each post to be displayed on the current page, and formats it according to how it matches specified criteria within The Loop tags. Any HTML or PHP code in the Loop will be processed on each post. https://codex.wordpress.org/The_Loop. through the option types separately. For example, if you want to add a coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. color control:

foreach ( $settings_parameters as $option ) {
	// Add controls for built-in control types		
	if ( in_array( $option['field_type'], array( 'text', 'checkbox', 'radio', 'select', 'dropdown_pages', 'textarea' ) ) ) {
		// Add setting control
		$wp_customize->add_control(
			// $id
			'theme_slug_options[' . $option['id'] . ']',
			// parameters array
			array()
		);
	}
	// Add color control
	else if ( 'color' == $option['field_type'] ) {
		$wp_customize->add_control( 
			new WP_Customize_Color_Control( 
				$wp_customize, 
				'link_color', 
				array() 
			) 
		);
	}
}

You can use the same method for any of the core controls, or for custom controls.

Sanitization/Validation

All-Settings Callback via register_setting()

With the Options API, you register your Theme options array using register_setting(), the third parameter of which is the sanitization/validation callback:

register_setting(
	// $optiongroup
	'theme_slug_options',
	// $option
	'theme_slug_options',
	// $sanitize_callback
	'theme_slug_options_validate'
);

When the Settings API settings page form is submitted, the Theme’s $option object is passed as $_POST data through the $sanitize_callback before being saved to the database.

With the Customizer API, the same thing happens. If you have called register_setting(), when the Customizer form is submitted, the Theme’s $option object is passed as $_POST data through the $sanitize_callback before being saved to the database.

Customizer API: All-Settings Callback via sanitize_options_$option Hook

With the Customizer API, you don’t necessarily have to retain the register_setting() call. You can also hook the $sanitize_callback into the 'sanitize_option_{$option}' hook, which would allow you to remove the register_setting() call altogether:

add_action( 'sanitize_option_theme_slug_options', 'theme_slug_options_validate' );

Customizer API: Per-Setting Sanitization

However, the Customizer is different from a settings page, in a way that makes the Customizer much more powerful: the live preview. By configuring settings in the Customizer, the user can see those settings changes applied immediately, via the live preview. But real-time configured settings are not passed through the 'sanitize_option_{$option}' hook until the settings are saved (i.e. when the Customizer form is submitted). That means that, without proper sanitization of the Customizer settings, the potential exists for XSS or other exploits.

The input will be passed through 'sanitize_callback' passed to $wp_customize->add_setting() not only when the settings are saved, but also when passed to the live previewer. Thus, per-setting sanitization, via the 'sanitize_callback' parameter passed to $wp_customize->add_setting(), is critical.

Dynamic Sanitization Callbacks

Let’s look at $wp_customize->add_setting() again:

$wp_customize->add_setting(
	// $id
	'theme_slug_options[' . $option['id'] . ']',
	// parameters array
	array(
		'default'		=> $option['default'],
		'type'			=> 'option',
		'sanitize_callback'	=> 'theme_slug_sanitize_' . $option['sanitize']
	)
);

Using this method, you need only define one callback for each sanitization type (HTML text, no-HTML text, email, checkbox true/false, etc. For example:

function theme_slug_sanitize_checkbox( $input ) {
	return ( ( isset( $input ) && true == $input ) ? true : false );
}
function theme_slug_sanitize_html( $input ) {
	return wp_filter_kses( $input );
}

function theme_slug_sanitize_nohtml( $input ) {
	return wp_filter_nohtml_kses( $input );
}

Dynamic Sanitization Callbacks for Select-Type Options

The above callbacks, that receive $input, manipulate it, and then return it, are fine for data that only requires sanitization. But what if the data requires validation, such as verifying that $input matches one of the valid choices defined for a select dropdown or radio list? An arbitrary callback such as the ones above will not have the list of valid choices. Fortunately, in addition to $input, the Customizer API passes a second parameter to 'sanitize_callback': the $setting object. We can use this object to grab the list of choices from the control:

function theme_slug_sanitize_select( $input, $setting ) {
	// Ensure input is a slug
	$input = sanitize_key( $input );
	// Get list of choices from the control
	// associated with the setting
	$choices = $setting->manager->get_control( $setting->id )->choices;
	// If the input is a valid key, return it;
	// otherwise, return the default
	return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
}

Caveat: for this little bit of magic to work, it is imperative that the setting ID is identical to the control ID; otherwise, the callback won’t be able to retrieve the control:

// Setting
$wp_customize->add_setting(
	// $id
	'theme_slug_options[' . $option['id'] . ']',
	// parameters array
	array()
);

// Control
$wp_customize->add_control(
	// $id
	'theme_slug_options[' . $option['id'] . ']',
	// parameters array
	array()
);

Notice that in both calls, the $id is 'theme_slug_options[' . $option['id'] . ']'.

Eliminating Unused Options API/Settings API Code

At this point, your Theme options should be retrieved from the database and exposed in the Customizer, configurable with live preview in the Customizer, and saved properly to the database from the Customizer. Everything in the Settings API settings page should now be redundant with the customizer. The next step is to remove unneeded code.

Settings API Settings Page

Now that the Settings API settings page is redundant with the Customizer, it can be removed. The call to add_theme_page() and its callback, calls to add_settings_section() and their callbacks, calls to add_settings_field and their callbacks, as well as any Contextual Help, can all be safely removed.

register_setting() and All-Settings Sanitization Callback

As mentioned above, with per-setting sanitization in the Customizer, the all-settings sanitization callback is no longer needed. Also, because the customizer implementation explicitly adds settings and controls for all options in the Theme options array, the call to register_setting() is also no longer required. Both can be removed safely.

Conclusion

That’s it! If you have other questions or issues regarding moving from Options API/Settings API to Options API/Customizer API, please post them in the comments below.

Meeting Notes 6/2/2015

The Theme Review Team met at our usual time today.

The first item on the agenda was a discussion of the proposal for a curated, multi-tier directory/review process. @greenshady will have a follow-up post soon, for a summary of that discussion, and continued discussion.

The second item on the agenda was a discussion of allowable content creation for Themes, per the presentation-vs-functionality requirement. Based on that discussion, the following types of content creation are allowable for Themes, as they represent trivial user content:

  • Site footer text
  • Call-to-Action (CTA) buttons/widgets
  • One-off descriptive content blocks (about us/profile/etc.)
  • Custom presentation of existing user data, with trivial content additions such as a widgetWidget A WordPress Widget is a small block that performs a specific function. You can add these widgets in sidebars also known as widget-ready areas on your web page. WordPress widgets were originally created to provide a simple and easy-to-use way of giving design and structure control of the WordPress theme to the user./content blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. with a static page link, custom icon, custom title/description)

No consensus was reached regarding repeatable-field content (testimonials, services, team members, etc.). We will have a follow-up blog post to discuss this type of content specifically.

The third item on the agenda was a discussion of the team’s code snippet library. Based on that discussion, we will begin adding code examples to the Theme Review Team GitHub account, beginning with code examples for basic implementation of the CustomizerCustomizer Tool built into WordPress core that hooks into most modern themes. You can use it to preview and modify many of your site’s appearance settings. APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways. using coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. controls.

As a reminder, if anyone has ideas for improving the Theme directory, the Theme review process, or anything related to the work of the Theme Review Team, please join in the discussion on this post.

Content Creation Discussion: Examples

In anticipation of the content creation discussion, and as a continuation of last week’s meeting, please post in the comments examples of types of content that you believe need clarification.

To reiterate: what we want to do is two-fold:

  1. Clarify what constitutes user content
  2. Identify what, if any, of such content is acceptable to be defined by the Theme

Please read @greenshady‘s post, where you will find the obvious things that are out-of-scope:

  • Custom post types.
  • Custom taxonomies.
  • Non-presentational post metadata.
  • Database tables.
  • Shortcodes.
  • Custom comment types.

Justin lists four types of other content:

  • Footer text
  • Portfolio projects
  • Profile widgetWidget A WordPress Widget is a small block that performs a specific function. You can add these widgets in sidebars also known as widget-ready areas on your web page. WordPress widgets were originally created to provide a simple and easy-to-use way of giving design and structure control of the WordPress theme to the user.
  • Front-page content sections

If you have more, please post your examples here, before discussing them in the meeting, in the hopes that the discussion in SlackSlack Slack is a Collaborative Group Chat Platform https://slack.com/. The WordPress community has its own Slack Channel at https://make.wordpress.org/chat/. will be as orderly/organized as possible. Also, please try to be as descriptive as possible. A “custom text widget” doesn’t really help. What is the purpose of that custom text widget? What type of content is it intended to create/hold/present?

How to Review Front Page Display Settings

One of the most important functional aspects of a Theme that must be tested during Theme review is the Theme’s handling of front page display settings.

It seems that this test is either getting missed during Theme reviews, or else is being misunderstood. The misunderstanding is, unfortunately, related to Justin’s earlier post regarding front page settings and demo content. Some Theme developers are using the technique of displaying demo content when 'posts' == get_option( 'show_on_front' )`, and then using other methods, such as Theme options, to configure the front page display settings otherwise. In other instances, the front page may show custom content along with a custom query of blog posts, thinking that the custom query suffices. In other instances, the coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. handling of front page display simply might not be well-understood.

(Note: the point of this post is not to point fingers, name names, or accuse anyone of doing anything intentionally wrong; rather, the focus is on ensuring that all reviewers are testing the front page display functionality consistently.)

The WordPress core handling of the front page display, and the blog posts index, and static pages can be a bit confusing. Please read this tutorial if you need additional information.

Review Requirements

In short:

  • When Settings->Reading->Front page displays is set to “Your latest posts”, the Theme must display the blog posts index
  • When Settings->Reading->Front page displays is set to “A static page”, the Theme must display a static page, and the blog posts index must be displayed on a static page.

Testing this functionality requires either studying the relevant template code, or installing the Theme, setting Settings->Reading->Front page displays to both settings, and verify what the Theme actually displays.

Environment Setup

In your test setup, you should have two static pages, one for the Front Page, and one for the blog posts index. I usually name these pages “Front Page” and “Blog”, and populate them with some dummy content. (The dummy content is important, to verify that the Theme is displaying the front page and blog posts index properly.)

Front Page Display: Latest Posts

The default state is Settings->Reading->Front Page displays = “Your latest posts”, which corresponds to 'posts' == get_option( 'show_on_front' )`.

When testing, the site front page should display the blog posts index, which should be rendered using the Home Template hierarchy, which is home.php, which falls back to index.php.

The site should render the Front Page page as a regular static page, with its dummy content displayed.  The site should render the Blog page as a regular static page, with its dummy content displayed.

Front Page Display: Static Page

Next, test the Theme with Settings->Reading->Front Page Displays to “A static page”, which corresponds to 'page' == get_option( 'show_on_front' )`. In the select dropdowns that appear, set “Front page” to “Front Page”, and set “Posts page” to “Blog”.

When testing, the site front page should display a static page. If the Theme includes front-page.php, the dummy content on the Front Page page will not be displayed; instead, the markup of front-page.php will be displayed. Otherrwise, the site front page will be rendered using the Page Template hierarchy, which is essentially page.php, which falls back to index.php. The Page Template hierarchy includes custom page templates and other complexity, so you may need to consider that as well.

The site should render the Front Page page as the site front page. The site should render the Blog page as the blog posts index, and the blog posts index display should replace the dummy content on the Blog page.

User Impact

Unfortunately, the impact to end users to fix incorrect handling of front page display settings after the fact is significant. Fixing Themes will almost always involve users having the behavior of their sites change when they update their Theme, and that is, quite simply, bad. Currently, there are some very heavily used Themes that will need to correct their handling of front page display, and thousands of users are going to be impacted.

I cannot stress enough how important it is that we get this aspect of the review correct before the Theme is approved. Many other issues can be addressed with minimal – or no – impact to users. Fixing this issue is going to cause a lot of surprise and frustration for many users.

But the Core Handling Sucks

Yes, the core handling might be less than ideal. Yes, the core workflow requires creating two “dummy” pages, changing a setting, and then changing two more settings. But unless and until core changes, Themes are required to support the core settings, and the core workflow. That is all the more reason to ensure that Themes are handling front page display properly before they are approved. If all Themes handle front page display the same way, the learning curve for the user is minimized, and the workflow is consistent from one Theme to the next.

Theme Review Requirements and Documentation

Some time ago, the Theme Review Guidelines underwent a major revision, with focus on Theme Review Requirements, and moving of the official version of those Requirements from the Codex to the Theme Review Handbook. During that change, several requirements were omitted from the previous iteration. Those omissions, along with the apparent spread of documentation locations, has led to considerable confusion for admins, reviewers, and developers alike.

Let’s sort that out, and clean it up.

Documentation

Documentation Sources

Handbook

The only and official Theme Review documentation is the Theme Review Handbook. Within that handbook are the Theme Review Requirements.

The Requirements page in the Handbook is deprecated. It should be removed. Until just a moment ago, it was linked from the welcome box on the front page of Make/Themes. I have changed that to a link to the official Requirements ins the Handbook.

The various Handbook pages linked from the Theme Review Codex page are also deprecated, and should be removed.

Make/Themes

The Make/Themes site (this site) is where we post official things, and generate discussion not well-suited to SlackSlack Slack is a Collaborative Group Chat Platform https://slack.com/. The WordPress community has its own Slack Channel at https://make.wordpress.org/chat/.. Anything discussed on Make/Themes is unofficial/discussion-only, unless and until it is posted in the Handbook, whether as a Requirement or a Recommendation.

Codex

The Codex is deprecated, and is slated to go away at some point. Thus, the Theme Review page on the Codex is no longer official, or maintained.

Summary

Official:

Unofficial/Discussion-Only:

  • Make/Themes blog posts

Deprecated:

Requirements

Next: requirements. Please discuss the additions in red italic, below. I am proposing them to be included again in the official version. The complete list of current Requirements is below:

A theme must meet all of the following requirements to be included in the WordPress.orgWordPress.org The community site where WordPress code is created and shared by the users. This is where you can download the source code for WordPress core, plugins and themes as well as the central location for community conversations and organization. https://wordpress.org/ theme repository.

Along with these checks you should also make sure you run the theme through the theme check pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party. It automatically checks for all the required items. You can find a full list of what it checks here.

AccessibilityAccessibility Accessibility (commonly shortened to a11y) refers to the design of products, devices, services, or environments for people with disabilities. The concept of accessible design ensures both “direct access” (i.e. unassisted) and “indirect access” meaning compatibility with a person’s assistive technology (for example, computer screen readers). (https://en.wikipedia.org/wiki/Accessibility)

  • If the theme has the tag ‘accessibility-ready’ then it needs to meet these requirements.

Code

  • No PHPPHP PHP (recursive acronym for PHP: Hypertext Preprocessor) is a widely-used open source general-purpose scripting language that is especially suited for web development and can be embedded into HTML. http://php.net/manual/en/intro-whatis.php. or JS errors.
  • Include at least index.php and style.css.
  • Have a valid DOCTYPE declaration and include language_attributes.
  • Sanitize everything.
  • No removing or modifying non-presentational hooksHooks In WordPress theme and development, hooks are functions that can be applied to an action or a Filter in WordPress. Actions are functions performed when a certain event occurs in WordPress. Filters allow you to modify certain functions. Arguments used to hook both filters and actions look the same..
  • No shortcodes are allowed.
  • Support the following WordPress-generated CSSCSS CSS is an acronym for cascading style sheets. This is what controls the design or look and feel of a site. classes:
    • alignleft
    • alignright
    • wp-caption
    • wp-caption-text
    • gallery-caption
    • sticky (can be unstyled)
    • bypostauthor (can be unstyled)
    • screen-reader-text
  • No backwards compatibility support for outdated versions of WordPress
  • No Theme Check required notices
  • No deprecated function or doing_it_wrong() notices
  • Provide a unique slug for everything the Theme defines in the public namespace
  • Prefix all options, custom functions, custom global variables and custom constants with the theme-slug. (Note: moved from Options and Settings, as the requirement has a wider scope than just options)

CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. Functionality and Features

  • Use WordPress functionality and features first, if available.
  • Don’t include admin/feature pointers.
  • No custom post types and no custom taxonomies.
  • No pay wall restricting any WordPress feature.
  • No disabling of the admin tool bar.
  • Use get_template_directory() rather than TEMPLATEPATH to return the template path.
  • Use get_stylesheet_directory() rather than STYLESHEETPATH to return the stylesheet path.
  • Avoid hard coding to modify content. Instead, use function parameters, filters and action hooks where appropriate. For example wp_title should be modified using a filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output..
  • Able to have child themes made from them. (Child themeChild theme A Child Theme is a customized theme based upon a Parent Theme. It’s considered best practice to create a child theme if you want to modify the CSS of your theme. https://developer.wordpress.org/themes/advanced-topics/child-themes/. ready)
  • Include comments_template().
  • The theme tags and description must match the what the theme actually does in respect to functionality and design.
  • Properly implement core template tags and action/filter hooks, when used.

Presentation vs. Functionality

  • Since the purpose of Themes is to define the presentation of user content, Themes must not be used to define the generation of user content, or to define Theme-independent site options or functionality.

Documentation

  • Any custom features, options or any limitations (for example menu restrictions), should be explained. Enough documentation should be provided.

Favicons

  • If implemented, disable favicons by default and have the ability for users to override.

Language

  • All theme text strings are to be translatable.
  • Include a text domain in style.css
  • Use a single unique theme slug – as the theme slug appears in style.css. If it uses a framework then no more than 2 unique slugs.
  • Can use any language for text, but only use the same one for all text.

Licensing

  • Be 100% GPLGPL GPL is an acronym for GNU Public License. It is the standard license WordPress uses for Open Source licensing https://wordpress.org/about/license/. The GPL is a ‘copyleft’ license https://www.gnu.org/licenses/copyleft.en.html. This means that derivative work can only be distributed under the same license terms. This is in distinction to permissive free software licenses, of which the BSD license and the MIT License are widely used examples. and/or 100% GPL-compatible licensed.
  • Declare copyright and license explicitly. Use the license and license uri headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. slugs to style.css.
  • Declare licenses of any resources included such as fonts or images.
  • All code and design should be your own or legally yours. Cloning of designs is not acceptable.

Naming

  • Theme names must not use: WordPress, Theme.
  • Spell “WordPress” correctly in all public facing text: all one word, with both an uppercase W and P.

Options and Settings

  • Save options in a single array.
  • Use sane defaults and don’t write default setting values to the database.
  • Use edit_theme_options capability for determining user permission to edit options, rather than rely on a role (e.g. “administrator”), or a different capability (e.g. “edit_themes”, “manage_options”).
  • Use the CustomizerCustomizer Tool built into WordPress core that hooks into most modern themes. You can use it to preview and modify many of your site’s appearance settings. for implementing theme options.

Plugins

  • Don’t include any plugins. A theme can recommend plugins but not include those plugins in the theme code.
  • Don’t do things in a theme considered plugin territory.

Screenshot

  • The Screenshot should be of the actual theme as it appears with default options, not a logo or mockup.
  • The screenshot should be no bigger than 1200 x 900px.

Security and Privacy

  • Don’t phone home without informed user consent. Find out more about security here.
  • Make any collection of user data “opt-in” only and have a theme option that is set to disabled by default. Validate and sanitize untrusted data before entering into the database. All untrusted data should be escaped before output. (See: Data Validation.)
  • No URLURL A specific web address of a website or web page on the Internet, such as a website’s URL www.wordpress.org shorteners used in the theme.
  • Use esc_attr() for text inputs and esc_textarea() for textareas.

Selling, credits and links

  • If you are a theme shop you should be selling under GPL to be in the WordPress.org repo.
  • If the theme adds a footer credit link, there should only be one (link to WordPress does not count)

Stylesheets and Scripts

  • No hard coding of scripts, styles and Favicons unless a browser workaround script. Everything should be enqueued.
  • No analytics or tracking.
  • No minification of scripts or files unless provide original files.
  • Required to use core-bundled scripts rather than including their own version of that script. For example jQuery.
  • Include all scripts and resources it uses rather than hot-linking. The exception to this is Google libraries.
  • If a tag is used in style.css the theme should support that feature or adhere to what that tag stands for. For example custom background or header.

Templates

  • If using templates, custom template files should be called using get_template_part() or locate_template().
  • There are several template specific things you should consider when certain ones are being used.
  • Use *_url() template tags, rather than bloginfo() equivalents.
  • Template files must follow the template hierarchy.

Obsolescence

  • Themes are required to be kept current once accepted in the Theme directory.

It’s worth noting we are working to automate a lot of the above requirements.

Along with the required items, you should also consider the recommended items. The recommended items are there to make sure your theme is the best it can be and good advice to include as best practice.

I am formally proposing that the text in red-italic be restored to the Theme Review Requirements. Please discuss in the comments.