When answering questions I often see outdated information and confusion about overloading adminhtml controllers. So, for the sake of reference, here is the modern way of making a controller.


Say the module name is “Knectar_Example”; start by editing the module’s config, “app/code/local/Knectar/Example/etc/config.xml“:

<config>
    <modules>
        <Knectar_Example>
            <version>1.0.0</version>
        </Knectar_Example>
    </modules>

    <admin>
        <routers>
            <adminhtml>
                <args>
                    <modules>
                        <knectar_example before="Mage_Adminhtml">Knectar_Example_Adminhtml</knectar_example>
                    </modules>
                </args>
            </adminhtml>
        </routers>
    </admin>
</config>

Pay attention to the value “Knectar_Example_Adminhtml”, that will be part of the class name in the next file, “app/code/local/Knectar/Example/controllers/Adminhtml/ExampleController.php“:

class Knectar_Example_Adminhtml_ExampleController
  extends Mage_Adminhtml_Controller_Action
{

    public function indexAction()
    {
        // Load the layout handle <adminhtml_example_index>
        $this->loadLayout();

        // Sets the window title to "Example / Knectar / Magento Admin"
        $this->_title($this->__('Knectar'))
             ->_title($this->__('Example'))
        // Highlight the current menu
             ->_setActiveMenu('knectar/example');

        $this->renderLayout();
    }

}

And that’s everything you need! Whenever you need an URL to that particular page use this format:

echo Mage::getUrl('*/example');

Because you’ll be using it from within another admin page (frontend pages cannot make links to the admin) the asterix will be replaced by “admin”. The “example” means the admin router will look for an “ExampleController” in correctly configured modules, which this is.


Addendum

There are exceptions to the above rule. If you need to use AJAX then you probably don’t want a full layout to be rendered. Here the config XML is unchanged and only the controller is different. The following shows two likely use cases for returning JSON or an HTML extract.

class Knectar_Example_Adminhtml_ExampleController
  extends Mage_Adminhtml_Controller_Action
{

    public function ajaxAction()
    {
        $result = new Varien_Object();
        // Populate $result with some values...

        // Output the result as JSON encoded
        $this->getResponse()->setHeader('Content-type', 'application/json');
        $this->getResponse()->setBody($result->toJson());
    }

    public function updateAction()
    {
        // Load the layout for another page
        $this->loadLayout(array('default', 'adminhtml_example_index'));

        // Get just part of the page
        $block = $this->getLayout()->getBlock('some_block_name');

        // Output the result after all processing is finished
        // or else you will get an "Headers already sent" error.
        $this->getResponse()->setBody($block->toHtml());
    }

}