I've made a plugin to import products to Wordpress with Woocommerce. It works fine except the product attributes - I can't find a way to import them properly.

The issue is that with products I add custom taxonomies which are not defined in the dashboard. Surprisingly I can't find a method for this. I tried wp_insert_term() with register_taxonomy(), but these don't add anything to the wp_woocommerce_attribute_taxonomies table in the database and I don't find them in the product's attributes neither on the product attributes page in the dashboard. I only found them in wp_terms table in the database and from what I learned it's not where product attributes belong in Woocommerce.

update_post_meta() also doesn't seem to work (it can add attributes as meta, but I need them as Woocommerce's product attributes).

I need to create attributes if they don't exist before I import the products. Is there a function to do this that I overlooked?

Not sure if I should post what I have so far, but here is the code related to the issue:

// This is an example of what I get from my AJAX input
$product_attributes = array(
    "attr_id_01" => array(
        "name" => "Material",
        "value" => "Metal",
        "is_visible" => 1,
        "is_taxonomy" => 1
    "attr_id_02" => array(
        "name" => "Type",
        "value" => "Has a handle",
        "is_visible" => 1,
        "is_taxonomy" => 1
foreach ($product_attributes_copy as $key => $value) {
    // sanitize_title filter is provided by CyrToLat plugin,
    // it basically makes the string url-friendly, 
    // it's used because names and values could contain Cyrillic, uppercase and spaces
    $filtered_name = apply_filters('sanitize_title', $value['name']);
    $filtered_value = apply_filters('sanitize_title', $value['value']);

    $taxonomy = 'pa_' . $filtered_name;
    $parent_term = term_exists( $filtered_value, $taxonomy );
    $parent_term_id = $parent_term['term_id'];

    if ( ! taxonomy_exists($taxonomy) ) {
        register_taxonomy($taxonomy, 'product', array('label' => $value['name']) );

    // No errors from the following
    $insert_result = wp_insert_term(
            'description'=> '',
            'slug' => $filtered_value,
            'parent'=> $parent_term_id

1 Answer 1


For some reason, Woocommerce doesn't seem to want you to do this. Not sure why, because requiring it to be done manually is a problem for scalability in a number of cases (not to mention, if you have attributes with a lot of possible values, the interface they provide loads really slowly). After digging a bit, here's the private functions that the admin pages use, modified for systemic call.

function process_add_attribute($attribute)
    global $wpdb;
//      check_admin_referer( 'woocommerce-add-new_attribute' );

    if (empty($attribute['attribute_type'])) { $attribute['attribute_type'] = 'text';}
    if (empty($attribute['attribute_orderby'])) { $attribute['attribute_orderby'] = 'menu_order';}
    if (empty($attribute['attribute_public'])) { $attribute['attribute_public'] = 0;}

    if ( empty( $attribute['attribute_name'] ) || empty( $attribute['attribute_label'] ) ) {
            return new WP_Error( 'error', __( 'Please, provide an attribute name and slug.', 'woocommerce' ) );
    } elseif ( ( $valid_attribute_name = valid_attribute_name( $attribute['attribute_name'] ) ) && is_wp_error( $valid_attribute_name ) ) {
            return $valid_attribute_name;
    } elseif ( taxonomy_exists( wc_attribute_taxonomy_name( $attribute['attribute_name'] ) ) ) {
            return new WP_Error( 'error', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), sanitize_title( $attribute['attribute_name'] ) ) );

    $wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute );

    do_action( 'woocommerce_attribute_added', $wpdb->insert_id, $attribute );

    delete_transient( 'wc_attribute_taxonomies' );

    return true;

function valid_attribute_name( $attribute_name ) {
    if ( strlen( $attribute_name ) >= 28 ) {
            return new WP_Error( 'error', sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), sanitize_title( $attribute_name ) ) );
    } elseif ( wc_check_if_attribute_name_is_reserved( $attribute_name ) ) {
            return new WP_Error( 'error', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), sanitize_title( $attribute_name ) ) );

    return true;

Called as follows:

$insert = proccess_add_attribute(array('attribute_name' => 'my-new-slug', 'attribute_label' => 'my-new-attribute', 'attribute_type' => 'text', 'attribute_orderby' => 'menu_order', 'attribute_public' => false));
if (is_wp_error($insert)) { do_something_for_error($insert); }

The fields associated with the attribute are the name (standard wp slug), label (human readable version of the attribute name), type (you have a choice between 'select' and 'text', but if you're creating systemically, you probably want text I modified the function to default to it), public (labeled as "Enable Archives" in the interface, I made the function default to the interface default), and orderby (choices are 'menu_order' (refered to as 'Custom ordering' in the interface), 'name' (self explanitory), 'name_num' (numeric name), and 'id' (term id))

  • I solved it differently, but didn't post the solution here because it's not generic enough and also not as beautiful as I'd like it to be (but it seems that there's no beautiful solution for this with Woocommerce...). I think your code should work too, so I'll mark it as the answer. And thanks for the effort with explanation! Nov 19, 2016 at 12:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.