Magento Module: Hide Empty Categories

There are a number of ways that you can hide empty categories: You can disable them manually, you can modify the template output to check the product count and if zero don’t display it, etc. Another way is to write a module and intercept the catalog collection output before it’s sent to the templates.

I could easily package this up and put it up on Magento Connect, but I’m more into trying to help people understand Magento modules, so below is a walkthrough of how to create a module that will provide this functionality.

Note: Update – Fixed for Flat Categories too. This has only been tested on 1.6.x.

Step 1

Create the file ‘/app/etc/modules/Prattski_HideEmptyCategories.xml’. This file tells Magento that your module exists, if it’s enabled or not, and where to find it.

<?xml version="1.0"?>
<!--
/**
 * Hide Empty Categories
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 * @copyright   Copyright (c) 2011 Prattski (http://prattski.com/)
 * @author      Josh Pratt (Prattski)
 */
-->
<config>
    <modules>
        <Prattski_HideEmptyCategories>
            <active>true</active>
            <codePool>local</codePool>
        </Prattski_HideEmptyCategories>
    </modules>
</config>

Step 2

Now we create our module directory. Create ‘/app/code/local/Prattski/HideEmptyCategories/’. This is where our module will reside. Inside that directory, create this file: ‘etc/config.xml’. This file does a number of things: It tells Magento which version your module is, defines your model namespace and directory, and creates and event observer to observe every time a category collection is loaded.

<?xml version="1.0"?>
<!--
/**
 * Hide Empty Categories
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 * @copyright   Copyright (c) 2011 Prattski (http://prattski.com/)
 * @author      Josh Pratt (Prattski)
 */
-->
<config>
    <modules>
        <Prattski_HideEmptyCategories>
            <version>1.0.0</version>
        </Prattski_HideEmptyCategories>
    </modules>
    <global>
        <models>
            <hideemptycategories>
                <class>Prattski_HideEmptyCategories_Model</class>
            </hideemptycategories>
            <catalog_resource>
                <rewrite>
                    <category_flat>Prattski_HideEmptyCategories_Model_Catalog_Resource_Category_Flat</category_flat>
                </rewrite>
            </catalog_resource>
        </models>
    </global>
    <frontend>
        <events>
            <catalog_category_collection_load_after>
                <observers>
                    <hideemptycategories>
                        <type>singleton</type>
                        <class>hideemptycategories/observer</class>
                        <method>catalogCategoryCollectionLoadAfter</method>
                    </hideemptycategories>
                </observers>
            </catalog_category_collection_load_after>
        </events>
    </frontend>
</config>

Step 3

Let’s create our observer model. As you can see in the file above, we are going to have the ‘catalogCategoryCollectionLoadAfter’ method run in this file when it observes the ‘catalog_category_collection_load_after’ event. Create this file: Model/Observer.php

<?php
/**
 * Hide Empty Categories
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 * @copyright   Copyright (c) 2011 Prattski (http://prattski.com/)
 * @author      Josh Pratt (Prattski)
 */
 
/**
 * Event Observer
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 */
class Prattski_HideEmptyCategories_Model_Observer extends Mage_Core_Model_Abstract
{
    /**
     * Remove hidden caegories from the collection
     *
     * @param Varien_Event_Observer $observer
     */
    public function catalogCategoryCollectionLoadAfter($observer)
    {
        if ($this->_isApiRequest()) return;
        $collection = $observer->getEvent()->getCategoryCollection();
        $this->_removeHiddenCollectionItems($collection);
    }
 
    /**
     * Remove hidden items from a product or category collection
     * 
     * @param Mage_Eav_Model_Entity_Collection_Abstract|Mage_Core_Model_Mysql4_Collection_Abstract $collection
     */
    public function _removeHiddenCollectionItems($collection)
    {
        // Loop through each category or product
        foreach ($collection as $key => $item)
        {
            // If it is a category
            if ($item->getEntityTypeId() == 3) {
 
                if ($item->getProductCount() < 1) {
                    $collection->removeItemByKey($key);
                }
            }
        }
    }
 
    /**
     * Return true if the reqest is made via the api
     *
     * @return boolean
     */
    protected function _isApiRequest()
    {
        return Mage::app()->getRequest()->getModuleName() === 'api';
    }
}

