1. Documentation /
  2. WooCommerce Plugin Developer Handbook

WooCommerce Plugin Developer Handbook

Want to create a plugin to extend WooCommerce? WooCommerce plugins are the same as regular WordPress plugins. For more information, visit Writing a plugin.

Your WooCommerce extension should:

  • Adhere to all WordPress plugin coding standards and best practice guidelines for harmonious existence within WordPress and alongside other WordPress plugins.
  • Have a single core purpose and use WooCommerce features as much as possible.
  • Not do anything malicious or underhanded — for example, inserting spam links or upselling services outside of the WooCommerce.com ecosystem.
  • Not subvert or override Marketplace connections in core — for example, extensions cannot create branded top-level menu items or introduce their own telemetry.
  • Adhere to Marketplace compatibility and interoperability guidelines.

Merchants make use of WooCommerce extensions daily, and should have a unified and pleasant experience while doing so without advertising invading their WP Admin or store.

Note: We provide this page as a best practice for developers.

Note: We are unable to provide support for custom code under our Support Policy. If you are unfamiliar with code and resolving potential conflicts, select a WooExpert or Developer  for assistance.

Check if WooCommerce is active

↑ Back to top

Most WooCommerce plugins do not need to run unless WooCommerce is already active. You can wrap your plugin in a check to see if WooCommerce is installed:

// Test to see if WooCommerce is active (including network activated).
$plugin_path = trailingslashit( WP_PLUGIN_DIR ) . 'woocommerce/woocommerce.php';

