<p>Here’s a simple plugin with two tabs and two fields, one field per tab. This plugin was generated on <a href="https://pluginfactory.io/" rel="noreferrer">https://pluginfactory.io/</a>. It’s a great starting point to get the plugin scaffolding set up. After that I added the code that I’ve included below to get it to work.</p>

<p>It should give you something like the following screenshot for the first tab:</p>

<p><a href="https://i.sstatic.net/yBZrD.png" rel="noreferrer"><img src="https://i.sstatic.net/yBZrD.png" alt="First tab of plugin"></a></p>

<p>and for the second tab it should look similar to this:</p>

<p><a href="https://i.sstatic.net/FIiba.png" rel="noreferrer"><img src="https://i.sstatic.net/FIiba.png" alt="Second tab of plugin"></a></p>

<h1>Folder Structure</h1>

<pre><code>[-] craft
   [-] plugins
      [-] myplugin
         [-] controllers
            |-- MyPlugin_TabOneController.php
            |-- MyPlugin_TabTwoController.php
         [+] resources
         [+] services
         [-] templates
            [-] _layouts
               |-- cp.twig
            [-] tabOne
               |-- _index.twig
            [-] tabTwo
               |-- _index.twig
         [+] translations
         [+] variables
         |-- MyPluginPlugin.php
</code></pre>

<hr>

<h2><code>myplugin/MyPluginPlugin.php</code></h2>

<p>I'm only highlighting the functions that were necessary for the plugins to show up. Add or adjust them as needed.</p>

<pre class="lang-php prettyprint-override"><code>...

public function registerCpRoutes()
{
    return array(
        'myplugin' =&gt; ['action' =&gt; "myPlugin/tabOne/index"],
        'myplugin/additional-settings' =&gt; ['action' =&gt; "myPlugin/tabTwo/index"],
        /**
         * Each 'action' relates to a controller in your plugin.
         * The names of your controller and these action must match.
         */
    );
}

protected function defineSettings()
{
    return array(
        'someSetting' =&gt; array(AttributeType::String, 'label' =&gt; 'Some Setting', 'default' =&gt; ''),
        'additionalSetting' =&gt; array(AttributeType::String, 'label' =&gt; 'Some Additional Setting', 'default' =&gt; ''),
        /*...more settings here as needed...*/
    );
}

public function getSettingsHtml()
{
   return craft()-&gt;templates-&gt;render('myplugin/MyPlugin_Settings', array(
       'settings' =&gt; $this-&gt;getSettings()
   ));
}

public function getSettingsUrl()
{
    return 'myplugin';
    /* This must be the same as the first segment in your cpRoutes. */
}

...
</code></pre>

<hr>

<h2><code>myplugin/controllers/MyPlugin_TabOneController.php</code></h2>

<pre class="lang-php prettyprint-override"><code>namespace Craft;

class MyPlugin_TabOneController extends BaseController
{

    /**
     * @var    bool|array Allows anonymous access to this controller's actions.
     * @access protected
     */
    protected $allowAnonymous = array('actionIndex',
        );

    /**
     */
    public function actionIndex()
    {
        $settings = craft()-&gt;plugins-&gt;getPlugin('myPlugin')-&gt;getSettings();
        $this-&gt;renderTemplate('myplugin/tabOne/_index', array(
            'settings' =&gt; $settings,
        ));
        /**
         * The path above is to your templates folder in your plugin.
         */
    }
}
</code></pre>

<hr>

<h2><code>myplugin/controllers/MyPlugin_TabTwoController.php</code></h2>

<pre class="lang-php prettyprint-override"><code>namespace Craft;

class MyPlugin_TabTwoController extends BaseController
{

    /**
     * @var    bool|array Allows anonymous access to this controller's actions.
     * @access protected
     */
    protected $allowAnonymous = array('actionIndex',
        );

    /**
     */
    public function actionIndex()
    {
        $settings = craft()-&gt;plugins-&gt;getPlugin('myPlugin')-&gt;getSettings();
        $this-&gt;renderTemplate('myplugin/tabTwo/_index', array(
            'settings' =&gt; $settings,
        ));
        /**
         * The path above is to your templates folder in your plugin.
         */
    }
}
</code></pre>

<hr>

<h2><code>myplugin/templates/_layouts/cp.twig</code></h2>