Step 4

Magento has the ability to turn on Flat Categories to speed up the performance whenever categories are loaded. The event that we are observing above does not work when Flat Categories are enabled. So, thanks to Vinai’s help, we need to rewrite a method in a core file to make this work. Create this file: Model/Catalog/Resource/Category/Flat.php

<?php
/**
 * Hide Empty Categories
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 * @copyright   Copyright (c) 2011 Prattski (http://prattski.com/)
 * @author      Josh Pratt (Prattski)
 */
 
/**
 * Event Observer
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 */
class Prattski_HideEmptyCategories_Model_Catalog_Resource_Category_Flat
    extends Mage_Catalog_Model_Resource_Category_Flat
{
    /**
     * Load nodes by parent id
     *
     * @param unknown_type $parentNode
     * @param integer $recursionLevel
     * @param integer $storeId
     * @return Mage_Catalog_Model_Resource_Category_Flat
     */
    protected function _loadNodes($parentNode = null, $recursionLevel = 0, $storeId = 0)
    {
        $nodes = parent::_loadNodes($parentNode, $recursionLevel, $storeId);
 
        foreach ($nodes as $node) {
            if ($node->getProductCount() == 0) {
                unset($nodes[$node->getId()]);
            }
        }
        return $nodes;
    }
}

Step 5

To recap all the files we have made:

  • /app/etc/modules/Prattski_HideEmptyCategories.xml
  • /app/code/local/Prattski/HideEmptyCategories/etc/config.xml
  • /app/code/local/Prattski/HideEmptyCategories/Model/Observer.php
  • /app/code/local/Prattski/HideEmptyCategories/Model/Catalog/Resource/Category/Flat.php

Test it out! You’ll be able to see if Magento recognizes your module by going to System > Configuration > Advanced. If you see the module in the list, Magento knows it’s there. You’ll have to have some categories in your system, some with products in them, some without, to see if it’s working properly on the frontend.

I hope you enjoyed learning how to build this module. If you find any issues or bugs, please let me know.

This entry was posted in Magento. Bookmark the permalink.