if (
    in_array( $plugin_path, wp_get_active_and_valid_plugins() )
    || in_array( $plugin_path, wp_get_active_network_plugins() )
) {
    // Custom code here. WooCommerce is active, however it has not 
    // necessarily initialized (when that is important, consider
    // using the `woocommerce_init` action).

Note that this check will fail if the WC plugin folder is named anything other than woocommerce.

Main file naming

↑ Back to top

The main plugin file should adopt the name of the plugin, e.g., A plugin with the directory name plugin-name would have its main file named plugin-name.php.

Text domains

↑ Back to top

Follow guidelines for Internationalization for WordPress Developers, the text domain should match your plugin directory name, e.g., A plugin with a directory name of plugin-name would have the text domain plugin-name. Do not use underscores.


↑ Back to top

All text strings within the plugin code should be in English. This is the WordPress default locale, and English should always be the first language. If your plugin is intended for a specific market (e.g., Spain or Italy), include appropriate translation files for those languages within your plugin package. Learn more at Using Makepot to translate your plugin.

Follow WordPress PHP Guidelines

↑ Back to top

WordPress has a set of guidelines to keep all WordPress code consistent and easy to read. This includes quotes, indentation, brace style, shorthand php tags, yoda conditions, naming conventions, and more. Please review the guidelines.

Code conventions also prevent basic mistakes, as Apple made with iOS 7.0.6.

Custom Database Tables & Data Storage

↑ Back to top

Avoid creating custom database tables. Whenever possible, use WordPress post types, taxonomies, and options.

Consider the permanence of your data. Here’s a quick primer:

  • If the data may not always be present (i.e., it expires), use a transient.
  • If the data is persistent but not always present, consider using the WP Cache.
  • If the data is persistent and always present, consider the wp_options table.
  • If the data type is an entity with n units, consider a post type.
  • If the data is a means or sorting/categorizing an entity, consider a taxonomy.

Logs should be written to a file using the WC_Logger class.

Prevent Data Leaks

↑ Back to top

Try to prevent direct access data leaks. Add this line of code after the opening PHP tag in each PHP file:

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly


↑ Back to top

All plugins need a standard WordPress readme.

Your readme might look something like this:

=== Plugin Name ===
Contributors: (this should be a list of wordpress.org userid's)
Tags: comments, spam
Requires at least: 4.0.1
Tested up to: 4.3
Requires PHP: 5.6
Stable tag: 4.3
License: GPLv3 or later License
URI: http://www.gnu.org/licenses/gpl-3.0.html


↑ Back to top

All plugins need a standard changelog.txt file.

Your changelog.txt file should look like this:

*** WooCommerce Extension Name Changelog ***

YYYY-MM-DD - version 1.1.0
* Added - Useful new feature
* Fixed - Important bug fix

YYYY-MM-DD - version 1.0.1
* Fixed a bug

YYYY-MM-DD - version 1.0.0
* Initial release

Changelog Entry Types

↑ Back to top

To showcase the different types of work done in a product update, use any of the following words to denote what type of change each line is:

The changelog for WooCommerce Bookings, as shown on WooCommerce.com.

Plugin Author Name

↑ Back to top

Consistency is important to us and our customers. Products offered through WooCommerce.com should provide a consistent experience for all aspects of the product, including finding information on who to contact with queries.

Customers should be able to easily differentiate a product purchased at WooCommerce.com from a product purchased elsewhere, just by looking through their plugin list in WordPress.

Thus, the following plugin headers should be in place:

  • The Plugin Author isYourName/YourCompany
  • The Developer header is YourName/YourCompany, with the Developer URI field listed as http://yourdomain.com/

For example:

 * Plugin Name: WooCommerce Extension
 * Plugin URI: https://woocommerce.com/products/woocommerce-extension/
 * Description: Your extension's description text.
 * Version: 1.0.0
 * Author: Your Name
 * Author URI: http://yourdomain.com/
 * Developer: Your Name
 * Developer URI: http://yourdomain.com/
 * Text Domain: woocommerce-extension
 * Domain Path: /languages
 * Woo: 12345:342928dfsfhsf8429842374wdf4234sfd
 * WC requires at least: 2.2
 * WC tested up to: 2.3
 * License: GNU General Public License v3.0
 * License URI: http://www.gnu.org/licenses/gpl-3.0.html

Declaring required and supported WooCommerce version

↑ Back to top

Use the follow headers to declare “required” and “tested up to” versions:

  • WC requires at least
  • WC tested up to

Plugin URI

↑ Back to top

Ensure that the Plugin URI line of the above plugin header is provided. This line should contain the URL of the plugin’s product/sale page on WooCommerce.com (if sold by WooCommerce) or to a dedicated page for the plugin on your website.

Woo Plugin Header For Updates

↑ Back to top

WooCommerce core looks for a Woo line in the plugin header comment, to ensure it can check for updates to your plugin, on WooCommerce.com. This line looks like this:

Woo: 12345:342928dfsfhsf8429842374wdf4234sfd

This is only required for products sold on WooCommerce.com. Make sure you do not use this for your product versions listed on WordPress.org or elsewhere.

For products sold on WooCommerce.com, Vendors can find this snippet by logging in to their logging in to the Vendors Dashboard and going to Products > All Products. Then, select the product and click Edit product page. This snippet will be in the upper-right-hand corner of the screen.

See the plugin header comment example above for how the Woo header looks in context.

Make it Extensible

↑ Back to top

Developers should use WordPress actions and filters to allow for modification/customization without requiring users to touch the plugin’s core code base.

If your plugin creates a front-end output, we recommend to having a templating engine in place so users can create custom template files in their theme’s WooCommerce folder to overwrite the plugin’s template files.

For more information, check out Pippin’s post on Writing Extensible Plugins with Actions and Filters.

Use of External Libraries

↑ Back to top

The use of entire external libraries is typically not suggested as this can open up the product to security vulnerabilities. If an external library is absolutely necessary, developers should be thoughtful about the code used and assume ownership as well as of responsibility for it. Try to  only include the strictly necessary part of the library, or use a WordPress-friendly version or opt to build your own version. For example, if needing to use a text editor such as TinyMCE, we recommend using the WordPress-friendly version, TinyMCE Advanced.

Use of Third-Party Systems

↑ Back to top

Plugins cannot send executable code through third-party systems. Loading code from documented services is allowed, but communication must be secure. Executing outside code within a plugin is not allowed.

For instance, using third-party CDNs for non-service-related JavaScript and CSS is prohibited. Iframes should not be used to connect admin pages.

Remove Unused Code

↑ Back to top

With version control, there’s no reason to leave commented-out code; it’s annoying to scroll through and read. Remove it and add it back later if needed.


↑ Back to top

If you have a function, what does the function do? There should be comments for most if not all functions in your code. Someone/You may want to modify the plugin, and comments are helpful for that. We recommend using PHP Doc Blocks  similar to WooCommerce.

Avoid God Objects

↑ Back to top

God Objects are objects that know or do too much. The point of object-oriented programming is to take a large problem and break it into smaller parts. When functions do too much, it’s hard to follow their logic, making bugs harder to fix. Instead of having massive functions, break them down into smaller pieces.

Test Extension Quality & Security with Quality Insights Tool

↑ Back to top

Integrate the into your development workflow to ensure your extension adheres to WordPress / WooCommerce quality and security standards. The QIT allows the ability to test your extensions against new releases of PHP, WooCommerce, and WordPress, as well as other active extensions, at the same time. Additionally, the toolkit includes a command-line interface (CLI) tool that allows you to run and view tests against development builds. This helps to catch errors before releasing a new version. The following tests are available today:

Test Your Code with WP_DEBUG

↑ Back to top

Always develop with WP_DEBUG mode on, so you can see all PHP warnings sent to the screen. This will flag things like setting a variable before checking the value.

Separate Business Logic & Presentation Logic

↑ Back to top

It’s a good practice to separate business logic (i.e., how the plugin works) from presentation logic (i.e., how it looks). Two separate pieces of logic are more easily maintained and swapped if necessary. An example is to have two different classes — one for displaying the end results, and one for the admin settings page.

Use Transients to Store Offsite Information

↑ Back to top

If you provide a service via an API, it’s best to store that information so future queries can be done faster and the load on your service is lessened. WordPress transients can be used to store data for a certain amount of time.

Logging Data

↑ Back to top

You may want to log data that can be useful for debugging purposes. This is great with two conditions:

  • Allow any logging as an ‘opt in’.
  • Use the WC_Logger class. A user can then view logs on their system status page.

If adding logging to your extension, here’s a snippet for presenting a link to the logs, in a way the extension user can easily make use of.

$label = __( 'Enable Logging', 'your-textdomain-here' );
$description = __( 'Enable the logging of errors.', 'your-textdomain-here' );
if ( defined( 'WC_LOG_DIR' ) ) {
$log_url = add_query_arg( 'tab', 'logs', add_query_arg( 'page', 'wc-status', admin_url( 'admin.php' ) ) );
$log_key = 'your-plugin-slug-here-' . sanitize_file_name( wp_hash( 'your-plugin-slug-here' ) ) . '-log';
$log_url = add_query_arg( 'log_file', $log_key, $log_url );
$label .= ' | ' . sprintf( __( '%1$sView Log%2$s', 'your-textdomain-here' ), '<a href="' . esc_url( $log_url ) . '">', '</a>' );
$form_fields['wc_yourpluginslug_debug'] = array(
'title' => __( 'Debug Log', 'your-textdomain-here' ),
'label' => $label,
'description' => $description,
'type' => 'checkbox',
'default' => 'no'

Error codes

↑ Back to top

This information is intended for the third-party developers so products they write can better handle errors. Error codes are produced by the Product Build Server when uploading a new submission or updating an existing product on the Marketplace.


↑ Back to top

The operation has completed successfully.


↑ Back to top

Internal error with the Product Build Server – cannot initialize the Apache daemon.


↑ Back to top

Internal error with the Product Build Server – cannot initialize the MySQL daemon.


↑ Back to top

Internal error with the Product Build Server – cannot initialize WordPress.


↑ Back to top

Internal error with the Product Build Server – cannot initialize WooCommerce.


↑ Back to top

Internal error with the Product Build Server – cannot configure dependencies.


↑ Back to top

Malware scanning error. This can happen if your product contains malware in the code.

Here’s an example output:

ObfuscatedPhp /tmp/product_clone/woocommerce-example/includes/views/html-settings-page.php
0x406:$ini_set:  ini_set(
0x506:$ini_set:  ini_set(
ObfuscatedPhp /tmp/product_clone/woocommerce-example/includes/views/html-extras-page.php
0x406:$eval:  exec(

This means that the character at the absolute position 0x406 (1030) and 0x506 (1286) in the file html-settings-page.php doesn’t pass the $ini_set rule, because it is using call_user_func in that file. Also, the other file html-extras-page.php doesn’t pass the rule $register_function which is using exec call.

The use of the following actions is not permitted on WooCommerce:

php://stdout php://stderr php://output php://fd php://memory php://temp php://filter


↑ Back to top

Cannot extract the product. Most common issue is the top directory of the zip does not match its slug.


↑ Back to top

phpcs checks failed. The check uses the WooCommerce-Core sniffs with following phpcs.xml:

<?xml version="1.0"?>
<ruleset name="WordPress Coding Standards">
<description>WooCommerce extension PHP_CodeSniffer ruleset.</description>
<!– Exclude paths –>
<!– Configs –>
<config name="minimum_supported_wp_version" value="4.7" />
<config name="testVersion" value="7.2-" />
<!– Rules –>
<rule ref="WordPress-Core">
<!– WPCS 3.0 added these, but we're not yet ready to force them upon third-party vendors: –>
<exclude name="Generic.WhiteSpace.IncrementDecrementSpacing" />
<exclude name="Modernize.FunctionCalls.Dirname" />
<exclude name="PEAR.Files.IncludingFile" />
<exclude name="PSR12.Traits.UseDeclaration" />
<exclude name="PSR2.Classes.ClassDeclaration.CloseBraceAfterBody" />
<exclude name="PSR2.Files.EndFileNewline.TooMany" />
<exclude name="PSR2.Methods.FunctionClosingBrace.SpacingBeforeClose" />
<exclude name="Squiz.Functions.FunctionDeclarationArgumentSpacing" />
<exclude name="Universal.Constants.UppercaseMagicConstants" />
<exclude name="Universal.Namespaces" />
<exclude name="Universal.UseStatements.NoLeadingBackslash" />
<rule ref="WordPress-Extra">
<exclude name="Generic.Commenting.DocComment.SpacingAfter" />
<exclude name="Generic.Files.LineEndings.InvalidEOLChar" />
<exclude name="Generic.Functions.FunctionCallArgumentSpacing.SpaceBeforeComma" />
<exclude name="PEAR.Functions.FunctionCallSignature" />
<exclude name="Squiz.Commenting" />
<exclude name="Squiz.PHP.DisallowSizeFunctionsInLoops.Found" />
<exclude name="Squiz.WhiteSpace" />
<exclude name="WordPress.Arrays" />
<exclude name="WordPress.Files.FileName" />
<exclude name="WordPress.NamingConventions" />
<exclude name="WordPress.Security.EscapeOutput.ErrorNotEscaped" />
<exclude name="WordPress.Security.EscapeOutput.ExceptionNotEscaped" />
<exclude name="WordPress.Security.ValidatedSanitizedInput.MissingUnslash" />
<exclude name="WordPress.WhiteSpace" />
<exclude name="WordPress.WP.I18n.NonSingularStringLiteralText" />
<!– WPCS 3.0 added these, but we're not yet ready to force them upon third-party vendors: –>
<exclude name="Universal.CodeAnalysis.NoEchoSprintf" />
<exclude name="Universal.ControlStructures.DisallowLonelyIf" />
<exclude name="Universal.Files.SeparateFunctionsFromOO" />
<exclude name="WordPress.WP.I18n.EmptyTextDomain" />
<!– WPCS 3.0 added these, replacing WordPress.Arrays.* items which we previously excluded: –>
<exclude name="NormalizedArrays.Arrays.ArrayBraceSpacing" />
<exclude name="NormalizedArrays.Arrays.CommaAfterLast" />
<!– WPCS 3.0 moved some WordPress.WhiteSpace/Squiz.WhiteSpace sniffs into these: –>
<exclude name="Generic.WhiteSpace.LanguageConstructSpacing" />
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration" />
<exclude name="Universal.WhiteSpace" />
<rule ref="WooCommerce-Core">
<exclude name="Core.Commenting.CommentTags.AuthorTag" />
<exclude name="Generic.WhiteSpace.ScopeIndent.Incorrect" />
<exclude name="Universal.Arrays.DisallowShortArraySyntax" />
<exclude name="WooCommerce.Commenting.CommentHooks.HookCommentWrongStyle" />
<exclude name="WooCommerce.Commenting.CommentHooks.MissingHookComment" />
<exclude name="WooCommerce.Commenting.CommentHooks.MissingSinceComment" />
<exclude name="WordPress.PHP.DontExtract" />
<rule ref="PHPCompatibility">
view raw phpcs.xml hosted with ❤ by GitHub

To install locally:

  1. Go to your product directory.
  2. Install WooCommerce sniffs with composer require woocommerce/woocommerce-sniffs.
  3. Put phpcs.xml above in the product directory.
  4. Run ./vendor/bin/phpcs --warning-severity=0 -s --ignore-annotations --extensions=php,html . .


↑ Back to top

Cannot install the product. Most common issue is the top directory of the zip does not match its slug.


↑ Back to top

Cannot activate the product. Refer to the build output for more details.


↑ Back to top

Cannot deactivate the product. Refer to the build output for more details.


↑ Back to top

This error means that your product is incompatible with other products in the host plan. Refer to the build output for more details.


↑ Back to top

This error means that your product is incompatible with other products in the host plan. Refer to the build output for more details.


↑ Back to top

This error means that your product is incompatible with other products in the host plan. Refer to the build output for more details.


↑ Back to top

Your theme is missing the theme info file theme_info.txt under the root directory.


↑ Back to top

Your theme info file theme_info.txt contains malformed data structure. It should contain the product ID, hash and main file, all separated by new lines. For example:



↑ Back to top

Your theme main file contains malformed data structure. Provide Theme Name, Version, and the Woo headers in your main file.


↑ Back to top

Your plugin main file contains malformed data structure. Provide Plugin Name, Version, and the Woo headers in your main file.


↑ Back to top

Your product main file contains an invalid Woo header structure. Use the format ID:HASH. For example:

 * Woo: 390890:911c438934af094c2b38d5560b9f50f3


↑ Back to top

Your product main file contains an invalid product ID in the Woo header.


↑ Back to top

Your product main file contains an invalid hash in the Woo header.


↑ Back to top

Your product is missing the main file.


↑ Back to top

Your product is missing the changelog.txt file.


↑ Back to top

The version in your product’s main file does not match with the provided version in changelog.txt.


↑ Back to top

Your product contains malformed changelog.txt structure. Refer to changelog.txt as an example.