Adding new tabs and fields to Prestashop products’ back office

It is easy enough to modify Prestashop’s products’ back office and add new fields by editing the AdminProductsController. However, by using a module and a new tab, it becomes much more maintainable!

Download Project Files

Prestashop back office hooks

Although we will go through the basics of setting up a module, this tutorial assumes you already know how to create one from scratch. If you are totally new to Prestashop, I strongly recommend that you read the Official documentation on how to create a Prestashop Module before approaching this tutorial.

Now, in order to add new fields to Prestashop’s products’ back office, it is essential to be aware of the hooks that can be used to perform various operations in the admin panel. Specifically, we will be using the following ones:

DisplayAdminProductsExtra: this hook adds new tabs to the product’s back office interface, thus, we will use this hook to plug in our new content’s input field with translations

ActionAdminControllerSetMedia: if we ever need to style our new tab, or add custom javascript functions, this is the ideal hook to use to plug css and js files. This is called for every Prestashop Admin page, thus we will need to prevent it from running in pages that are not the product’s configuration.

ActionProductUpdate: the new field needs to be saved in the database. Specifically, we will add a new column (custom_field) to the product_lang table and add a new translatable field, without using overrides at all. By plugging code snippets into this hook, we can easily update the column when the product itself is updated.

At this point, we can line out our plan of action: after giving the module a basic setup, we will first create the template file that will display the new field’s input box on its own table. Then, we will see how to overcome a little issue with translatable fields, using the ActionAdminControllerSetMedia hook, and lastly, on data submission, we will update the new custom field once the product is updated. We will create a new field for demonstrational purposes only, but feel free to dare beyond the limits of this tutorial if you want!

Setting up the module

First of all, let’s start with a simple module template. You can download the blank module folder from the link at the beginning of the article. Drop the newfieldstut folder (the one from the “Start here” archive!) in the modules/ folder of your Prestashop Root. Then open up newfieldstut.php and start by adding some regular module’s code.

<?php

if (!defined('_PS_VERSION_'))
	exit;

class newFieldsTut extends Module
{
	/* @var boolean error */
	protected $_errors = false;
	
	public function __construct()
	{
		$this->name = 'newfieldstut';
		$this->tab = 'front_office_features';
		$this->version = '1.0';
		$this->author = 'Nemo';
		$this->need_instance = 0;

	 	parent::__construct();

		$this->displayName = $this->l('New Fields Tutorial');
		$this->description = $this->l('Test module from Nemo\'s Post Scriptum Tutorial (nemops.com).');
	}
		
}
?>

Now that the basic structure is laid out, we want to specify the install and uninstall functions. Let’s add a new column to product_lang with an ALTER TABLE, and provide a way to remove it if this module is uninstalled:


	public function install()
	{
		if (!parent::install() OR
			!$this->alterTable('add') OR			
			!$this->registerHook('actionAdminControllerSetMedia') OR
			!$this->registerHook('actionProductUpdate') OR
			!$this->registerHook('displayAdminProductsExtra'))
			return false;
		return true;
	}
	
	public function uninstall()
	{
		if (!parent::uninstall() OR !$this->alterTable('remove'))
			return false;
		return true;
	}


	public function alterTable($method)
	{
		switch ($method) {
			case 'add':
				$sql = 'ALTER TABLE ' . _DB_PREFIX_ . 'product_lang ADD `custom_field` TEXT NOT NULL';
				break;
			
			case 'remove':
				$sql = 'ALTER TABLE ' . _DB_PREFIX_ . 'product_lang DROP COLUMN `custom_field`';
				break;
		}
		
		if(!Db::getInstance()->Execute($sql))
			return false;
		return true;
	}

Explanation: A couple of things here! First, in the install method, we

  • 1- As with every module, call the parent’s install method
  • 2- Alter the product_lang table, to add the new field
  • 3- Register all the hooks we will use

Notice how I decided to structure the alterTable method. To avoid creating two functions, I stacked together the drop and add statements, passing a $method variable to determine what the function should do. Therefore, we call alterTable with ‘add’ when installing, and ‘remove’ when uninstalling.

Now, just to be sure we did everything correctly without errors, let’s try installing the module! Reach the Modules page in the back office and look for New Fields Tutorial in the modules list, as shown below.

Our module in the Prestashop modules list

Click install, and see if it works. If it does, move on to the next step!

Creating a new tab in the Product Back Office (DisplayAdminProductsExtra)

Okay, our module is installed, and the hooks have been registered as well. Time to test it out! Add the following function to your module:

	public function hookDisplayAdminProductsExtra($params)
	{
		if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
		{

			return $this->display(__FILE__, 'newfieldstut.tpl');
		}
	}	

