I am having trouble to find a way for how to add categories for all the products.

I have Apple, Samsung, Sony, Xiaomi, Huawei categories and each of there needs subcategories which will stand for models, and also specific part.

example of category tree:
Apple>Iphone 6s>Iphone 6s battery
Apple>Iphone 6s>Iphone 6s replacement screen>
Apple>Iphone 8>Iphone 8 battery

All the products needs to be in separated category so I can show correct item under correct menu item and no other Iphone stuff, only the battery for example.

How do I create 500+ categories which will represent all the items we have got?

I was thinking to export existing database and edit in text editor and write all the categories there as it would be simpler then using category manager inside WordPress cpanel which is very bad for this, as I am getting lost of what is in there and what isn't.

Do you think it will work this way? Or what do you suggest? Thanks!


You can try using BulkPress. It allows you to create hundreds of categories very easily. Follow these steps:

  1. Install and activate the plugin.
  2. In the left side bar, hover your cursor on Bulkpress. Then click on Terms.
  3. A new page will appear. In Taxonomy field, choose Category.
  4. Insert your Categories in the Terms field.
  5. Choose your desired Parent, if required.
  6. Finally, click on the Add Terms button.
| improve this answer | |

Wouldn't it be handy if we could write our category hierarchy that we wish to import as:

apple|Apple|Apple products
 iphone-6s|Iphone 6s|Apple Iphone 6s
  iphone-6s-battery|Iphone 6s battery|Apple Iphone 6s battery
  iphone-8-battery|Iphone 8 battery|Apple Iphone 8 battery

with each row as:

term slug|term name|term description

and the hierarchy defined by the indentation.

Example #1

An example using a space for indentation and | as a column delimiter:

$import = '
a1|A1|Term description
 a11|A11|Term description
  a111|A111|Term description
  a112|A112|Term description
 a12|A12|Term description

wpse324129_bulk_terms_importer( $import, 'category', '|', PHP_EOL );

that would import the terms as:

Import example #1

Example #2

Another example using a tab for indentation and comma as a column delimiter:

$import = '
a1,A1,"Term description, including column delimiter"
    a11,A11, "Term description, including column delimiter"
        a111,A111,"Term description, including column delimiter"
        a112,A112,"Term description, including column delimiter"
    a12,A12,"Term description, including column delimiter"

wpse324129_bulk_terms_importer( $import, 'category', ',', PHP_EOL );

importing terms as:

Import examle #2


Here's a first draft for such a function to bulk import terms from a string:

  * Bulk Term Importer
  * Bulk import terms with a given hierarchy, defined by indentation (tab or space).
  * @version 0.1.3
  * @see https://wordpress.stackexchange.com/a/324157/26350
  * @param string $import        Terms to import
  * @param string $tax           Taxonomy. Default 'category'.
  * @param string $col_delimiter Column delimiter. Default '|'.
  * @param string $row_delimiter Row delimiter. Default PHP_EOL.
function wpse324129_bulk_term_importer( $import, $tax = 'category', $col_delimiter = '|', $row_delimiter = PHP_EOL ) {
    $rows         = explode( $row_delimiter, trim( $import ) );
    $level        = 0;
    $prev_term_id = 0;
    $ancestors    = new \SplStack(); // Last-In First-Out.

    foreach( $rows as $row ) {
        $cols = str_getcsv( $row, $col_delimiter );
        if ( 3 !== count( $cols ) ) {
            throw new Exception( __( 'Incorrect number of columns', 'wpse' ) );
        $term_slug = $cols[0];
        $term_name = $cols[1];
        $term_desc = $cols[2];

        // Hierarchy traversal level (non negative).
        $level = strlen( $term_slug ) - strlen( ltrim( $term_slug ) );

        // Push the previous term to the ancestors stack if we go inner (right).
        if ( $level > $ancestors->count() ) {
            $ancestors->push( $prev_term_id );
        } // After: level === ancestors count

        // Reduce the ancestors' stack when we go outer (left).
        while ( $level < $ancestors->count() ) {
        } // After: level === ancestors count

        // Arguments for term creation.
        $args = [
            'description' => $term_desc,
            'slug'        => $term_slug,

        // Check parent term and add to the term creation arguments if needed.
        if ( $prev_term_id > 0 && $ancestors->count() > 0 ) {
            $parent_id   = $ancestors->top(); // The parent is the one on the top.
            $parent_term = get_term_by( 'term_id', $parent_id, $tax, ARRAY_A );
            if ( isset( $parent_term['term_id'] ) ) {
                $args['parent'] = $parent_term['term_id'];

        // Check if current term slug exists and insert if needed.
        $term = get_term_by( 'slug', $term_slug, $tax, ARRAY_A );   
        if ( ! isset( $term['term_id'] ) ) {    
            $result = wp_insert_term( $term_name, $tax, $args );
            if ( is_wp_error( $result ) ) {
                throw new Exception( __( 'Could not insert term!', 'wpse' ) );
            $prev_term_id = $result['term_id'];
        } else {
            $prev_term_id = $term['term_id'];

When we insert a term, we need to know about it's parent. So we need to collect the direct term ancestors when we traverse the hierarchy. The stack is a suitable data structure, where the last item in is the first item out (LIFO). In PHP 5.3+ we can use SplStack that already implements methods like pop(), push(), top() and count().

By comparing the current level to the number of current ancestors, we can determine if we are going inner (right) or outer (left) and adjust the stack accordingly. Reducing the stack when we go left and push to the stack when we go right.

For large import, one could run it through the wp-cli to avoid timeout.

Hope you can extend this further to your needs, e.g. with a format validator and what to do with existing terms (we currently leave them unaffected here).

Please backup before testing!

| improve this answer | |
  • ps: alternative format could be to use numbers instead of indentation (thanks Egill for the suggestion) – birgire Dec 30 '18 at 12:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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