<p>It’s not necessary to break out the templates like I have here but it does help to keep from repeating your self in the templates, especially if you have several tabs.</p>

<pre class="lang-twig prettyprint-override"><code>{% extends "_layouts/cp" %}
{% import "_includes/forms" as forms %}

{% includeCssResource "myplugin/css/Myplugin_Style.css" %}
{% includeJsResource "myplugin/js/Myplugin_Script.js" %}

{% set title = 'My Plugin' %}

{% set tabs = {
    settings: { label: "Settings"|t, url: url('myplugin') },
    additionalSettings: { label: "Additional Settings"|t, url: url('myplugin/additional-settings') },
} %}
</code></pre>

<hr>

<h2><code>myplugin/templates/tabOne/_index.twig</code></h2>

<p>The <code>data-saveshortcut</code> attribute on the form will allow you to use the keyboard shortcut to save your plugin’s settings by using <kbd>⌘ + S</kbd> on a Mac. I’m assuming that <kbd>Ctrl + S</kbd> works on Windows although I haven’t tried it.</p>

<pre class="lang-twig prettyprint-override"><code>{% extends "myplugin/_layouts/cp" %}
{% import "_includes/forms" as forms %}

{% set selectedTab = 'settings' %}

{% set content %}

    &lt;h2&gt;Settings&lt;/h2&gt;

    &lt;form method="post" action="" accept-charset="UTF-8" data-saveshortcut data-confirm-unload&gt;

        {{ getCsrfInput() }}

        &lt;input type="hidden" name="action" value="plugins/savePluginSettings"&gt;
        &lt;input type="hidden" name="pluginClass" value="MyPlugin"&gt;
        &lt;input type="hidden" name="redirect" value="myplugin"&gt;

        {{ forms.textField({
            label: 'Some Setting',
            instructions: 'Enter some setting here.',
            id: 'settings-someSetting',
            name: 'settings[someSetting]', 
            value: settings['someSetting']})
        }}

        &lt;div class="buttons"&gt;
            &lt;input class="btn submit" type="submit" value="{{ 'Save'|t }}"&gt;
        &lt;/div&gt;

    &lt;/form&gt;

{% endset %}
</code></pre>

<hr>

<h2><code>myplugin/templates/tabOne/_index.twig</code></h2>

<p>Notice that you’ll need to set the <code>selectedTab</code> in each template for the tabs to work correctly. This should match one of your keys that was set in the <code>tabs</code> variable in the <code>myplugin/templates/_layouts/cp.twig</code>. Also the hidden <code>redirect</code> input in the form should be set to wherever you want to it end up. I noticed that it felt the best when it went back to the original page and it showed the saved values.</p>

<pre class="lang-twig prettyprint-override"><code>{% extends "myplugin/_layouts/cp" %}
{% import "_includes/forms" as forms %}

{% set selectedTab = 'additionalSettings' %}

{% set content %}

    &lt;h2&gt;Additional Settings&lt;/h2&gt;

    &lt;form method="post" action="" accept-charset="UTF-8" data-saveshortcut data-confirm-unload&gt;

        {{ getCsrfInput() }}

        &lt;input type="hidden" name="action" value="plugins/savePluginSettings"&gt;
        &lt;input type="hidden" name="pluginClass" value="MyPlugin"&gt;
        &lt;input type="hidden" name="redirect" value="myplugin/additional-settings"&gt;

        {{ forms.textField({
            label: 'Some Additional Setting',
            instructions: 'Enter some setting here.',
            id: 'settings-additionalSetting',
            name: 'settings[additionalSetting]', 
            value: settings['additionalSetting']})
        }}

        &lt;div class="buttons"&gt;
            &lt;input class="btn submit" type="submit" value="{{ 'Save'|t }}"&gt;
        &lt;/div&gt;

    &lt;/form&gt;

{% endset %}
</code></pre>

<p>That should do it!</p>

CC BY-SA 3.0
2018-01-31T20:49:25.343Z
the return for getSettingsHtml() does seem to be correct. Where do I have to link here or what to return.
Simon Franzen
CC BY-SA 3.0
2018-01-31T20:55:17.383Z
Oh ok. I was on the wrong track. I still used the old URL in my browser. But what about the getSettingHTML -> I think it is not needed, because defined other routes and templates
Simon Franzen
CC BY-SA 3.0
2017-08-11T15:11:46.080Z
2017-11-26T14:23:15.593Z
2017-11-26T14:23:15.593Z
Cavell Blood
<p>I would like to add tabs in setting page in my plugin. I have the following code in <code>/[myplugin]/templates/_setting.html</code>. However, the tabs do not show up, does anyone have any sample code to set up the tabs? Thanks</p>

<pre><code>{% import "_includes/forms" as forms %}

{% set tabs = {
customer: { label: "Customer"|t, url: url('emailUser/_settings') },
veterinarian:    { label: "Veterinarian"|t, url: url('emailUser/_vetSettings') }
} %}

{{ forms.textField({
label: "Customer Account Creation Confirmation Subject"|t,
instructions: "Customer Account Creation Confirmation Subject" |t,
name: 'custAccountCreationSubject',
value: settings.custAccountCreationSubject,
})}}

{{ forms.textareaField({
label: "Customer Account Creation Confirmation Message"|t,
instructions: "Customer email message when customer register" |t,
name: 'custAccountCreation',
value: settings.custAccountCreation,
rows:10,
})}}
</code></pre>

<p>In <code>myplugin.php</code></p>

<pre><code>public function getSettingsHtml()
{
    return craft()-&gt;templates-&gt;render('emailuser/_settings',array(
                'settings'=&gt;$this-&gt;getSettings()
    ));
}
</code></pre>

<plugin-development><settings>
Set tabs in settings on Plugin
# Set tabs in settings on Plugin

(Asked by: username_0 on 2017-07-04)

I would like to add tabs in setting page in my plugin. I have the following code in /[myplugin]/templates/_setting.html. However, the tabs do not show up, does anyone have any sample code to set up the tabs? Thanks
{% import "_includes/forms" as forms %}

{% set tabs = {
customer: { label: "Customer"|t, url: url('emailUser/_settings') },
veterinarian:    { label: "Veterinarian"|t, url: url('emailUser/_vetSettings') }
} %}

{{ forms.textField({
label: "Customer Account Creation Confirmation Subject"|t,
instructions: "Customer Account Creation Confirmation Subject" |t,
name: 'custAccountCreationSubject',
value: settings.custAccountCreationSubject,
})}}

{{ forms.textareaField({
label: "Customer Account Creation Confirmation Message"|t,
instructions: "Customer email message when customer register" |t,
name: 'custAccountCreation',
value: settings.custAccountCreation,
rows:10,
})}}

In myplugin.php
public function getSettingsHtml()
{
    return craft()->templates->render('emailuser/_settings',array(
                'settings'=>$this->getSettings()
    ));
}

**Tags:** plugin-development, settings


---

## 1 answers

### Answer by username_1 on 2017-08-11. Score: 9 | Accepted answer

Here’s a simple plugin with two tabs and two fields, one field per tab. This plugin was generated on https://pluginfactory.io/. It’s a great starting point to get the plugin scaffolding set up. After that I added the code that I’ve included below to get it to work.
It should give you something like the following screenshot for the first tab:

and for the second tab it should look similar to this:

Folder Structure
[-] craft
   [-] plugins
      [-] myplugin
         [-] controllers
            |-- MyPlugin_TabOneController.php
            |-- MyPlugin_TabTwoController.php
         [+] resources
         [+] services
         [-] templates
            [-] _layouts
               |-- cp.twig
            [-] tabOne
               |-- _index.twig
            [-] tabTwo
               |-- _index.twig
         [+] translations
         [+] variables
         |-- MyPluginPlugin.php


myplugin/MyPluginPlugin.php
I'm only highlighting the functions that were necessary for the plugins to show up. Add or adjust them as needed.
...

public function registerCpRoutes()
{
    return array(
        'myplugin' => ['action' => "myPlugin/tabOne/index"],
        'myplugin/additional-settings' => ['action' => "myPlugin/tabTwo/index"],
        /**
         * Each 'action' relates to a controller in your plugin.
         * The names of your controller and these action must match.
         */
    );
}