Explanation: first, we make sure the current product object is correctly loaded; if so we display the content of the module’s tpl file. Reach a product’s back office page; you should see something like this, by clicking on the new tab:

New tab to Prestashop Products' back office

At this point, we can start filling in the tpl file. Open up newfieldstut.tpl, get rid of the placeholder text and add the following instead:


<h4>{l s='New Fields Tutorial' mod='newfieldstut'}</h4>
<div class="separation"></div>
<table>
	<tr>
		<td class="col-left">
			<label>{l s='Our New Field:'}</label>
		</td>
		<td>
			{include file="controllers/products/input_text_lang.tpl"
				languages=$languages
				input_name='custom_field'
				input_value=$custom_field}
			<p class="preference_description">{l s='Simply a new custom field'}. <strong>{l s='ie: something here as a hint'}</strong></p>
		</td>
	</tr>
</table>

Do not test this out yet! It will not work. Why? Let’s have a look at what we just did: first, we setup a simple table structure imitating the one Prestashop uses by default in the Product configuration page. Then, inside the rightmost table cell (td), we include a template file Prestashop kindly provides. This file is input_text_lang.tpl, and it is located in the admin theme folder, inside the controller section for products. What it does, is it provides a standardized way to add new translatable fields without having to mess with foreach loops in our own code. We can assign the currently available languages, name of the new input field and its value.

However, if we were to refresh the page now, and turn on error reporting, this is what we would see:

Error before adding variables to the hook

This happens because we have not assigned any of those 2 variables we mention in the code: $languages and $custom_field. Let’s take care of this.

Back to newfieldstut.php, create a new method as follows:


	public function prepareNewTab()
	{

		$this->context->smarty->assign(array(
			'custom_field' => '',
			'languages' => $this->context->controller->_languages,
			'default_language' => (int)Configuration::get('PS_LANG_DEFAULT')
		));

	}

For the time being, we will not bother adding a real value to the new field. As for the other 2 variables, we get the currently available languages by the controller itself, and the default language for our store.

At this point, amend hookDisplayAdminProductsExtra as follows:

	public function hookDisplayAdminProductsExtra($params)
	{
		if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
		{
			$this->prepareNewTab();
			return $this->display(__FILE__, 'newfieldstut.tpl');
		}
	}	

The new tab is ready! ….BUT! Hey, watch closely at what happens if you wait a couple of seconds before accessing it, or click its label right after the page reloads!

The language flags pops out after a while, or does not appear at all!

This is (I believe) yet another Prestashop bug. If we want our new translatable field to work correctly every time, we absolutely need to take this bug off. How to?

Properly enabling multilanguage fields in the product Back Office

What on earth is going on here? Basically, Prestashop loads the language translations flags using javascript. Not only this, but it also loads each tab asynchronously with Ajax, and ‘forgets’ to call the function which assigns those flags for custom tabs. This is where the ActionAdminControllerSetMedia hook becomes useful! We will plug in a new javascript file to take it into account, so that flags are assigned to our new tab as well.

First off, open up newfieldstut.js, which you can find in the project’s module folder you already know. Here is how it will look:

$(document).ready(function() {

});

Nothing but a simple jQuery ready statement. Inside, add the following


	$("#product-tab-content-ModuleNewfieldstut").on('loaded', function(){
			displayFlags(languages, id_language, allowEmployeeFormLang);
	})

And that’s it, really. A few notes though: Notice the ID we are checking the load status for. This will vary depending on the name you give your module, and you can check it by inspecting the tab element with the browser’s developer tools:

Inspecting the Product tab id in the back office

Just bear in mind you have to point the right ID, nothing else.

Now, back to newfieldstut.php once more. Add the following method:

	public function hookActionAdminControllerSetMedia($params)
	{

		// add necessary javascript to products back office
		if($this->context->controller->controller_name == 'AdminProducts' && Tools::getValue('id_product'))
		{
			$this->context->controller->addJS($this->_path.'/js/newfieldstut.js');
		}

	}

Explanation: As we saw at the beginning of the article, this hook runs for every page of the back office. Thus, we need to be sure we are embedding our javascript for the single product configuration page only. Therefore, we first check the the current controller is AdminProducts, then, to be sure we are not viewing the products list, we also make sure a product id is set in the current url. After performing these checks, we simply use a regular addJS to embed the code.

Save & refresh, at this point the language flags are always added to the new tab!

Saving and retrieving the new value

Everything is in place, but we are not doing anything now. We are not displaying the new field value, nor are we able to save its content!