48 Responses to Magento Module: Hide Empty Categories

  1. itza says:

    will this work on magento 1.9?

  2. Clint says:

    Works great in 1.8.1. Thanks!

  3. Penina says:

    Works great!

    I am getting the following error in system.log

    ERR (3): Strict Notice: Declaration of Prattski_HideEmptyCategories_Model_Catalog_Resource_Category_Flat::_loadNodes() should be compatible with that of Mage_Catalog_Model_Resource_Category_Flat::_loadNodes() in /var/www/app/code/local/Prattski/HideEmptyCategories/Model/Catalog/Resource/Category/Flat.php on line 19

    Why is that?

    • Kamik says:

      in Flat.php function declaration should be
      protected function _loadNodes($parentNode = null, $recursionLevel = 0, $storeId = 0, $onlyActive = true)

  4. Penina says:

    Works great!

    The only problem is, when I go to my Index Management page, it gives me the following error: Fatal error: Call to a member function isBuilt() on a non-object in Flat.php on line 80.

    And in system.log, I see the following:
    2014-04-25T15:08:37+00:00 ERR (3): Warning: include(Prattski/HideEmptyCategories/Model/Catalog/Resource/Category/Flat.php): failed to open stream: No such file or directory in /var/www/lib/Varien/Autoload.php on line 93
    2014-04-25T15:08:37+00:00 ERR (3): Warning: include(): Failed opening ‘Prattski/HideEmptyCategories/Model/Catalog/Resource/Category/Flat.php’ for inclusion (include_path=’/var/www/app/code/local:/var/www/app/code/community:/var/www/app/code/core:/var/www/lib:.:/usr/share/php:/usr/share/pear’) in /var/www/lib/Varien/Autoload.php on line 93

    Help please!

    • Penina says:

      Never mind, I turned enables flat categories and added the code for it in step 4 and it works!

  5. Pingback: Ask Experts » Magento – How to hide categories and sub-categories with no products

  6. pgilbert says:

    what happened to System->Configuration->Catalog??
    It is totally blank!

  7. Prathibha says:

    Hello,

    Thanks for posting this article. I am working on Magento CE 1.7.0.2. We have multiple categories like Main Cat/SubCat1/subcat2/…./productlist. When the product list is deleted, the sub category is not hidden. I didn’t have any errors/exceptions and followed the steps mentioned.

    Any help is greatly appreciated!

    Thanks in advance
    Prathibha

  8. Eppie says:

    Hi – thanks so much for this module. I had it running locally on MAMP and it was working great. However, when I try to make it work on the live server – I get the following error: Fatal error: Call to a member function setStoreId() on a non-object in /home//public_html/app/code/core/Mage/Catalog/Model/Category.php on line 410.

    Would appreciate any help you could provide. I’m not what you’d call an experienced programmer!

    Thanks,
    Ciara

  9. rodrigo says:

    is there any script or extension to delete all empty cat/subcat’s ???

  10. Jörg Märtin says:

    Hey. Thanks for the module. It is working perfectly, only thing is, if a category has items not in stock and invisible, the category is still shown. Is there a way to make it work, if a category has items not in stock? Thanks.

  11. Pankaj Sharma says:

    Thanks a Lot dear… Along with this you also helped me understanding how to create observer… Thanks

  12. Axel Freudiger says:

    Are you sure to use getProductCount() on $item? Cause I’m pretty sure this will call out another select from database which can get incredibly slow.
    I’d rather do a sql count on the original sql to get the product counts.

  13. Hamid says:

    For everyone like me that struggled for the module to get it to show categories which had products in theh child categories, heres the solution.

    In theory replacing getProductCount() with getProductCollection()->getSize(); should do the trick but this still only returns the product count for teh parent category.

    So to make it work, change the middle selection to:

    // Loop through each category or product
    foreach ($collection as $key => $item)
    {
    // If it is a category
    if ($item->getEntityTypeId() == 3) {

    /* $prodcount = $item->getProductCount(); */

    $products = Mage::getModel(‘catalog/category’)->load($item->getId())
    ->getProductCollection()
    ->addAttributeToSelect(‘entity_id’)
    ->addAttributeToFilter(‘status’, 1)
    ->addAttributeToFilter(‘visibility’, 4);

    $prodcount = $products->count();

    if ($prodcount removeItemByKey($key);
    }
    }

    This works on 1.7.0.2. Should work with other versions with no or little modification on the standard category mode (not checked or tried with flat).

    • Tom says:

      Hamid, nice. To make it work for flat category mode, use the following code.

      foreach ($nodes as $node):

      $category = Mage::getModel(‘catalog/category’)->load($node->getId());

      if($category->getDisplayMode() == Mage_Catalog_Model_Category::DM_PRODUCT):

      $products = $category->getProductCollection()
      ->addAttributeToSelect(‘entity_id’)
      ->addAttributeToFilter(‘status’, Mage_Catalog_Model_Product_Status::STATUS_ENABLED)
      ->addAttributeToFilter(‘visibility’, Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH)
      ->load();
      if($products->count() == 0):
      unset($nodes[$node->getId()]);
      endif;

      endif;

      endforeach;

      I’ve added a check to make sure the category display is in ‘products’ mode.

    • Mo says:

      Hamid – I have the same version installed and am facing the same issue as you, your code wont load, do you have a syntax error?

    • Stefan says:

      You missed something in your code snippet, this corrections works like a charm:

      public function _removeHiddenCollectionItems($collection)
      {
      // Loop through each category or product
      foreach ($collection as $key => $item)
      {
      // If it is a category
      if ($item->getEntityTypeId() == 3) {

      $products = Mage::getModel('catalog/category')->load($item->getId())
      ->getProductCollection()
      ->addAttributeToSelect('entity_id')
      ->addAttributeToFilter('status', 1)
      ->addAttributeToFilter('visibility', 4);

      $prodcount = $products->count();

      if (!$prodcount){
      $collection->removeItemByKey($key);
      }
      }
      }

      }

  14. M says:

    Hi if I want to delete the categories instead of hiding then please suggest some idea.

  15. Mukesh says:

    Hi Josh

    This is very helpful post.I am just a magento beginner.Could you please help me?
    I want to implement some thing that in our magento we may have many categories.
    There may be many nested categories.I want to delete those categories which does not have any product in them.
    If a parent category does not have any product,but the child category have some products then parent category should not be deleted.

    I am using Magento 1.7.0.2.
    Thanks.

  16. M. A. says:

    I’m using Magento 1.7.0.2. I followed this tutorial and created the extension. It shows on the backend but I am still seeing the empty categories on the front end.

    I have logged out, then back in cleared cache and re-indexed and still no joy.

  17. Patrick Mackessy says:

    I left a previous comment but it seems to have been deleted. Could you let me know why?

    • Josh Pratt says:

      @Patrick – I can’t remember. I get loads of posts with tons of code, or advertisements, etc. If it was deleted, it was probably because of something like that.

  18. Patrick says:

    We have created your module as instructed but can’t seem to get it to work. The module appears in configuration/advanced but categories with no products are still appearing.

    We are using a custom theme so would this need to be put somewhere else within our theme?

    Can this also be modified to only count products that are enabled and have a stock qty?

    Any help would be greatly appreciated.

    Thanks

    Patrick

    • marlize says:

      Hey there, just implemented this in magento 1.5.1 and empty categories are still displaying. Please advise?

    • CMcM says:

      Hi, thanks so much – fantastic addition. I too am using a theme (Ultimo) and trying to figure out how to apply. It’s working great everywhere except the main (theme) horizontal navigation. If anyone knows this, would appreciate the head start! Thanks. C

  19. Patrice Bois says:

    Hi Josh, if i have multiple stores in my magento, if one store have a product in category but not the other store, the category will be displayed…

  20. vegetaaa says:

    Tried this module but i can’t seem to get it to work. I’ve tested it on 1.6.1.0 and 1.6.2.0 but no luck. When i try to open a category on the frontend i get this error: Mage registry key “_singleton/hideemptycategories/observer” already exists

    Also the system –> configuration –> catalog in the backend is not appearing anymore.

    Anybody have an idea what i’m doing wrong?

  21. gizmo says:

    I’m not having any luck with this at all. I’ve created the files as instructed. The module shows in ADMIN: System > Advanced and it is enabled…. but empty categories are still showing in my menus.

    I’ve turned flat files on and off, re-indexed caches, enabled/disabled/moved products.

    There is no effect, no matter what I do.

  22. Hamid says:

    Hi Josh,

    Very useful module but it doesnt seem to work for me if the product belongs to a subsubcat or subsubsubcat. It seems to work OK if I assign the parent subcat to the actual product itself and then it picks it up otherwise it doesnt seem to recognise there are products lower down then subcats.

    I have checked anchor setttings etc, and am using magento CE 1.6.1

    Any ideas?

  23. Ian says:

    Dont worry, my mistake forgot to upload the Observer.php code, lol

  24. Ian says:

    i get this error

    Mage registry key “_singleton/hideemptycategories/observer” already exists

    Using 1.6.0.0

  25. Alias says:

    You can add items not visible?
    Thanks

  26. martijn says:

    getproductcount() seems to count products, even if they are not set as visible. is there a way to fix that?

  27. Hey Josh, good sharing. It helped me a lot. Keep sharing such posts.

  28. martin says:

    wow, this is simply brilliant!
    works perfect on 1.5.1

  29. amadex says:

    Hi! thank you for this extension! Nevertheless, the categories are hidden if they don’t directly have products, despite their subcategories having products… Mage 1.6, with no flat categories active.

    Tkx

  30. imran says:

    its working now because i have enabled flat categories and re-added product! but after install it if there are some product already then all product need to be updated manually and save again. can u solve this bug? Thanks

    • Josh Pratt says:

      imran – I’m not quite sure there is even a bug. I’m not even quite sure what you are trying to explain, and i’ve tried to reproduce your issue, but it always works fine for me…

  31. imran says:

    its doesnt work for me. if i use flat categry yes then full menu become high even there is products!! and if i use flat category NO then all categories show. even there is no products, can you help? thanks

  32. Vinai says:

    Hi Josh,

    very cool post, thank you. Can you expand to make it work when the flat category tables are turned on?
    Thank you!