protected function defineSettings()
{
    return array(
        'someSetting' => array(AttributeType::String, 'label' => 'Some Setting', 'default' => ''),
        'additionalSetting' => array(AttributeType::String, 'label' => 'Some Additional Setting', 'default' => ''),
        /*...more settings here as needed...*/
    );
}

public function getSettingsHtml()
{
   return craft()->templates->render('myplugin/MyPlugin_Settings', array(
       'settings' => $this->getSettings()
   ));
}

public function getSettingsUrl()
{
    return 'myplugin';
    /* This must be the same as the first segment in your cpRoutes. */
}

...


myplugin/controllers/MyPlugin_TabOneController.php
namespace Craft;

class MyPlugin_TabOneController extends BaseController
{

    /**
     * @var    bool|array Allows anonymous access to this controller's actions.
     * @access protected
     */
    protected $allowAnonymous = array('actionIndex',
        );

    /**
     */
    public function actionIndex()
    {
        $settings = craft()->plugins->getPlugin('myPlugin')->getSettings();
        $this->renderTemplate('myplugin/tabOne/_index', array(
            'settings' => $settings,
        ));
        /**
         * The path above is to your templates folder in your plugin.
         */
    }
}


myplugin/controllers/MyPlugin_TabTwoController.php
namespace Craft;

class MyPlugin_TabTwoController extends BaseController
{

    /**
     * @var    bool|array Allows anonymous access to this controller's actions.
     * @access protected
     */
    protected $allowAnonymous = array('actionIndex',
        );

    /**
     */
    public function actionIndex()
    {
        $settings = craft()->plugins->getPlugin('myPlugin')->getSettings();
        $this->renderTemplate('myplugin/tabTwo/_index', array(
            'settings' => $settings,
        ));
        /**
         * The path above is to your templates folder in your plugin.
         */
    }
}


myplugin/templates/_layouts/cp.twig
It’s not necessary to break out the templates like I have here but it does help to keep from repeating your self in the templates, especially if you have several tabs.
{% extends "_layouts/cp" %}
{% import "_includes/forms" as forms %}

{% includeCssResource "myplugin/css/Myplugin_Style.css" %}
{% includeJsResource "myplugin/js/Myplugin_Script.js" %}

{% set title = 'My Plugin' %}

{% set tabs = {
    settings: { label: "Settings"|t, url: url('myplugin') },
    additionalSettings: { label: "Additional Settings"|t, url: url('myplugin/additional-settings') },
} %}


myplugin/templates/tabOne/_index.twig
The data-saveshortcut attribute on the form will allow you to use the keyboard shortcut to save your plugin’s settings by using ⌘ + S on a Mac. I’m assuming that Ctrl + S works on Windows although I haven’t tried it.
{% extends "myplugin/_layouts/cp" %}
{% import "_includes/forms" as forms %}

{% set selectedTab = 'settings' %}

{% set content %}

    Settings

    

        {{ getCsrfInput() }}

        
        
        

        {{ forms.textField({
            label: 'Some Setting',
            instructions: 'Enter some setting here.',
            id: 'settings-someSetting',
            name: 'settings[someSetting]', 
            value: settings['someSetting']})
        }}

        
            
        

    

{% endset %}


myplugin/templates/tabOne/_index.twig
Notice that you’ll need to set the selectedTab in each template for the tabs to work correctly. This should match one of your keys that was set in the tabs variable in the myplugin/templates/_layouts/cp.twig. Also the hidden redirect input in the form should be set to wherever you want to it end up. I noticed that it felt the best when it went back to the original page and it showed the saved values.
{% extends "myplugin/_layouts/cp" %}
{% import "_includes/forms" as forms %}

{% set selectedTab = 'additionalSettings' %}

{% set content %}

    Additional Settings

    

        {{ getCsrfInput() }}

        
        
        

        {{ forms.textField({
            label: 'Some Additional Setting',
            instructions: 'Enter some setting here.',
            id: 'settings-additionalSetting',
            name: 'settings[additionalSetting]', 
            value: settings['additionalSetting']})
        }}

        
            
        

    

{% endset %}

That should do it!

> **Comments:**
> **username_2**: the return for getSettingsHtml() does seem to be correct. Where do I have to link here or what to return.  
> **username_2**: Oh ok. I was on the wrong track. I still used the old URL in my browser. But what about the getSettingHTML -> I think it is not needed, because defined other routes and templates  

---