First off, let’s save the value for each language inside the database. Append the following method to our beloved newfieldstut.php:


	public function hookActionProductUpdate($params)
	{
		// get all languages
		// for each of them, store the new field

		$id_product = (int)Tools::getValue('id_product');

		$languages = Language::getLanguages(true);
		foreach ($languages as $lang) {
			if(!Db::getInstance()->update('product_lang', array('custom_field'=> pSQL(Tools::getValue('custom_field_'.$lang['id_lang']))) ,'id_lang = ' . $lang['id_lang'] .' AND id_product = ' .$id_product ))
				$this->context->controller->_errors[] = Tools::displayError('Error: ').mysql_error();
		}

	}

Explanation: the hook we are targeting here(ActionProductUpdate) runs at the very same time a product’s data is stored inside the database. The perfect time to add our new content! First, we retrieve the current product ID, and then get all the currently active languages. For each of the languages, we update the database field for the matching language and product id. If the update operation cannot be completed, we spawn an error using the default Prestashop error handler for Admin tabs.

A small note on Tools::getValue(‘custom_field_’.$lang[‘id_lang’]): if you remember, we called our new field ‘custom_field’ when embedding the translatable input box. This means Prestashop generates one field, appending each language id, for each language. Thus, we end up having custom_field_1, custom_field_2 etc… Where the number after the second underscore represents the language ID. That’s why we are targeting it like this ‘custom_field_’.$lang[‘id_lang’], to be sure each language is correctly added.

Now, let’s try inputting different values for each languages, and hit ‘Save and stay’. Then, since we are not yet retrieving the field, let’s check it out in the database.

Our custom fields in the Prestashop Database

Okay, it’s done! At this point, we only need to retrieve those values! Let’s add a new method to our friend newfieldstut.php:


	public function getCustomField($id_product)
	{
		$result = Db::getInstance()->ExecuteS('SELECT custom_field, id_lang FROM '._DB_PREFIX_.'product_lang WHERE id_product = ' . (int)$id_product);
		if(!$result)
			return array();

		foreach ($result as $field) {
			$fields[$field['id_lang']] = $field['custom_field'];
		}

		return $fields;
	}

Explanation: Quite simply, using the product id as source data, we retrieve all entries for the custom_field column. Then, we format it for an array whose keys are language ids, and values the custom field value for that specific language. This is how Prestashop will be able to use them in the template: array(‘5′ => ‘value for id_lang 5), …’).

Of course, as a last step we call this function when assigning variables in prepareNewTab:


	public function prepareNewTab()
	{

		$this->context->smarty->assign(array(
			'custom_field' => $this->getCustomField((int)Tools::getValue('id_product')),
			'languages' => $this->context->controller->_languages,
			'default_language' => (int)Configuration::get('PS_LANG_DEFAULT')
		));

	}

And we are finished!

The new custom field added to Prestashop Products' back office

Conclusion

As you could see, adding new fields to products using modules, without any remote sign of overrides, is not as complex as it might seem. Moreover, it’s a much more maintainable practice than messing with the original back office templates and controllers! Of course, at this point, you can take this tutorial further and add front office hooks to display your new, translatable field!

 

Additional Resources

You like the tuts and want to say "thank you"? Well, you can always feel free to donate:

  • Gytis

    Honestly, this is kind of bad tutorial. Inserting PS language field code manually into template files, it’s goign to get real messing very soon. Probably won’t even work with PS1.6. I foudna workaround for this – generate the form with formhelper, then strip the tags and return it. Everything is autogenerate, meaning no messy template files.

    • slonn

      How to use formhelper class?

  • http://www.prestarocket.com/ Prestarocket

    Hello Nemo,

    I think you can’t display error in hookActionProductUpdate. In the admin product controller, there is not $this->errors[] after hookActionProductUpdate execution;

  • Niyi

    Can this be used on PS.1.6?

    • Patryk Makowski

      Yes. Not in 1.7.

      • PR

        Dear Nemo, it would be very kind if you could do a version for 1.7 too. I have tested and don’t know how to deal with the tab-part. The installation part, I could change on my own. My inquiry seems to have been delete by moderater. So, I ask kindly again.

        • NemoPS

          Hi PR,
          I am not sure if I will since the back office hook has been completely removed in 1.7 as far as I could see

  • Romain

    Hi,

    How to use this extrafield in the shopping-cart-product-line.tpl AND cart-summary.js

    Thanks in advance.

  • Luigi
  • rlaurent

    Hi,

    How to show this extrafield as a new tab in product.tp ?

    Thx in advance.

    • http://nemops.com Nemo

      In this case you must add hooks for ProductTab (the tab title, which you click to show the content) and ProductTabContent (the actual tab content)

  • mdusamaansari

    Hi Nemo,

    Really very great post, which helped me a lot. Thanks a ton.

    How can I add more text fields on instead of using one field as custom_field. I have cloned script lines to 3 copies and changed the variables, but still my values are not getting stored in database.

    Please help me out.

    Thanks in advance.

  • mdusamaansari

    Hi Nemo,

    Really very helpful post.

    I have a doubt on it, I have created a hook and I need it to be displayed on my back office orders page. Just the total amount of all orders to be displayed on bottom of my orders page.


    name = 'myordertotal';
    $this->tab = 'Test';
    $this->version = 1.0;
    $this->author = 'Mohammed Usama';
    $this->need_instance = 1;

    parent::__construct();

    $this->displayName = $this->l( 'My Order Total' );
    $this->description = $this->l( 'Order total will be displayed in orders page.' );
    }

    public function install()
    {
    return ( parent::install() );
    }

    public function uninstall()
    {
    if ( !parent::uninstall() )
    Db::getInstance()->Execute( 'DELETE FROM `' . _DB_PREFIX_ . 'myordertotal`' );
    parent::uninstall();
    }
    }
    ?>

  • Pat

    Hi, I have a shop with multistore activated and I’m experiencing some problems with the extra field… when I change and save a product (or save and stay) sometimes it deletes the content of the extra field. Do I have to add something more to the hook actionproductudate? Thanks!

    • Pat

      Hi, I got the multistore working but there still seems to be a bug… If I edit a product (e.g. price or quantity) and save it (either with save and stay or save) the content of the custom field turns blank. The content of the custom field is only saved when I click on the “custom-field-Tab” before I save e.g. a new price…

      Here’s my current hookActionProductUpdate Code:

      public function hookActionProductUpdate($params)
      {
      // get all languages
      // for each of them, store the new field
      $id_product = $params['product']->id;
      $id_shop = $this->context->shop->id;

      $languages = Language::getLanguages(true);
      foreach ($languages as $lang) {
      if(!Db::getInstance()->update('product_lang', array('custom_field'=> pSQL(Tools::getValue('custom_field_'.$lang['id_lang']))) ,'id_lang = ' . $lang['id_lang'] .' AND id_shop = ' .$id_shop. ' AND id_product = ' .$id_product ))
      $this->context->controller->_errors[] = Tools::displayError('Error: ').mysql_error();
      }
      }

      Thanks!

      • http://www.creabilis.com Jeff

        hi Pat,

        I’ve found how to solve your bug in hookActionProductUpdate, you’ve to check if POST vars are set before save it with Tools::getIsset.

        public function hookActionProductUpdate($params)
        {
        // get all languages
        // for each of them, store the new field

        $id_product = (int)Tools::getValue(‘id_product’);

        $languages = Language::getLanguages(true);
        foreach ($languages as $lang) {
        if (Tools::getIsset(‘custom_field_’.$lang[‘id_lang’]))
        if(!Db::getInstance()->update(‘product_lang’, array(‘custom_field’=> pSQL(Tools::getValue(‘custom_field_’.$lang[‘id_lang’]))) ,’id_lang = ‘ . $lang[‘id_lang’] .’ AND id_product = ‘ .$id_product ))
        $this->context->controller->_errors[] = Tools::displayError(‘Error: ‘).mysql_error();
        }

        }

  • Luigi

    Hi Nemo,
    I replaced

    include file=”controllers/products/input_text_lang.tpl”

    with

    include file=”controllers/products/textarea_lang.tpl”

    and I can see the textarea, BUT how can I active the tinymce?

  • clegeard

    Hi,

    Thanks for your tutorial is great.
    I needed to add a tab with a text area to save html in a text area, I manage to create the text area, but the problem I have is that the data is saved plain in the database and I would like to save html (as for the product description)
    How can I do that?

    Thanks in advance

    Chris

  • Fred

    I found a bug, if you can help me to fix it, it would be great.

    – Take product and add custom text in “Our New Field”
    – Close the product
    – Open that product again and click to Save button
    – Close product
    – Open again and go to New Fields Tutorial. Custom text is not exists

    Thnaks in advance

    • http://nemops.com Nemo

      Checking this out as soon as I can! Nasty! :D

  • Roberto

    Hi Nemo!

    Thanks for your awesome tutorial!

    Are there any way to use a wysiwyg editor or allow HTML?

    Thanks in advance!

    Regards,
    Roberto

  • Xanaxilovsky

    I was about editing the core and the god of prestashop guided me to this tutorial :)

  • Ronin

    And I see it in product.tpl?
    I ask you to answer.

    • http://opticalab.com Vikram

      Hi Ronin,

      I am not sure if you are addressing me?
      In my product.tpl file following code shows up my dropdowns in tpl file.

      {if isset($HOOK_DISPLAY_PRESCRIPTION) && $HOOK_DISPLAY_PRESCRIPTION}{$HOOK_DISPLAY_PRESCRIPTION}{/if}

      Display is working fine. But when i click add to cart, dropdown values does not get submitted.

      Nemo:

      Still waiting for your kind guidance with that.

      Regards
      Vikram

  • http://opticalab.com Vikram

    Hi Nemo,

    Great and very useful tutorial.

    I am writing a module for ps version 1.5.5 to have some fields on product page on front end and save those to a new table in database while the product is added to cart. I have set up the rest of it. Its quite similar to what you explained in this tutorial.

    1. The tpl file is displayed on product page, my dropdowns are showing up right on product page.
    2. My query for inserting value (dummy values) to database is working right when i click add to cart button.

    http://opticalab.com/en/opticalab/34-colours.html
    Click Prescription radio button, select values and click add to cart

    The only problem is its not reading the value of my dropdowns when those are posted. Actually its using jquery add to cart (flying feature) and it looks like the values are not actually being “posted”. My function is:

    public function hookActionCartSave($params) {
    $rightSpherical= Tools::getIsset(‘rightSpherical’);//DOES NOT ASSIGN MY VALUE TO VARIABLE
    $rightSpherical=Tools::getValue(‘rightSpherical’);//DOES NOT ASSIGN MY VALUE TO VARIABLE

    // echo ”;
    // print_r($params); //DOES NOT SHOW MY VALUES
    // print_r($_REQUEST); //DOES NOT SHOW MY VALUES
    // echo ”;
    // $rightSpherical=2.09;// ASSIGNED THIS WAY VALUES SAVED TO DB
    // die;
    if (!Db::getInstance()->Execute(‘INSERT INTO `’._DB_PREFIX_.’prescription` (`cart_id`, `user_id`, `rightSpherical`) VALUES (‘.(int)($this->id).’, ‘.(int)($id_user).’, ‘.(float)($rightSpherical).’)’))
    }

    Hope you can look in to and help!

    Thanks for your time :)

  • Paddle

    Hi!
    Great tutorial! This helped me a lot! I will have to display the new field in scenes.tpl… is there a hook where I can add this field to $product.details or will I have to override products or scene class? Thanks!!

    • Paddle

      ok, it was very simple… I just added products.php containing the new variable to override/classes:

      class Product extends ProductCore
      {
      /** @var string Custom Product Field */
      public $custom_field;
      }

      Like this I can display the content with $product.details->custom_field in scenes.tpl.

  • Cathy

    Hi Nemo thank you for your great tutorial! Just that the field I would like to add in this case is a non translatable data field & time field, would you mind to give me some light on how to do that? Thank you so much in advance!!

    • Cathy

      I mean date field

  • http://pelechano.es Enrique

    Excellent tutorial as always Nemo. Only to point that it’s interesting also the use of the hook which is triggered when adding a product

    public function hookActionProductAdd($params) {
    $this->hookActionProductUpdate($params);
    }

    In that case is better use the $params instead of Tools::getValue(‘id_product’) to completely reuse the hookActionProductUpdate

    public function hookActionProductUpdate($params) {
    $id_product = $params[‘product’]->id;
    ……………………….etc
    ………………………..etc
    }

    Because when creating a new product the Tools::getValue(‘id_product’); will return 0 so you can use $id_product = $params[‘product’]->id; which works in the update and in the add as well.

    In any case it’s a very small issue that practically nobody will notice since everybody uses the save and stay button to create the product (if not, you should :-)) several times and will leave the new tab for the end…

  • Selian

    Great tutorial!

    I am just wondering if adding new tabs into the main section would have the same procedure? Don’t you know where I could find some informations how to do that?

    Thank you for your amazing job!

    • http://nemops.com Nemo

      Hi!
      What do you mean by same section?

      • Selian

        No I mean main section, the “root menu” or how to call it. The section where you have catalog, orders, customers etc. I would like to know how to edit this section add and remove new tabs. I know, that I can do that in administration->menu, but how to do this inside module if you understand me.

        Thank you for answer

        • http://nemops.com Nemo

          Oh! I see. No, that’s a completely different story, it’s necessary to create a controller for that. You can find some info iin the official docs, perhaps I’ll do a tut on this subject as well someday!

Store Top Sales

You like the tuts and want to say "thank you"? Well, you can always feel free to donate: