Documentation

Documentation

TYPO3 extension

Estimated reading: 232 minutes 64 views

How to create an extension in TYPO3

(You can check the official doc. from here.)

  1. Let’s create an extension in TYPO3. Before creating an extension, the vendor name and the extension key should be decided. Once you decide on the extension key and vendor name, you will have to check if that extension key is available or not in TER (TYPO3 Extension Repository) as the extension key must be unique.
  2. You can check the extension key availability from here – https://extensions.typo3.org/.
  3. If the extension key has already been used then please choose another key for the extension, as we can not use the extension key which is already in use.
  4. Here we are taking the vendor name – “company” and the extension key – “employee”.
  5. If you want to launch your extension and make it published, then you must be the owner of the vendor name and should register it on Packagist.
  6. Let’s check for the extension key availability in TER.
  7. There is no extension that exists with the key “employee“, so we can go with this extension key.
  8. If the extension key is already in use then you will see that extension in the list as seen in the screenshot below.
  9. Things to follow to create an extension key are in this ref. link – https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/BestPractises/ExtensionKey.html#choosing-an-extension-key.
  10. Required files to create an extension are: i) ext_emconf.php and ii) composer.json

Create the file ext_emconf.php

  1. Create a folder and name it as the name of your extension key in lowercase.
  2. Here the extension key is “employee” so create a folder named “employee”.
  3. Now create a file ext_emconf.php into the employee directory.
  4. File path: employee/ext_emconf.php
  5. Add the following code to the file.
    <?php
    $EM_CONF[$_EXTKEY] = [
    'title' => 'Employees',
    'description' => 'Employee Details',
    'category' => 'be',
    'author' => 'Author name',
    'author_email' => 'author@company.in',
    'state' => 'stable',
    'version' => '1.0.0',
    'constraints' => [
    'depends' => [
    'typo3' => '12.1.0-12.4.99'
    ],
    'conflicts' => [
    ],
    'suggests' => [
    ],
    ],
    ];

    See the reference link – https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/ExtEmconf.html#file-ext-emconf-php

    1. title – The name of the extension in English, and that name will appear in the list of extensions.
    2. description – Short and precise description in English of what the extension does and for whom it might be useful.
    3. category – Which category does the extension belong to? – here it is “be” (Backend), but it could be anything from the below list.
      1. be – Backend (Generally backend-oriented, but not a module)
      2. module – Backend modules (When something is a module or connects with one)
      3. fe – Frontend (Generally frontend oriented, but not a “true” plugin)
      4. plugin – Frontend plugins (Plugins inserted as a “Insert Plugin” content element)
      5. misc -Miscellaneous stuff (Where not easily placed elsewhere)
      6. services – Contains TYPO3 services
      7. templates – Contains website templates
      8. example – Example extension (Which serves as examples etc.)
      9. doc – Documentation (e.g. tutorials, FAQ, etc.
      10. distribution – Distribution, an extension kickstarting a full sit
    4. author – Author name
    5. author_email – Author email address
    6. author_company – Author company
    7. state- Which state is the extension in; it could be anything from the below options
      1. alpha – The alpha state is used for very initial work, basically the extension is during the very process of creating its foundation.
      2. beta – Under current development. Beta extensions are functional, but not complete in functionality.
      3. stable – Stable extensions are complete, mature, and ready for production environment. Authors of stable extensions carry a responsibility to maintain and improve them.
      4. experimental – The experimental state is useful for anything experimental – of course. Nobody knows if this is going anywhere yet… Maybe still just an idea.
      5. test – Test extension, demonstrates concepts, etc.
      6. obsolete – The extension is obsolete or deprecated. This can be due to other extensions solving the same problem but in a better way or if the extension is not being maintained anymore.
      7. excludeFromUpdates – This state makes it impossible to update the extension through the Extension Manager (neither by the update mechanism nor by uploading a newer version to the installation). This is very useful if you made local changes to an extension for a specific installation and do not want any administrator to overwrite them.
    8. version – Version of the extension. Automatically managed by extension manager / TER. Format is [int].[int].[int]
    9. constraints – List of requirements, suggestions, or conflicts with other extensions or TYPO3 or PHP versions.
    10. Here’s how a typical setup might look:
      EXT:some_extension/ext_emconf.php


      1. depends – List of extensions that this extension depends on. Extensions defined here will be loaded before the current extension.
      2. conflicts – List of extensions that will not work with this extension.
      3. suggests – List of suggestions of extensions that work together or enhance this extension. Extensions defined here will be loaded before the current extension. Dependencies take precedence over suggestions. Loading order especially matters when overriding TCA or SQL of another extension. The above example indicates that the extension depends on a version of TYPO3 between 11.4 and 12.4 (as only bug and security fixes are integrated into TYPO3 when the last digit of the version changes, it is safe to assume it will be compatible with any upcoming version of the corresponding branch, thus .99). Also the extension has been tested and is known to work properly with PHP 7.4. and 8.1 It will conflict with “templavoila plus” (any version) and it is suggested that it might be worth installing “news” (version at least 9.0.0). Be aware that you should add at least the TYPO3 and PHP version constraints to this file to make sure everything is working properly.

        For legacy installations, the ext_emconf.php file is the source of truth for required dependencies and the loading order of active extensions.

Create composer.json

  1. Create a composer.json file for the extension.
  2. file path: employee/composer.json
  3. Below is the code of the composer.json file, which is according to the vendor name and the extension key.
    {
    "name": "company/employee",
    "type": "typo3-cms-extension",
    "description": "Employee Details",
    "authors": [
    {
    "name": "Author Name",
    "role": "Developer"
    }
    ],
    "license": "GPL-2.0-or-later",
    "require": {
    "typo3/cms-core": "^12.0"
    },
    "autoload": {
    "psr-4": {
    "Company\\Employee\\": "Classes/"
    }
    },
    "extra": {
    "typo3/cms": {
    "extension-key": "employee"
    }
    }
    }

    See the reference link: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/ComposerJson.html
    1. name: The name has the format: <my-vendor>/<dashed extension key>. Here “Dashed extension key” means that every underscore (_) has been changed to a dash (-).
      1. According to the vendor name and extension key here, the name of the extension would be company/employee.
    2. type: Use typo3-cms-extension for third-party extensions.
    3. description: Description of your extension (1 line).
    4. authors: Provide information for authors like name, role, etc.
    5. license: It has to be GPL-2.0-only or GPL-2.0-or-later.
    6. require: At least, you will need to require typo3/cms-core in the according version(s). You should add other system extensions and third-party extensions, if your extension depends on them. In composer-based installations the loading order of extensions and their dependencies is derived from require and suggest.
    7. autoload: The autoload section defines the namespace/path mapping for PSR-4 autoloading <https://www.php-fig.org/psr/psr-4/>. In TYPO3 we follow the convention that all classes (except test classes) are in the directory Classes/.
    8. extra.typo3/cms.extension-key: Not providing this property will emit a deprecation notice and will fail in future versions.

After completing the above steps, you will be able to find your extension in the extension list if you already have created the extension in your TYPO3 website. But if you have created it in the local TYPO3 setup then you will have to upload the extension after completing above steps and you will see your extension into the extension list as seen in the screenshot below:

Here you can see that the icon for the extension is missing. Let’s add the icon for the extension.

Add icon for the extension

  1. To add the icon for the extension, you just need to upload the icon with the name “Extension.png” and upload the icon to employee/Resources/Public/Icons in employee extension. You should be punctual towards the name of icon.
  2. After that you will see the icon is appearing for the extension.

    Activate the extension by clicking on the A/D (Activate/Deactivate) icon on the left of the extension.

Extension For basic CRUD

Primary goal of this document is to make you understand how to perform custom CRUD with extbase. Here a question has arrised that what is extbase ? Let’s check about extbase and how it will work for you.

What is extbase?

  1. Extbase is an extension framework to create TYPO3 frontend plugins and TYPO3 backend modules. Extbase can be used to develop extensions but it does not have to be used.
  2. Extbase can be and is often used in combination with the Fluid templating engine, but Fluid can also be used without Extbase.
  3. Backend modules and plugins can be implemented using Extbase, but can also be done with TYPO3 Core native functionality.
  4. Extbase is not a prerequisite for extension development. In most cases, using Extbase means writing less code, but the performance may suffer.
  5. You can check more about the extbase from this reference link – https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Extbase/Introduction/Index.html
  6. Things that we can use in extbase are:
    1. Model / Domain
      1. Model
      2. Persistence
      3. Repository
      4. Validator
    2. Controller
      1. ActionController
      2. Error action
      3. Property mapping
      4. Type converters
    3. View
    4. URI builder (Extbase)
    5. Registration of frontend plugins
    6. TypoScript configuration
    7. Annotations
    8. Validation
    9. Caching
    10. Localization
    11. URI arguments and reserved keywords
  7. Now decide the table struture before performing any CRUD operations.
  8. Here the extension is Employees (extension key is employee) on which the CRUD operations will be performed so the Employees should be stored in a database table. And to create tables, the ext_tables.sql file should be added.

Table creation for CRUD

Before creating the table, the definition should be clear for the CRUD. Fields for the Employees are as below:

  1. Firstname
  2. Last Name
  3. Gender 
  4. BirthDate
  5. Joining Date
  6. Image
  7. Bio
  8. Experience
  9. Salary
  10. Languages
  11. Country
  12. Education (option for add multiple)
    1. Title
    2. Start date
    3. End Date
    4. CGPA
    5. University
    6. Roll number

Education field will be used for multiple entries like one employee can have multiple degrees and should fill the details for those degrees, so another table will be created for Education field and link both tables with 1:n relation like one employee will have multiple educations/degrees.

Let’s start to create a custom table with TYPO3.
TYPO3 provides an option to define tables in the ext_tables.sql file.

Create ext_tables.sql file

  1. Define the sql structure in ext_tables.sql file
  2. File path should be employee/ ext_tables.sql ( it should be extension_key_name_folder/ext_tables.sql)
  3. This file should contain a table-structure dump of the tables used by the extension which are not auto-generated. 
  4. It is used for evaluation of the database structure and is applied to the database when an extension is enabled.
  5. If you add additional fields (or depend on certain fields) to existing tables you can also put them here.
  6. In this case insert a CREATE TABLE structure for that table, but remove all lines except the one defining the fields you need. 
  7. TYPO3 will merge this table definition to the existing table definition when comparing expected and actual table definitions.
  8. The database schema analyzer automatically creates TYPO3 “management”-related database columns by reading a table’s TCA and checking the Table properties (ctrl) section for table capabilities.
  9. These columns below are automatically added if not defined in ext_tables.sql for database tables that provide a $GLOBALS[‘TCA’] definition:
    1. uid : If the uid field is not provided inside the ext_tables.sql file, the PRIMARY KEY constraint must be omitted, too.
    2. pid : The column pid is unsigned, if the table is not workspace-aware, the default index parent includes pid and hidden as well as deleted, if the latter two are specified in TCA’s Table properties (ctrl). 
    3. tstamp : Often set to tstamp or updatedon.
    4. crdate  :Often set to crdate or createdon.
    5. delete : Often set to deleted.
    6. disabled : Often set to hidden or disabled.
    7. starttime : Often set to starttime.
    8. endtime : Often set to endtime.
    9. fe_group : Often set to fe_group.
    10. sortby : Often set to sorting.
    11. descriptionColumn : Often set to description.
    12. editlock : Often set to editlock.
    13. languageField : Often set to sys_language_uid.
    14. transOrigPointerField  :Often set to l10n_parent.
    15. translationSource : Often set to l10n_source.
    16. l10n_state : Column added if [‘ctrl’][‘languageField’] and [‘ctrl’][‘transOrigPointerField’] are set.
    17. origUid : Often set to t3_origuid.
    18. transOrigDiffSourceField : Often set to l10n_diffsource.
  10. Let’s create ext_tables.sql file.
  11. Define table with create table query.
  12. Table name pattern should be tx_extensionkey_domain_model_<tablename>
  13. Here in this example the table is tx_employee_domain_model_employ
  14. Define the employ table like this:
    CREATE TABLE tx_employee_domain_model_employ (
    first_name varchar(255) DEFAULT '' NOT NULL,
    last_name varchar(255) DEFAULT '' NOT NULL,
    gender int(11) DEFAULT 0 NOT NULL,
    birth_date int(11) DEFAULT '0' NOT NULL,
    joining_date int(11) DEFAULT '0' NOT NULL,
    image int(11) unsigned DEFAULT '0',
    bio text,
    experiance text,
    salary double(11,2) DEFAULT 0.00 NOT NULL,
    languages int(11) DEFAULT 0 NOT NULL,
    country tinytext,
    );
  15. Also define the table for education

    CREATE TABLE tx_employee_domain_model_education (
    title varchar(255) DEFAULT '' NOT NULL,
    rollnumber varchar(255) DEFAULT '' NOT NULL,
    start_date int(11) DEFAULT '0' NOT NULL,
    end_date int(11) DEFAULT '0' NOT NULL,
    cgpa double(11,2) DEFAULT 0.00 NOT NULL,
    university tinytext,
    );

    After saving this file, please toggle the extension (Deactivate once and then activate again) or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache” and then you will get the tables you have created.


  16. Still the autogenerated fields are not added in tables as defined above, because the TCA files are yet not defined.
  17. Let’s create the TCA files

Create TCA files of table

  1. TCA file’s name should be <table_name>.php and it will resides in in employee/Configuration/TCA/ folder
  2. There will be one TCA file per database table.
  3. Here in this example TCA file path are: i ) employee/Configuration/TCA/tx_employee_domain_model_employ.php and ii ) employee/Configuration/TCA/tx_employee_domain_model_education.php
  4. The Table Configuration Array (or $GLOBALS[‘TCA’], TCA) is a global array in TYPO3 which extends the definition of database tables beyond what can be done strictly with SQL.
  5. First and foremost $GLOBALS[‘TCA’] defines which tables are editable in the TYPO3 backend.
  6. Database tables with no corresponding entry in $GLOBALS[‘TCA’] are “invisible” to the TYPO3 backend, so if no TCA file is created for individual table, then you will not find any option in backend to add new record for that particular table.
  7. the $GLOBALS[‘TCA’] definition of a table also covers the following:
    1. Relations between that table and other tables.
    2. What fields should be displayed in the backend and with which layout.
    3. How should a field be validated (e.g. required, integer, etc.).
    4. How a field is used in the frontend by Extbase and any extension that may refer to this information.
  8. The general structure for a TCA file (looking at a single table) is as follows:
    (The code of TCA files are included after this point)
    1. [‘ctrl’] The table
      1. Reference for ctrl : https://docs.typo3.org/m/typo3/reference-tca/12.4/en-us/Ctrl/Index.html#ctrl 
      2. These are basically divided in two main categories:
        1. properties which affect how the table is displayed and handled in the backend interface . ( This includes which icon, what name, which columns contains the title value, which column defines the type value etc.)
        2. properties which determine how it is processed by the system (TCE). (This includes publishing control, “deleted” flag, whether the table can only be edited by admin-users, may only exist in the tree root etc.)

          Employee TCA file for “ctrl” (file path: employee/Configuration/TCA/tx_employee_domain_model_employ.php)



          Education TCA file for “ctrl” (file path: employee/Configuration/TCA/tx_employee_domain_model_education.php)

    2. [‘interface’] Backend interface handling
      1. The “interface” section contains properties related to the tables display in the backend, mostly the Web > List module.
      2. Ref. link: https://docs.typo3.org/m/typo3/reference-tca/12.4/en-us/Interface/Index.html#interface
    3. [‘columns’] Individual fields
      1. The “columns” section contains configuration for each table field (also called “column”) which can be edited in the backend.
      2. Ref. link : https://docs.typo3.org/m/typo3/reference-tca/12.4/en-us/Columns/Index.html#columns 

        Employee TCA file

        Education TCA file
    4. [‘types’] Form layout for editing
      1. The “types” section defines how the fields in the table (configured in the “columns” section) should be arranged inside the editing form; in which order, with which “palettes” (see below) and with which possible additional features applied.
      2. Ref. link : https://docs.typo3.org/m/typo3/reference-tca/12.4/en-us/Types/Index.html#types

        Employee TCA file


        Education TCA file
    5. [‘palettes’] Palette fields order
      1. A palette is just a list of fields which will be arranged horizontally side-by-side.
      2. Ref. link : https://docs.typo3.org/m/typo3/reference-tca/12.4/en-us/Palettes/Index.html#palettes

Employ table TCA full code

Path:  employee/Configuration/TCA/tx_employee_domain_model_employ.php

<?php
return [
    'ctrl' => [
        'title' => 'Employe',
        'label' => 'first_name',
        'tstamp' => 'tstamp',
        'crdate' => 'crdate',
        'versioningWS' => true,
        'label_alt_force' => true,
        'origUid' => 't3_origuid',
        'languageField' => 'sys_language_uid',
        'transOrigPointerField' => 'l10n_parent',
        'transOrigDiffSourceField' => 'l10n_diffsource',
        'delete' => 'deleted',
        'enablecolumns' => [
            'disabled' => 'hidden',
            'starttime' => 'starttime',
            'endtime' => 'endtime',
        ],
        'searchFields' => 'first_name,last_name,bio',
        'iconfile' => 'EXT:employee/Resources/Public/Icons/Extension.png',
        'security' => [
            'ignorePageTypeRestriction' => true,
        ],
    ],
    'types' => [
        '1' => [
            'showitem' => 'first_name,last_name,gender,birth_date,joining_date,image,bio,experiance,salary,languages,country, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime'
        ],
    ],
    'columns' => [
        'sys_language_uid' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
            'config' => [
                'type' => 'language',
            ],
        ],
        'l10n_parent' => [
            'displayCond' => 'FIELD:sys_language_uid:>:0',
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
            'config' => [
                'type' => 'select',
                'renderType' => 'selectSingle',
                'default' => 0,
                'items' => [
                    ['label' => '', 'value' => 0]
                ],
                'foreign_table' => 'tx_employee_domain_model_employ',
                'foreign_table_where' => 'AND {#tx_employee_domain_model_employ}.{#pid}=###CURRENT_PID### AND {#tx_employee_domain_model_employ}.{#sys_language_uid} IN (-1,0)',
            ],
        ],
        'l10n_diffsource' => [
            'config' => [
                'type' => 'passthrough',
            ],
        ],
        'hidden' => [
            'config' => [
                'type' => 'check',
                'items' => [
                    ['label' => 'Disable'],
                ],
            ]
        ],
        'starttime' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
            'config' => [
                'type' => 'input',
                'renderType' => 'datetime',
                'eval' => 'datetime,int',
                'default' => 0,
                'behaviour' => [
                    'allowLanguageSynchronization' => true
                ]
            ],
        ],
        'endtime' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
            'config' => [
                'type' => 'input',
                'renderType' => 'datetime',
                'eval' => 'datetime,int',
                'default' => 0,
                'range' => [
                    'upper' => mktime(0, 0, 0, 1, 1, 2038)
                ],
                'behaviour' => [
                    'allowLanguageSynchronization' => true
                ]
            ],
        ],

        'first_name' => [
            'l10n_mode' => 'prefixLangTitle',
            'label' => 'First Name',
            'description' => 'Enter First Name',
            'config' => [
                'type' => 'input',
                'behaviour' => [
                    'allowLanguageSynchronization' => true,
                ],
            ]
        ],
        'last_name' => [
            'l10n_mode' => 'prefixLangTitle',
            'label' => 'Last Name',
            'description' => 'Enter Last Name',
            'config' => [
                'type' => 'input',
                'behaviour' => [
                    'allowLanguageSynchronization' => true,
                ],
            ]
        ],
        'gender' => [
            'label' => 'Gender',
            'description' => 'Select Gender',
            'config' => [
                'type' => 'radio',
                'items' => [
                    [
                        'label' => 'Male',
                        'value' => 1,
                    ],
                    [
                        'label' => 'FeMale',
                        'value' => 2,
                    ]
                ],
            ],
        ],
        'birth_date' => [
            'label' => 'Birth Date',
            'config' => [
                'type' => 'datetime',
                'format' => 'date',
                'eval' => 'int',
                'default' => 0,
            ]
        ],
        'joining_date' => [
            'label' => 'Joining Date',
            'config' => [
                'type' => 'datetime',
                'format' => 'datetime',
                'eval' => 'int',
                'default' => 0,
            ]
        ],
        'image' => [
            'label' => 'Image',
            'config' => [
                'type' => 'file',
                'allowed' => 'common-image-types'
            ],
        ],
        "bio" => [
            'label' => 'Bio',
            'config' => [
                'type' => 'text',
                'cols' => 20,
                'rows' => 2,
            ],
        ],
        "experiance" => [
            'label' => 'Experiance',
            'config' => [
                'type' => 'text',
                'enableRichtext' => true,
            ],
        ],
        "salary" => [
            'label' => 'Salary',
            'config' => [
                'type' => 'number',
                'format' => 'decimal'
            ]
        ],
        "languages" => [
            'label' => 'Languages',
            'config' => [
                'type' => 'check',
                'items' => [
                    [
                        'label' => 'English',
                        'value' => 'en',
                    ],
                    [
                        'label' => 'German',
                        'value' => 'de',
                    ],
                    [
                        'label' => 'French',
                        'value' => 'fr',
                    ],
                ],
            ],
        ],
        "country" => [
            'label' => 'Country',
            'config' => [
                'type' => 'select',
                'renderType' => 'selectSingle',
                'items' => [
                     [
                        'label' => 'US',
                        'value' => 'US',
                    ],
                    [
                        'label' => 'Germany',
                        'value' => 'Germany',
                    ],
                    [
                        'label' => 'France',
                        'value' => 'France',
                    ],
                ],
            ],
        ]
    ],
];

Education table TCA full code

Path:  employee/Configuration/TCA/tx_employee_domain_model_education.php

 <?php
return [
    'ctrl' => [
        'title' => 'Education',
        'label' => 'title',
        'tstamp' => 'tstamp',
        'crdate' => 'crdate',
        'versioningWS' => true,
        'label_alt_force' => true,
        'origUid' => 't3_origuid',
        'languageField' => 'sys_language_uid',
        'transOrigPointerField' => 'l10n_parent',
        'transOrigDiffSourceField' => 'l10n_diffsource',
        'delete' => 'deleted',
        'enablecolumns' => [
            'disabled' => 'hidden',
            'starttime' => 'starttime',
            'endtime' => 'endtime',
        ],
        'searchFields' => 'title,rollnumber,university',
        'iconfile' => 'EXT:employee/Resources/Public/Icons/Extension.png',
        'security' => [
            'ignorePageTypeRestriction' => true,
        ],
    ],
    'types' => [
        '1' => [
            'showitem' => 'title,rollnumber,start_date, end_date, cgpa,employ, university, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime'
        ],
    ],
    'columns' => [
        'sys_language_uid' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
            'config' => [
                'type' => 'language',
            ],
        ],
        'l10n_parent' => [
            'displayCond' => 'FIELD:sys_language_uid:>:0',
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
            'config' => [
                'type' => 'select',
                'renderType' => 'selectSingle',
                'default' => 0,
                'items' => [
                    ['label' => '', 'value' => 0]
                ],
                'foreign_table' => 'tx_employee_domain_model_education',
                'foreign_table_where' => 'AND {#tx_employee_domain_model_education}.{#pid}=###CURRENT_PID### AND {#tx_employee_domain_model_education}.{#sys_language_uid} IN (-1,0)',
            ],
        ],
        'l10n_diffsource' => [
            'config' => [
                'type' => 'passthrough',
            ],
        ],
        'hidden' => [
            'config' => [
                'type' => 'check',
                'items' => [
                    ['label' => 'Disable'],
                ],
            ]
        ],
        'starttime' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
            'config' => [
                'type' => 'input',
                'renderType' => 'datetime',
                'eval' => 'datetime,int',
                'default' => 0,
                'behaviour' => [
                    'allowLanguageSynchronization' => true
                ]
            ],
        ],
        'endtime' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
            'config' => [
                'type' => 'input',
                'renderType' => 'datetime',
                'eval' => 'datetime,int',
                'default' => 0,
                'range' => [
                    'upper' => mktime(0, 0, 0, 1, 1, 2038)
                ],
                'behaviour' => [
                    'allowLanguageSynchronization' => true
                ]
            ],
        ],

        'title' => [
            'l10n_mode' => 'prefixLangTitle',
            'label' => 'Title',
            'description' => 'Title',
            'config' => [
                'type' => 'input',
                'behaviour' => [
                    'allowLanguageSynchronization' => true,
                ],
            ]
        ],
        'rollnumber' => [
            'l10n_mode' => 'prefixLangTitle',
            'label' => 'Roll Number',
            'description' => 'Enter Seat Number',
            'config' => [
                'type' => 'input',
                'behaviour' => [
                    'allowLanguageSynchronization' => true,
                ],
            ]
        ],
        'start_date' => [
            'label' => 'Start Date',
            'config' => [
                'type' => 'datetime',
                'format' => 'date',
                'eval' => 'int',
                'default' => 0,
            ]
        ],
        'end_date' => [
            'label' => 'End Date',
            'config' => [
                'type' => 'datetime',
                'format' => 'date',
                'eval' => 'int',
                'default' => 0,
            ]
        ],
        "cgpa" => [
            'label' => 'CGPA',
            'config' => [
                'type' => 'number',
                'format' => 'decimal'
            ]
        ],
        "university" => [
            'label' => 'University',
            'config' => [
                'type' => 'select',
                'renderType' => 'selectSingle',
                'items' => [
                    [
                        'label' => 'University 1',
                        'value' => 'uni-1',
                    ],
                    [
                        'label' => 'University 2',
                        'value' => 'uni-2',
                    ],
                    [
                        'label' => 'University 3',
                        'value' => 'uni-3',
                    ],
                ],
            ],
        ], 
];

After saving these files, please toggle the extension (Deactivate once and then activate again) or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache”. As explained above after adding TCA files and toggling the extension, the table will have the default fields.



As mentioned above in point 6, after adding TCA files now the option should be visible to add records for the “Employee“. Let’s check if the option is available now or not in this example.


Go to Web > List > select the folder in which you want to store the records, and press on the “+Create new record” button.


After selecting “Employe“, the below screen will appear and we will be able to add the employee record.

Here you can see that there is no option to add the education for the employee. So we need to establish the relation between the tables.

Relation with table

As per our definition, one employee can have multiple educations, so we should give options in “Employe” form to add multiple educations, and to do that we should establish the relation 1:n between 2 tables.
We can establish the relationship by updating the 3 files which are
i ) employee/ext_tables.sql,
ii )employee/Configuration/TCA/tx_employee_domain_model_education.php, and
iii ) employee/Configuration/TCA/tx_employee_domain_model_employ.php. Let’s see the updates in files in details

  1. employee/ext_tables.sql
    1. Add fields in both tables to relate them to each other.
    2. Add employ field in education table and add the education field in employ table.
    3. Below is the code of SQL file after these updates.


      CREATE TABLE tx_employee_domain_model_employ (
      first_name varchar(255) DEFAULT '' NOT NULL,
      last_name varchar(255) DEFAULT '' NOT NULL,
      gender int(11) DEFAULT 0 NOT NULL,
      birth_date int(11) DEFAULT '0' NOT NULL,
      joining_date int(11) DEFAULT '0' NOT NULL,
      image int(11) unsigned DEFAULT '0',
      bio text,
      experiance text,
      salary double(11,2) DEFAULT 0.00 NOT NULL,
      languages int(11) DEFAULT 0 NOT NULL,
      country tinytext,
      educations int(11) DEFAULT 0 NOT NULL,
      );


      CREATE TABLE tx_employee_domain_model_education (
      title varchar(255) DEFAULT '' NOT NULL,
      rollnumber varchar(255) DEFAULT '' NOT NULL,
      start_date int(11) DEFAULT '0' NOT NULL,
      end_date int(11) DEFAULT '0' NOT NULL,
      cgpa double(11,2) DEFAULT 0.00 NOT NULL,
      university tinytext,
      employ int(11) DEFAULT '0' NOT NULL,
      );
  2. employee/Configuration/TCA/tx_employee_domain_model_education.php
    1. Add field in TCA file of education to connect with employ table.

       "employ" => [
      'label' => 'Employ',
      'config' => [
      'type' => 'select',
      'renderType' => 'selectSingle',
      'foreign_table' => 'tx_employee_domain_model_employ',
      'foreign_table_where' => 'AND {#tx_employee_domain_model_employ}.{#pid}=###CURRENT_PID### AND {#tx_employee_domain_model_employ}.{#sys_language_uid}=\'###REC_FIELD_sys_language_uid###\'',
      ],
      ]
  3. employee/Configuration/TCA/tx_employee_domain_model_employ.php
    1. Add field in TCA file of employ to connect with employ table.
      'educations' => [
      'label' => 'Educations',
      'description' => 'Add Educations',
      'config' => [
      'type' => 'inline',
      'foreign_table' => 'tx_employee_domain_model_education',
      'foreign_field' => 'employ',
      'appearance' => [
      'showSynchronizationLink' => true,
      'showAllLocalizationLink' => true,
      'showPossibleLocalizationRecords' => true,
      ],
      ],
      ],

After these changes in files, please toggle the extension (deactivate and activate again), or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache”, then the field of education will be added to the “employ” form.

How to perform CRUD (Here the CRUD has been performed for this example, you can implement the CRUD in your own example )

Create

To create a new record, go to Web > List > Select the folder Or create the folder where you want to store your records for employees and then click on the “+Create new record” button and select Employe and you will see the form for the employee.


Fill up the form with the details of the employee and save the form.

Read

To get the list of all the employee’s go to Web > List > Select the folder you have stored your records in and you will see the list of all the records you have stored.

Update

To update any record, just click on the edit icon for the particular record you want to edit, update the detail you want to update, and save the form, and you will see the value has been updated.

Delete

To delete a particular record, go to Web > List > Select the folder from which you want to delete the record and you will see the list of the records. Then click on the delete icon for that particular record, then confirm that you want to delete the record and that record will be deleted.

So we have performed the CRUD for the Employee from the TYPO3 admin panel, now it’s time to show those employees on the front-end page, but there is no option to render records on front-end pages. That is possible by creating the front-end plugin. Let’s create Front end plugin.

Front-end plugin in extension for the list page of employees

Ref. link – https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Extbase/Reference/FrontendPlugins.html

  1. When you want to use Extbase controllers in the front-end you need to define a front-end plugin.
  2. There are different technology choices to create frontend plugins in TYPO3.
  3. Extbase allows to define multiple front-end plugins for different use cases within one extension.
  4. A frontend plugin can be defined as a content element or as a pure TypoScript frontend plugin.
  5. Content element plugins can be added by editors to pages in the Page module while TypoScript frontend plugins can only be added via TypoScript or Fluid in a predefined position of the page. 
  6. All content element plugins can also be used as TypoScript plugins.
  7. Let’s check if there is any existing plugin or not.

Configure the new plugin

Let’s create a front-end plugin to list out employees in the front-end.

  1. You will need to configure the plugin in ext_localconf.php file at the root of the extension. (Path: employee/ext_localconf.php)
  2. Here you will define a controller and action in ext_localconf.php file, and before that, you will need to create that controller and action.

Let’s see about the controllers in TYPO3.

Controller

  1. TYPO3 does have 4 types of controllers.
    1. ActionController
    2. Error action
    3. Property mapping
    4. Type converters
  2. Most Extbase controllers are based on the \TYPO3\CMS\Extbase\Mvc\Controller\ActionController.
  3. Most public and protected methods that end with “Action” (for example indexAction() or showAction()), are automatically registered as actions of the controller.
  4. There are multiple things for the extbase controller which you can refer from this link – https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/Extbase/Reference/Controller/ActionController.html#actioncontroller
  5. Here the Action controller will be created, which will be used for the front-end plugin to list the employees in the front-end.
  6. Create the controller file in the <extension_directory>/Classes/Controller folder, here the controller will be created in the employee/Classes/Controller folder.
  7. For this example of the “employee” extension, let’s create the Employee controller named “EmployController.php” and the path will be employee/Classes/Controller/EmployController.php
  8. You can name the controller as per your choice, but the postfix”Controller” is mandatory. Like here the name can be EmployeeController.php or EmployeesController.php or anything you want, but to add the postfix “Controller” after the Employee or Employees is mandatory.
  9. Create the controller with indexAction() as seen in the code below, to list out the employees. Here you can create any action like you can create listAction() instead of indexAction(), but adding the “Action” postfix is mandatory.
  10. Here the indexAction() has been defined only, not fetching any records for now.

    <?php
    declare(strict_types=1);

    namespace Company\Employee\Controller;

    use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
    use Psr\Http\Message\ResponseInterface;

    class EmployController extends ActionController
    {

    /**
    * action list
    *
    * @return string|object|null|void
    */
    public function indexAction(): ResponseInterface
    {

    $this->view->assign('title', 'List Of Employee!!');
    return $this->htmlResponse();
    }
    }

      1. namespace: namespace for controller is vendor/ext_name/Controller.
      2. Controller name: Here the Action type controller has been used, so Define the controller name (EmployController here) with extending ActionController.
      3. Action: Here the indexAction() has been created to list out the employees, Define the actione with public accessibilities, and it will return the html response as per TYPO3 standards.

    Plugin Configuration

    1. Configure the plugin now in ext_localconf.php file.
    2. Config the plugin with the controller name and the action name that are used with the plugin.
    3. Below is the screenshot of ext_localconf.php file.
      <?php
      
      declare(strict_types=1);
      
      use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
      use Company\Employee\Controller\EmployController;
      
      // employee list plugin
      ExtensionUtility::configurePlugin(
          // extension key or extension name in UpperCamelCase (if the extension name is my products then the name here will be MyProducts)
          'Employee',
          // A unique identifier for your plugin in UpperCamelCase that will be the name of your plugin
          'Employlist',
          // all actions
          [EmployController::class => 'index'],
          // non-cacheable actions
          [EmployController::class => 'index'],
      );
    4. TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin() generates the necessary TypoScript to display the plugin in the front-end with the following parameters
      1. Extension key ’employee’ or extension name Employee.
      2. A unique identifier for your plugin in UpperCamelCase: ‘Employlist’.
      3. An array of allowed combinations of controllers and actions is stored in an array.
      4. (Optional) an array of controller names and action names that should not be cached.
    5. Here the allowed action of the EmployController is “index”, no other action is allowed here by this plugin.
    6. Now let’s register the plugin.

    Plugin Registration

    1. Register the plugin by adding it to the tt_content file, and if the file is not available, you will need to create it. (it will make the plugin available in cType (Dropdown) ).
    2. File path:  <extension_directory>/Configuration/TCA/Overrides/tt_content.php, here in this example it will be employee/Configuration/TCA/Overrides/tt_content.php

      <?php
      defined TYPO3 or die();
      (static function (): void{
      \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
      // extension name, matching the PHP namespaces (but without the vendor)
      'Employee',
      // arbitrary, but unique plugin name (not visible in the backend), must be the same as used in configurePlugin()
      'Employlist',
      // plugin title, as visible in the drop-down in the backend, use "LLL:" for localization
      'List Of Employee',
      // icon identifier
      'EXT:employee/Resources/Public/Icons/Extension.png',
      );

      })();
    3. Register the plugin with the following parameters
      1. Extension key ’employee’ or extension name “Employee”.
      2. A unique identifier for your plugin in UpperCamelCase: ‘Employlist’, must be the same as used in configurePlugin() or the plugin will not render.
      3. Plugin title in the backend: Can be a string or a localized string starting with LLL:.
      4. (Optional) the icon identifier or file path prepended with “EXT:”
    4. Now the plugin has been registered.

    How to use the plugin in front-end?

    1. After completing the plugin registration, flush the cache and add the plugin to the page by going to the plugin tab > select the General plugin as seen in the screenshot below.


    2. Still, the plugin has not been available to the wizard.


    Make the plugin available to the content wizard (so the plugin can be added directly from the wizard)

    1. Write tsconfig file to make the plugin available to the wizard.
    2. Add tsconfig file into <extension_directory>/Configuration/page.tsconfig, here for this extension the path of the tsconfig file will be employee/Configuration/page.tsconfig
      mod.wizards.newContentElement.wizardItems {
      plugins {
      elements {
      // plugin signature: It is the extension name in lowercase without underscores, followed by one underscore, followed by the plugin identifier in lowercase without underscores.
      employee_employlist {
      // icon identifier: It will load the icon that is identified write the icon identifier for this extension (the explanation of this topic is below, please continue to follow the doc.)
      iconIdentifier = employee
      // title: It will be rendered as the title of the plugin in the wizard.
      title = Emplyees List
      // description: It will rendered as the description of the plugin in the wizard.
      description = List of Employees
      tt_content_defValues {
      CType = list
      // list_type: It will use the same plugin identifier.
      list_type = employee_employlist
      }
      }
      // To add the plugin to the list add the code as seen in the below line
      show := addToList(employee_employlist)
      }
      }
      }
    3. Now clean the cache and open wizard and you will find the plugin is available in wizard now.
    4. Now you don’t need to select the general plugin and then select the custom front-end plugin
    5. In the above screenshot, as you can see the icon of the plugin is not loaded.
    6. The icon identifier has been set in tsconfig file as “employee” but that icon identifier is not registered yet.
    7. Let’s Register the icon identifier.

    Icon Identifier 

    1. All icons must be registered in the icon registry.
    2. Before registering them, you need to upload the icons.
    3. To register icons for your own extension, create a file named Icons.php in your extension, the file path is: <extension_directory>/Configuration/Icons.php.
    4. For this extension, the file path is: employee/Configuration/Icons.php
      <?php

      declare(strict_types=1);

      use TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider;

      return [
      'employee' => [
      'provider' => BitmapIconProvider::class,
      'source' => 'EXT:employee/Resources/Public/Icons/Extension.png',
      'spinning' => true,
      ],

      ];
      Here your icon should exist on employee/Resources/Public/Icons/ path as here the defined source is: employee/Resources/Public/Icons/Extension.png
    5. Name the iconIdentifier with the following parameters
      1. provider: Icon provider
      2. source: Path of the icon which should be loaded with that iconIdentifier
      3. spinning: set this boolean value to true if you want to spin the icon and false if you don’t want the spinning icon.
      4. After saving this file, clean the cache and open the wizard and you will find the plugin containing the icon now.

    Use the list plugin by adding it to the page

    1. Go to Web > Page > Select the page you want to add the plugin to or create a new page if you want to add the plugin to the new page and click on the “+content” button, so the wizard will be open as seen in the screenshot below. Then select the “Plugins” tab and select the extension you want to use, here we want to use the “Employee List” extension we created.

    2. Go to the “Plugin” tab as seen in the screenshot below, and select the plugin “List of Employee” from the dropdown.
    3. Save the page.
    4. After saving the page, open the front-end and the below error will be seen.

    Error: 500 Page Not found

    1. This error is occurring because the typoscript is still pending.
    2. Typoscript files are located in <extension_directory>/Configuration/TypoScript. Here it will be employee/Configuration/TypoScript.
    3. Typoscript files contain .typoscript extension
    4. Let’s write the employee/Configuration/TypoScript/setup.typoscript file to render the page in the front-end.
    
    config.contentObjectExceptionHandler = 0
    # DEfine page config 
    page = PAGE
    page { 
        10 = FLUIDTEMPLATE
        10 {
            templateName = TEXT
            templateName {
                cObject = TEXT
                cObject {
                    data = pagelayout
                    required = 1
                    case = uppercamelcase
                    split {
                        token = pagets__
                        cObjNum = 1
                        1.current = 1
                    }
                }
                ifEmpty = Default
            }
            templateRootPaths {
                0 = EXT:employee/Resources/Private/Templates/Page/
                1 = {$page.fluidtemplate.templateRootPath}
            }
            partialRootPaths {
                0 = EXT:employee/Resources/Private/Partials/Page/
                1 = {$page.fluidtemplate.partialRootPath}
            }
            layoutRootPaths {
                0 = EXT:employee/Resources/Private/Layouts/Page/
                1 = {$page.fluidtemplate.layoutRootPath}
            }
            dataProcessing {
                10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
                10 {
                    references.fieldName = media
                }
            }
        } 
    }
    
    
    lib.dynamicContent = COA 
    lib.dynamicContent { 
        5 = LOAD_REGISTER 
        5 { 
            colPos.cObject = TEXT 
            colPos.cObject { 
                field = colPos 
                ifEmpty.cObject = TEXT 
                ifEmpty.cObject { 
                    value.current = 1 
                    ifEmpty = 0 
                } 
            } 
            pageUid.cObject = TEXT 
            pageUid.cObject { 
                field = pageUid 
                ifEmpty.data = TSFE:id 
            } 
            contentFromPid.cObject = TEXT 
            contentFromPid.cObject { 
                data = DB:pages:{register:pageUid}:content_from_pid 
                data.insertData = 1 
            } 
            wrap.cObject = TEXT 
            wrap.cObject { 
                field = wrap 
            } 
        } 
        20 = CONTENT 
        20 { 
            table = tt_content 
            select { 
                includeRecordsWithoutDefaultTranslation = 1 
                orderBy = sorting 
                where = {#colPos}={register:colPos} 
                where.insertData = 1 
                pidInList.data = register:pageUid 
                pidInList.override.data = register:contentFromPid 
            } 
            stdWrap { 
                dataWrap = {register:wrap} 
                required = 1 
            } 
        } 
        90 = RESTORE_REGISTER 
    } 
    

    save the file and now call the file to load this typoscript.

    There are 2 ways to add the typoscript in your extension. (Ref. link: https://docs.typo3.org/m/typo3/reference-typoscript/12.4/en-us/UsingSetting/AddTypoScriptWithExtensions.html)

    1. Make TypoScript available for static includes
      1. If you include the TypoScript this way, it will not be automatically loaded. You must load it by adding the static include in the Web > Template module in the backend, see Include TypoScript from extensions.
      2. This has the advantage of better configurability.
      3. This will load your constants and your setup once the template is included statically.
    2. Make TypoScript available (always load)
      1. Only do this, if your TypoScript must really be always loaded in your site.
      2. If this is not the case, use the method described in the previous section Make TypoScript available for static includes.

    Here in this example, the typoscript has been added with the first way “Make TypoScript available for static includes

    1. To add the typoscript this way, call the typoscript file in sys_template.php file
    2. Path: <extension_directory>/Configuration/TCA/Overrides/sys_template.php. Here it will be employee/Configuration/TCA/Overrides/sys_template.php
      <?php
      defined('TYPO3') or die();
      
      call_user_func(function () {
          \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile(
              // extension key
              'employee',
              // configuration/typoscript to load the typoscript
              'Configuration/TypoScript',
              // description of the typoscript
              'Employee'
          );
      });
    3. Static file function has 3 parameters which are i) extension key ii) configuration/typoscript to load the typoscript and iii) description of typoscript.
    4. After creating this file, save the file, flush the caches, then go to Site Management > TypoScript > select root page > Edit TypoScript Record and click on Edit the whole TypoScript record button.
    5. Then go to the Advanced Options Tab > Include TypoScript sets > and see your extension typoscripts on “Available Items”. Now click on that typoscript to include the typoscript of that extension, and that will be included by moving to the “Selected Items”.
    6. Now flush the cache and open the front-end, and you will be redirected to the screen as seen in the screenshot below.

    Error: InvalidTemplateResourceException

    The layout and template file are not added in the extension as per the typoscript page setup, and that is the reason for the error InvalidTemplateResourceException occurring. Follow the below steps to resolve the error.

    1. Let’s add the layout file.
      1. The file path is <extension_directory>\Resources\Private\Layouts\Page\Default.html. Here in this example, it would be employee\Resources\Private\Layouts\Page\Default.html
      2. Write the line of code below and save the file.

        <f:render section="Main" />
    2. Let’s add the template file.
      1. The file path is <extension_directory>\Resources\Private\Templates\Page\Default.html. Here in this example, it would be employee\Resources\Private\Templates\Page\Default.html
    <f:layout name="Default" />
    <f:section name="Main">
      <f:cObject typoscriptObjectPath="lib.dynamicContent" data="{colPos: 0}" />
    </f:section>

    Save the file, flush the cache, and load the front-end page, and you will see the below screen with an error of non-existent service.

    Error: non-existent service

    To resolve the error of non-existent service that is occurring in the above screenshot, follow the below steps:

    1. Let’s define the service in Services.yaml file.
    2. The file path is <extension_directory>/Configuration/Services.yaml, here in this example it would be employee/Configuration/Services.yaml
      services:
      _defaults:
      autowire: true
      autoconfigure: true
      public: false

      Company\Employee\:
      resource: '../Classes/*'
      exclude: '../Classes/Domain/Model/*'
    3. Line 7 in the above code (Company\Employee) – add <vendor>\<extension key>.
    4. Line 8 in the above code (resources) – allows the classes folder to be added as a service.
    5. Line 9 in the above code (exclude) – exclude the model folder from the service.
    6. Save the file, toggle the extension (deactivate the extension and then activate it again), or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache”, flush the cache, and check the front-end page.

    Error: Controller Action file not found

    1. The controller action template has not been created yet in the resource > private > templates folder and that is the reason the above error is occurring.
    2. Create the template file on path: <extension_directory>/Resources/Private/Templates/<controller_name>/<action_name>.html, here in this example the path is employee/Resources/Private/Templates/Employ/index.html
      <div class="section">
      <div class="container">
      <div>
      <h1>List Of Employee</h1>
      </div>
      </div>
      </div>
    3. Add the above code and save the file and refresh the front-end page and you will see the HTML is rendering, if still, you can’t see the updates then please flush the cache.

      Hurray!! Now the page does render with the added plugin.
      Still, the employee list does not render, because the query has not been written and the templating is also not implemented. Now let’s do it to render the record on the page.

    Fetch records & render them on the front-end

    1. To fetch the records that have been added to the TYPO3 admin panel, need to write the TYPO3 extbase query, and that needs models & repositories which have not been created yet.
    2. Let’s create a model and repository first.

    Models

    1. All classes of the domain model should inherit from the class \TYPO3\CMS\Extbase\DomainObject\AbstractEntity.
    2. Objects stored in the database are usually entities as they can be identified by the uid and are persisted, therefore having continuity.
    3. It is possible to define models that are not persisted in the database. However, in the most common use cases, you want to save your model to the database and load it from there.
    4. A public getter takes precedence over a public property. Getters have the advantage that you can make the properties themselves protected and decide which ones should be mutable.
    5. For more details, you can check this ref.link – Ref. link: https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/Extbase/Reference/Domain/Model.html

    For each table, there will be one model file. Here in this example, 2 models will be created as 2 tables are created for the employee extension.
    Then file path will be : <extension_directory>/Classes/Domain/Model/<table>.php

    Below is the model file for the table of employ, file path is: employee\Classes\Domain\Model\Employ.php

    <?php
    
    declare(strict_types=1);
    
    namespace Company\Employee\Domain\Model;
    
    use DateTime;
    use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
    use TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
    use TYPO3\CMS\Extbase\Domain\Model\FileReference;
    use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
    use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
    use Company\Employee\Domain\Model\Education;
    
    /**
     * Employ Model
     */
    class Employ extends AbstractEntity
    {
        /**
         * firstName
         *
         * @var string
         */
        protected $firstName = '';
    
        /**
         * lastName
         *
         * @var string
         */
        protected $lastName = '';
    
        /**
         * gender
         *
         * @var string
         */
        protected $gender = '';
    
        /**
         * birthDate
         *
         * @var DateTime
         */
        protected $birthDate;
    
        /**
         * joiningDate
         *
         * @var DateTime
         */
        public $joiningDate;
    
        /**
         * @Lazy
         * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
         */
        protected ObjectStorage $image;
    
        /**
         * bio
         *
         * @var string
         */
        protected $bio = '';
    
        /**
         * experiance
         *
         * @var string
         */
        protected $experiance = '';
    
        /**
         * salary
         *
         * @var string
         */
        protected $salary = '';
    
        /**
         * integer
         *
         * @var integer
         */
        protected $languages = '';
    
        /**
         * country
         *
         * @var string
         */
        protected $country = '';
    
        /**
         * @Lazy
         * @var ObjectStorage<Education>
         */
        protected $educations;
    
    
    
    
        public function __construct()
        {
            $this->initializeObject();
    
        }
    
        public function initializeObject(): void
        {
            $this->educations = $this->educations ?? new ObjectStorage();
            $this->image = $this->image ?? new ObjectStorage();
        }
    
        /**
         * Returns the firstName
         *
         * @return string
         */
        public function getFirstName(): string
        {
            return $this->firstName;
        }
    
        /**
         * Sets the firstName
         *
         * @param string $firstName
         * @return void
         */
        public function setFirstName(string $firstName): void
        {
            $this->firstName = $firstName;
        }
    
    
        /**
         * Returns the lastName
         *
         * @return string
         */
        public function getLastName(): string
        {
            return $this->lastName;
        }
    
        /**
         * Sets the lastName
         *
         * @param string $lastName
         * @return void
         */
        public function setLastName(string $lastName): void
        {
            $this->lastName = $lastName;
        }
    
    
        /**
         * Returns the gender
         *
         * @return string
         */
        public function getGender(): string
        {
            return $this->gender;
        }
    
        /**
         * Sets the gender
         *
         * @param string $gender
         * @return void
         */
        public function setGender(string $gender): void
        {
            $this->gender = $gender;
        }
    
    
        /**
         * Get birthDate
         *
         * @return DateTime|null
         */
        public function getBirthDate(): ?DateTime
        {
            return $this->birthDate;
        }
    
        /**
         * Set birthDate
         *
         * @param DateTime $birthDate
         */
        public function setBirthDate(DateTime $birthDate): void
        {
            $this->birthDate = $birthDate;
        }
    
        /**
         * Get year of birthDate
         *
         * @return int
         */
        public function getYearOfBirthDate(): int
        {
            if ($this->getBirthDate()) {
                return (int) $this->getBirthDate()->format('Y');
            }
            return 0;
        }
    
        /**
         * Get month of birthDate
         *
         * @return int
         */
        public function getMonthOfBirthDate(): int
        {
            if ($this->getBirthDate()) {
                return (int) $this->getBirthDate()->format('m');
            }
            return 0;
        }
    
        /**
         * Get day of birthDate
         *
         * @return int
         */
        public function getDayOfBirthDate(): int
        {
            if ($this->birthDate) {
                return (int) $this->birthDate->format('d');
            }
            return 0;
        }
    
    
    
    
        /**
         * Set joiningDate
         *
         * @param DateTime $joiningDate
         */
        public function setJoiningDate(DateTime $joiningDate): void
        {
            $this->joiningDate = $joiningDate;
        }
    
        /**
         * Get year of joiningDate
         *
         * @return int
         */
        public function getYearOfJoiningDate(): int
        {
            if ($this->getJoiningDate()) {
                return (int) $this->getJoiningDate()->format('Y');
            }
            return 0;
        }
    
        /**
         * Get month of joiningDate
         *
         * @return int
         */
        public function getMonthOfJoiningDate(): int
        {
            if ($this->getJoiningDate()) {
                return (int) $this->getJoiningDate()->format('m');
            }
            return 0;
        }
    
        /**
         * Get day of joiningDate
         *
         * @return int
         */
        public function getDayOfJoiningDate(): int
        {
            if ($this->joiningDate) {
                return (int) $this->joiningDate->format('d');
            }
            return 0;
        }
    
    
        /**
         * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
         */
        public function getImage(): ObjectStorage
        {
            return $this->image;
        }
    
        /**
         * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $image
         */
        public function setImage(ObjectStorage $image): self
        {
            $this->image = $image;
            return $this;
        }
    
    
    
        /**
         * Returns the bio
         *
         * @return string
         */
        public function getBio(): string
        {
            return $this->bio;
        }
        /**
         * Sets the bio
         *
         * @param string $bio
         * @return void
         */
        public function setBio(string $bio): void
        {
            $this->bio = $bio;
        }
    
        /**
         * Returns the experiance
         *
         * @return string
         */
        public function getExperiance(): string
        {
            return $this->experiance;
        }
        /**
         * Sets the experiance
         *
         * @param string $experiance
         * @return void
         */
        public function setExperiance(string $experiance): void
        {
            $this->experiance = $experiance;
        }
    
    
        /**
         * Returns the salary
         *
         * @return string
         */
        public function getSalary(): string
        {
            return $this->salary;
        }
        /**
         * Sets the salary
         *
         * @param string $salary
         * @return void
         */
        public function setSalary(string $salary): void
        {
            $this->salary = $salary;
        }
    
     
        /**
         * Returns the languages
         *
         * @return integer
         */
        public function getLanguages(): int
        {
            return $this->languages;
        }
        /**
         * Sets the languages
         *
         * @param integer $languages
         * @return void
         */
        public function setLanguages(int $languages): void
        {
             
            $this->languages = $languages;
        }
    
        /**
         * Returns the country
         *
         * @return string
         */
        public function getCountry(): string
        {
            return $this->country;
        }
        /**
         * Sets the country
         *
         * @param string $country
         * @return void
         */
        public function setCountry(string $country): void
        {
            $this->country = $country;
        }
    
    
        public function addEducations(Education $educations): void
        {
            $this->educations = $this->getEducations();
            $this->educations->attach($educations);
        }
    
        /**
         * @return ObjectStorage<Education>
         */
        public function getEducations(): ObjectStorage
        {
            if ($this->educations instanceof LazyLoadingProxy) {
                $this->educations->_loadRealInstance();
            }
    
            if ($this->educations instanceof ObjectStorage) {
                return $this->educations;
            }
    
            return $this->educations = new ObjectStorage();
        }
    
    
        public function removeEducations(Education $educations): void
        {
            $this->educations = $this->getEducations();
            $this->educations->detach($educations);
        }
    
        /**
         * @param ObjectStorage<Education> $educations
         */
        public function setEducations(ObjectStorage $educations): void
        {
            $this->educations = $educations;
        }
    }

    Below is the model file for the table of education, file path is: employee\Classes\Domain\Model\Education.php

    <?php
    
    declare(strict_types=1);
    
    namespace Company\Employee\Domain\Model;
    
    use DateTime;
    use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
    use TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
    use TYPO3\CMS\Extbase\Domain\Model\FileReference;
    use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
    use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
    
    /**
     * Education Model
     */
    class Education extends AbstractEntity
    {
        /**
         * title
         *
         * @var string
         */
        protected $title = '';
    
        /**
         * rollnumber
         *
         * @var string
         */
        protected $rollnumber = '';
    
        /**
         * startDate
         *
         * @var DateTime
         */
        protected $startDate;
    
        /**
         * endDate
         *
         * @var DateTime
         */
        protected $endDate;
    
        /**
         * cgpa
         *
         * @var float
         */
        protected $cgpa = '';
    
        /**
         * university
         *
         * @var string
         */
        protected $university = '';
    
         /**
         * @var Employ
         */
        protected Employ $employ; 
        protected Employ|null $secondEmploy; 
        
        public function __construct()
        {
            $this->initializeObject();
    
        }
    
        public function initializeObject(): void
        {
             
        }
    
        /**
         * Returns the title
         *
         * @return string
         */
        public function getTitle(): string
        {
            return $this->title;
        }
    
        /**
         * Sets the title
         *
         * @param string $title
         * @return void
         */
        public function setTitle(string $title): void
        {
            $this->title = $title;
        }
    
    
        /**
         * Returns the rollnumber
         *
         * @return string
         */
        public function getRollnumber(): string
        {
            return $this->rollnumber;
        }
    
        /**
         * Sets the rollnumber
         *
         * @param string $rollnumber
         * @return void
         */
        public function setRollnumber(string $rollnumber): void
        {
            $this->rollnumber = $rollnumber;
        }
    
        
        /**
         * Get startDate
         *
         * @return DateTime|null
         */
        public function getStartDate(): ?DateTime
        {
            return $this->startDate;
        }
    
        /**
         * Set startDate
         *
         * @param DateTime $startDate
         */
        public function setStartDate(DateTime $startDate): void
        {
            $this->startDate = $startDate;
        }
    
        /**
         * Get year of startDate
         *
         * @return int
         */
        public function getYearOfStartDate(): int
        {
            if ($this->getStartDate()) {
                return (int) $this->getStartDate()->format('Y');
            }
            return 0;
        }
    
        /**
         * Get month of startDate
         *
         * @return int
         */
        public function getMonthOfStartDate(): int
        {
            if ($this->getStartDate()) {
                return (int) $this->getStartDate()->format('m');
            }
            return 0;
        }
    
        /**
         * Get day of startDate
         *
         * @return int
         */
        public function getDayOfStartDate(): int
        {
            if ($this->startDate) {
                return (int) $this->startDate->format('d');
            }
            return 0;
        }
    
    
    
    
        /**
         * Set endDate
         *
         * @param DateTime $endDate
         */
        public function setEndDate(DateTime $endDate): void
        {
            $this->endDate = $endDate;
        }
    
        /**
         * Get year of endDate
         *
         * @return int
         */
        public function getYearOfEndDate(): int
        {
            if ($this->getEndDate()) {
                return (int) $this->getEndDate()->format('Y');
            }
            return 0;
        }
    
        /**
         * Get month of endDate
         *
         * @return int
         */
        public function getMonthOfEndDate(): int
        {
            if ($this->getEndDate()) {
                return (int) $this->getEndDate()->format('m');
            }
            return 0;
        }
    
        /**
         * Get day of endDate
         *
         * @return int
         */
        public function getDayOfEndDate(): int
        {
            if ($this->endDate) {
                return (int) $this->endDate->format('d');
            }
            return 0;
        }
    
    
        
        /**
         * Returns the cgpa
         *
         * @return float
         */
        public function getCgpa(): float
        {
            return $this->cgpa;
        }
        /**
         * Sets the cgpa
         *
         * @param float $cgpa
         * @return void
         */
        public function setCgpa(float $cgpa): void
        {
            $this->cgpa = $cgpa;
        }
    
    
    
        /**
         * Returns the university
         *
         * @return string
         */
        public function getUniversity(): string
        {
            return $this->university;
        }
        /**
         * Sets the university
         *
         * @param string $university
         * @return void
         */
        public function setUniversity(string $university): void
        {
            $this->university = $university;
        }
    }

    Persistence

    1. Establish the connection between the database table and model by persistence.
    2. The mapping between the database table and the model can be configured into the file <extension_directory>/Configuration/Extbase/Persistence/Classes.php, here in example employee/Configuration/Extbase/Persistence/Classes.php. The mapping in this file overrides the automatic mapping by naming convention.
      <?php
      declare(strict_types=1);
      return [
      \Company\Employee\Domain\Model\Employ::class => [
      'tableName' => 'tx_employee_domain_model_employ',
      ],
      \Company\Employee\Domain\Model\Education::class => [
      'tableName' => 'tx_employee_domain_model_education',
      ],
      ];

      Here the defined class is \Company\Employee\Domain\Model\Employ , and the path of it is: <vendor_name>\<extension_name>\Domain\Model\<table_name>

    Repository

    1. All Extbase repositories inherit from \TYPO3\CMS\Extbase\Persistence\Repository.
    2. A repository is always responsible for precisely one type of domain object.
    3. The naming of the repositories is important: If the domain object is, for example, Employ (with the full name \Company\Employee\Classes\Domain\Model\Employ), then the corresponding repository is named EmployRepository (with the full name \Company\Employee\Classes\Domain\Repository\EmployRepository).
    4. The \TYPO3\CMS\Extbase\Persistence\Repository already offers a large number of useful functions.
    5. Therefore, in simple classes that extend the Repository class and leave the class empty otherwise is sufficient.
    6. Let’s create empty repositories for each table, so here 2 repositories will be created, one for the Employ table and another for the Education table

    Below is the repository file for the table of employ, file path is: \Company\Employee\Classes\Domain\Repository\EmployRepository.php

    <?php
    declare(strict_types=1);
    namespace Company\Employee\Domain\Repository;
    use TYPO3\CMS\Extbase\Persistence\Repository;

    /**
    * The repository for Employ
    */
    class EmployRepository extends Repository
    {
    }

    Below is the repository file for the table of education, file path is: \Company\Employee\Classes\Domain\Repository\EmployRepository.php

    <?php
    declare(strict_types=1);
    namespace Company\Employee\Domain\Repository;
    use TYPO3\CMS\Extbase\Persistence\Repository;
    /**
    * The repository for Education
    */
    class EducationRepository extends Repository
    {

    }

    After creating the models and repositories, now it’s time for the templating. Fetch the records from the controller and assign those records to the template. 

    Template of the employee list page

    1. To fetch the records from the table to the template, first, inject that repository into the controller (Controller path: employee\Classes\Controller\EmployController.php).
    2. Let’s inject the repository into the controller.
      /**
      * employRepository
      *
      * @var \Company\Employee\Domain\Repository\EmployRepository
      */
      protected $employRepository = null;
      /**
      * @param \Company\Employee\Domain\Repository\EmployRepository $employRepository
      */
      public function injectEmployRepository(\Company\Employee\Domain\Repository\EmployRepository $employRepository)
      {
      $this->employRepository = $employRepository;
      }
    3. Use the injected repository to fetch all the records of the employ table in list action by writing the code below
      /**
      * action list
      *
      * @return string|object|null|void
      */
      public function indexAction(): ResponseInterface //ResponseInterface is the response type here
      {
      // This will find all the records from the employ repository
      $employs = $this->employRepository->findAll();
      // $employs is the result and it is assigning to the variable "employs", now employs will be used in templating
      $this->view->assign('employs', $employs);
      return $this->htmlResponse();
      }
      Check details about ResponseInterface here: https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/Extbase/Reference/View/Index.html
    4. Below is the final code of the EmployController.php after added the indexAction (employee\Classes\Controller\EmployController.php)


      <?php
      declare(strict_types=1);
      namespace Company\Employee\Controller;
      use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
      use Psr\Http\Message\ResponseInterface;
      class EmployController extends ActionController
      {
      /**
      * employRepository
      *
      * @var \Company\Employee\Domain\Repository\EmployRepository
      */
      protected $employRepository = null;
      /**
      * @param \Company\Employee\Domain\Repository\EmployRepository $employRepository
      */
      public function injectEmployRepository(\Company\Employee\Domain\Repository\EmployRepository $employRepository)
      {
      $this->employRepository = $employRepository;
      }


      /**
      * action list
      *
      * @return string|object|null|void
      */
      public function indexAction(): ResponseInterface
      {
      $employs = $this->employRepository->findAll();
      $this->view->assign('employs',$employs);
      return $this->htmlResponse();
      }
      }
    5. Now we want to select the ID of the page or folder in which we have stored the records and want to list them on front-end, we can do that by adding code in setup.typoscript file, and create one more typoscript file which is constants.typoscript file on the same path where the setup.typoscript has been created. We already have called the Typoscript folder in employee/Configuration/TCA/Overrides/sys_template.php file so no need to call the constants.typoscript file separately.
      1. Add the below code to the constants.typoscript file
        plugin.tx_employee{
        persistence {
        # cat=plugin.tx_employee//a; type=string; label=Set Root Page ID
        storagePid =
        }
        }
      2. This will add the category plugin.tx_employee. You can check it by going to SIte Management > Typoscript > select the root page, select the “constant editor” from the left top dropdown, and you will see the option “plugin.tx_employee” has been added in the category dropdown, select that option.

        After selecting the “plugin.tx_employee” option, you will see the below screen, and will get an option to add the ID of the page or folder you want to fetch the records from.
      3. Now write the below code in setup.typoscript to bind the records in front-end.
        plugin.tx_employee{
            persistence { 
                storagePid = {$plugin.tx_employee.persistence.storagePid}
            }
        }
    6. Let’s do templating and render the records with only the required information in the front-end.
    7. Here in this example of the “employee” extension, the indexAction() of the EmployController.php has been used,  so the template that should be altered is the template of the EmployController.php’s indexAction.
    8. Create the template file, and the file path is:<extension_directory>\Resources\Private\Templates\<folder_name_derived_from_the_controller_name_without_controller_suffix>\Index.html

    Below is the template file code: file path is employee\Resources\Private\Templates\Employ\Index.html

    <div class="employee_list_section">
        <div class="container">
            <div class="employee_list">
                <f:for each="{employs}" as="emp">
                        <div class="employee row">                      
                                <div class="emp_img">
                                    <f:if condition="{emp.image}">
                                        <f:for each="{emp.image}" as="img">
                                            <f:image image="{img}" alt="{emp.firstName}" width="200c" height="200c" />
                                        </f:for>
                                    </f:if>
                                </div>
                            <div class="emp_info">
                                <div class="">
                                    <p><strong>Name : </strong>{emp.firstName} {emp.lastName}</p>
                                </div>
                                <div class="">
                                    <p><strong>Gender : </strong>{emp.gender == 1 ? "Male" :"Female"}</p>
                                </div>
                                <div class="emp_bio">
                                    <p><strong></strong>{emp.bio}</p>
                                </div>
                            </div>
                        </div>
                </f:for>
            </div>
        </div>
    </div>

    After altering the template file and adding some dummy data, flush the cache and refresh the front-end page and you will see the output in the below screenshot.

    Here the dummy content and the dummy image have been used to just show the example. This is just a listing of the data we are getting that we stored in the TYPO3 admin panel. The design is required for the employee list.

    Let’s add some style to the page.

    Add CSS & JS into the extension

    1. We can use Bootstrap for some basic styles. ( You can find bootstrap CSS and js files from here – https://getbootstrap.com/docs/5.3/getting-started/download/ )
    2. Take the CSS and JS files from bootstrap and upload the bootstrap.min.css file to the employee/Resources/Public/css folder and upload the bootstrap.min.js file to the employee/Resources/Public/js folder.
    3. To include the CSS/JS files specifically in TYPO3, write a typoscript.
    4. We already have a typoscript file here and that is employee/Configuration/TypoScript/setup.typoscript
    5. Write the code as seen in the screenshot below

      Below is the final code of the setup.typoscript file after including the CSS and JS files as above screenshot
      config.contentObjectExceptionHandler = 0
      # DEfine page config
      page = PAGE
      page {
      10 = FLUIDTEMPLATE
      10 {
      templateName = TEXT
      templateName {
      cObject = TEXT
      cObject {
      data = pagelayout
      required = 1
      case = uppercamelcase
      split {
      token = pagets__
      cObjNum = 1
      1.current = 1
      }
      }
      ifEmpty = Default
      }
      templateRootPaths {
      0 = EXT:employee/Resources/Private/Templates/Page/
      1 = {$page.fluidtemplate.templateRootPath}
      }
      partialRootPaths {
      0 = EXT:employee/Resources/Private/Partials/Page/
      1 = {$page.fluidtemplate.partialRootPath}
      }
      layoutRootPaths {
      0 = EXT:employee/Resources/Private/Layouts/Page/
      1 = {$page.fluidtemplate.layoutRootPath}
      }
      dataProcessing {
      10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
      10 {
      references.fieldName = media
      }
      }
      }
      includeCSS {
      bootstrap = EXT:employee/Resources/Public/css/bootstrap.min.css
      owl = EXT:employee/Resources/Public/css/owl.carousel.min.css
      custom = EXT:employee/Resources/Public/css/custom.css
      }

      includeJS {
      jquery = EXT:employee/Resources/Public/js/jquery-3.7.1.min.js
      owl = EXT:employee/Resources/Public/js/owl.carousel.min.js
      bootstrap = EXT:employee/Resources/Public/js/bootstrap.min.js
      custom = EXT:employee/Resources/Public/js/custom.js
      }
      }


      lib.dynamicContent = COA
      lib.dynamicContent {
      5 = LOAD_REGISTER
      5 {
      colPos.cObject = TEXT
      colPos.cObject {
      field = colPos
      ifEmpty.cObject = TEXT
      ifEmpty.cObject {
      value.current = 1
      ifEmpty = 0
      }
      }
      pageUid.cObject = TEXT
      pageUid.cObject {
      field = pageUid
      ifEmpty.data = TSFE:id
      }
      contentFromPid.cObject = TEXT
      contentFromPid.cObject {
      data = DB:pages:{register:pageUid}:content_from_pid
      data.insertData = 1
      }
      wrap.cObject = TEXT
      wrap.cObject {
      field = wrap
      }
      }
      20 = CONTENT
      20 {
      table = tt_content
      select {
      includeRecordsWithoutDefaultTranslation = 1
      orderBy = sorting
      where = {#colPos}={register:colPos}
      where.insertData = 1
      pidInList.data = register:pageUid
      pidInList.override.data = register:contentFromPid
      }
      stdWrap {
      dataWrap = {register:wrap}
      required = 1
      }
      }
      90 = RESTORE_REGISTER
      }


      // plugin.employee_employlist{
      // persistence {
      // storagePid = {$plugin.employee_employlist.persistence.storagePid}
      // }
      // }

      // plugin.employee_jobdetail{
      // persistence {
      // storagePid = {$plugin.employee_jobdetail.persistence.storagePid}
      // }
      // }

      plugin.tx_employee{
      persistence {
      storagePid = {$plugin.tx_employee.persistence.storagePid}
      }
      }
      module.tx_employee {
      persistence {
      storagePid = {$module.tx_employee.persistence.storagePid}
      }
      view {
      templateRootPaths.0 = EXT:employee/Resources/Private/Backend/Templates/
      templateRootPaths.1 = {$module.tx_employee.view.templateRootPath}
      partialRootPaths.0 = EXT:employee/Resources/Private/Backend/Partials/
      partialRootPaths.1 = {$module.tx_employee.view.partialRootPath}
      layoutRootPaths.0 = EXT:employee/Resources/Private/Backend/Layouts/
      layoutRootPaths.1 = {$module.tx_employee.view.layoutRootPath}
      }
      }

    After including the CSS and JS files, flush the cache and reload the front-end page and the style from Bootstrap will be loaded as you can see in the screenshot below.

    Here the employee list needs some more modification to its look and feel, let’s implement a slider of employees with some style.

    1. To implement the slider, we have the option to use the owl carousel – https://owlcarousel2.github.io/OwlCarousel2/ , you can download owl.carousel.min.css and owl.carousel.min.js files from the given URL.
    2. Now upload these bootstrap files to the corresponding folders of CSS and JS to the path employee\Resources\Public\
    3. Now include them in the setup.typoscript file as we included the bootstrap files.
    4. Then create a JS file to write custom JS code. Let’s create a custom.js file here (you can name the file anything you want) to the path employee\Resources\Public\js\
    5. Write the JS code given below by using the owl carousel in the employee\Resources\Public\js\custom.js file
      $(document).ready(function(){
      var employee_list = $('.employee_list_section .employee_list');
      employee_list.owlCarousel({
      responsiveClass: true,
      dots: false,
      nav:true,
      responsive: {
      0: {
      items: 1,
      nav: true,
      },
      578: {
      items: 1,
      nav: true,
      },
      768: {
      items: 2,
      nav: true,
      },
      992: {
      items: 3,
      nav: true,
      },
      1200: {
      items: 3,
      nav: true,
      }
      },

      });
      });
    6. Now upload the jQuery (you can download it from here – https://jquery.com/download/) to the employee\Resources\Public\js\ folder and include it in the setup.typoscript.
    7. Now create a CSS file to write custom CSS code, let’s create a custom.css file, and include it in setup.typoscript file. Below is the code of custom.css file (employee\Resources\Public\css\custom.css)
      .employee_list_section .employee .row {
        display: flex;
        flex-direction: column;
        width: auto;
      }
      
      .employee_list_section .employee_list {
        display: flex;
        overflow: hidden;
      }
      
      .employee_list .owl-stage {
        display: flex;
      }
      
      .employee_list .owl-item {
        padding: 15px;
        background: #fff;
        box-shadow: 0 0pc 50px 0 rgba(2, 2, 48, 0.06);
        margin: 45px;
      }
      
      .employee_list_section .emp_img img {
        width: 100%;
        height: auto;
        object-fit: cover;
      }
      
      .employee_list_section .emp_img {
        margin-bottom: 20px;
      }
      
      .emp_bio {
        min-height: 135px;
      }
      
      .employee_list {
        position: relative;
      }
      
      .employee_list .owl-prev,
      .employee_list .owl-next {
        border: 1px solid gray;
        border-radius: 100%;
        padding: 0 16px;
        background: transparent;
      }
      
      .employee_list .owl-prev span,
      .employee_list .owl-next span {
        font-size: 30px;
      }
      
      .employee_list .owl-prev {
        position: absolute;
        left: 0;
        top: 50%;
      }
      
      .employee_list .owl-next {
        position: absolute;
        right: 0;
        top: 50%;
      }
    8. Included all the CSS and JS files in setup.typoscript file, and the code is below
       includeCSS {
      bootstrap = EXT:employee/Resources/Public/css/bootstrap.min.css
      owl = EXT:employee/Resources/Public/css/owlowl.carousel.min.css
      custom = EXT:employee/Resources/Public/css/custom.css
      }

      includeJS {
      jquery = EXT:employee/Resources/Public/js/jquery-3.7.1.min.js
      bootstrap = EXT:employee/Resources/Public/js/bootstrap.min.js
      owl = EXT:employee/Resources/Public/js/owl.carousel.min.js
      custom = EXT:employee/Resources/Public/js/custom.js
      }
    9. Now flush the cache and reload the front-end and you will see a working slider of employee’s data,

    Front-end plugin in extension for the detail page of each employee

    Configure the new plugin

    Let’s create a front-end plugin to create detail page for the employees in front-end.

    Plugin Configuration

    1. Configure the plugin in ext_localconf.php file at the root of the extension. (Path: employee/ext_localconf.php). Add the below code to the file ext_localconf.php you already have created at the time of creating the employee list plugin into the root directory.
      ExtensionUtility::configurePlugin(
      // extension key or extension name in UpperCamelCase (if the extension name is my products then the name here will be MyProducts)
      'Employee',
      // A unique identifier for your plugin in UpperCamelCase that will be the name of your plugin
      'Employdetail',
      // all actions
      [EmployController::class => 'detail'],
      // non-cacheable actions
      [EmployController::class => 'detail'],
      );
    2. As you can see in the above screenshot, the defined action is “detail” and the defined controller is “EmployController” which has been created already at the time of the employee list plugin was created. Add the below code to define the detail action in the EmployController.php file.
          /**
      * action detail
      *
      * @return string|object|null|void
      */
      public function detailAction(): ResponseInterface
      {
      $this->view->assign('employee details');
      return $this->htmlResponse();
      }
      In the above code, the detail action has been defined only and not fetching any records for now. Will do that at the time of templating.

    Plugin Registration

    1. Register the plugin by adding it to the tt_content file to make the plugin available in cType (Dropdown). (file path: employee/Configuration/TCA/Overrides/tt_content.php)
      \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
              // extension name, matching the PHP namespaces (but without the vendor)
              'Employee',
              // arbitrary, but unique plugin name (not visible in the backend), must be the same as used in configurePlugin()
              'Employdetail',
              // plugin title, as visible in the drop-down in the backend, use "LLL:" for localization
              'Detail Of Employee',
              // icon identifier
              'EXT:employee/Resources/Public/Icons/Extension.png',
          );
    2. Below is the screenshot of the final code of the tt_content file after adding the employee list and employee-detail plugin as described above.

    Make the plugin available to the content wizard.

    1. Add code in tsconfig file to make the plugin available to the wizard.
    2. We already have created the tsconfig file on path: employee/Configuration/page.tsconfig, replace the existing code with the below code.
      mod.wizards.newContentElement.wizardItems {
      plugins {
      elements {
      // plugin signature: It is the extension name in lowercase without underscores, followed by one underscore, followed by the plugin identifier in lowercase without underscores.
      employee_employlist {
      // icon identifier: It will load the icon that is identified write the icon identifier for this extension (the explanation of this topic is below, please continue to follow the doc.)
      iconIdentifier = employee
      // title: It will be rendered as the title of the plugin in the wizard.
      title = Emplyees List
      // description: It will rendered as the description of the plugin in the wizard.
      description = List of Employees
      tt_content_defValues {
      CType = list
      // list_type: It will use the same plugin identifier.
      list_type = employee_employlist
      }
      }

      // Employee detail plugin
      employee_employdetail {
      iconIdentifier = employee
      title = Emplyees Detail
      description = Detail of Employees
      tt_content_defValues {
      CType = list
      list_type = employee_employdetail
      }
      }

      // To add the plugin to the list add the code as seen in the below line
      show := addToList(employee_employlist, employee_employdetail)
      }
      }
      }
    3. Now clean the cache and open wizard and you will find the plugin is available in wizard now.
    4. If you want to add a different icon for the employee detail plugin, then you can define the icon in employee/Configuration/Icons.php file, upload the icon image to the path before define it.
      <?php
      declare(strict_types=1);
      use TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider;

      return [
      'employee' => [
      'provider' => BitmapIconProvider::class,
      'source' => 'EXT:employee/Resources/Public/Icons/Extension.png',
      'spinning' => true,
      ],

      // define a different icon for the Employee list plugin
      'employee list' => [
      'provider' => BitmapIconProvider::class,
      'source' => 'EXT:employee/Resources/Public/Icons/EmployeeList.png',
      'spinning' => true,
      ],

      ];

      And assign that icon to the “iconIdentifier” in the tsconfig file that is already added in above step, (\employee\Configuration\page.tsconfig) as highlighted in the below screenshot.
    5. You will see the icon is now loaded on the wizard for the particular plugin as seen in the screenshot below

    How to use the plugin in the front-end?

    1. Create a new page for the “Employee detail” under the “Employee” page, click on the “+Content” button and you will see the wizard, go to Plugins > select the Employees Detail plugin and you will be redirected to the screen as seen in the screenshot below.

      just save the page.

    Add the link in the list page to open the detail page

    1. Open the template file for the employee (file path: \employee\Resources\Private\Templates\Employ\Index.html).
    2. Add the below code in the template file after the “bio” section to provide a link for each of the employees to open their corresponding detail page.
      <div class="emp_read_more">
      <f:link.page pageUid="14" additionalParams="{uid: emp.uid}">Read More</f:link.page>
      </div>
    3. Below is the screenshot of the template file after adding the above code:
    4. In additionalParams attribute the uid of the employee table has been passed to fetch the data of the employee by that uid.
    5. As you can see in the above screenshot, the pageUid is 14 which has been put static for now. That is the ID of the page on which the details will be rendered, the page “Employee Detail” as you can see in the below screenshot.
    6. Now go to the employee list in your browser and you will see the “Read More” link as seen in the screenshot below
    7. Let’s add the below CSS for the Read More button in the file employee\Resources\Public\css\custom.css
        .employee_list .emp_read_more{
          margin-bottom: 10px;
          margin-top: 10px;
          display: inline-block;
        }
      
        .emp_read_more a {
          background: #000;
          color: #fff;
          padding: 15px;
          border-radius: 10px;
          margin-bottom: 10px;
          text-decoration: none;
      }
    8. After adding the above CSS, the employee slider will look like this

    Template of the employee detail page

    1. The repository is already injected in the controller as we injected it at the time when the list plugin was created, so there is no need to inject it again to fetch the records from the table to the template.
    2. Use the injected repository to fetch all the records of the employ table in detail action by writing the code below in the EmployController.php file (\employee\Classes\Controller\EmployController.php)
      /**
           * action detail
           *
           * @return string|object|null|void
           */
          public function detailAction(): ResponseInterface
          {
              $uid = GeneralUtility::_GP('uid');
      
              if ($uid) {
                  $details = $this->employRepository->findByUid($uid);
                  $this->view->assign('details', $details);
              } else {
                  $this->view->assign('details', null);
              }
              
              return $this->htmlResponse();
          }

      The details variable will be used in the template to render the data of employee details.
    3. Here the GeneralUtility has been used to fetch the uid from the detail. You will need to import the GeneralUtility in the controller file to use it as you can see in the below code:
      use TYPO3\CMS\Core\Utility\GeneralUtility;
    4. You can see the final code of the controller file in the below screenshot after adding the GeneralUtility and the detail action.
    5. Let’s do templating and render the details of the employees in the front-end.
    6. Here the detailAction() of the EmployController.php has been used, so the template that should be altered is the template of the EmployController.php’s detailAction.
    7. Create a template file and the template file path is: \employee\Resources\Private\Templates\Employ\Detail.html
    8. Below is the code of the detail.html template file
      <div class="section">
          <div class="container">
              <div class="row">
                  <f:if condition="{details}">
                      <f:then>
                          <h1 class="text-center">{details.firstName} {details.lastName}</h1>                  
                          <f:if condition="{details.image}">
                              <f:for each="{details.image}" as="img">
                                  <div class="image-wrapper text-center">
                                      <f:image image="{img}" alt="{details.firstName}" />
                                  </div>
                              </f:for>
                          </f:if>                   
                          <div class="">
                              <div class="">
                                  <h3>Basic Details</h3>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>First name </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.firstName}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Last name </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.lastName}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Gender </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.gender == 1 ? "Male":"Female"}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Birth Date </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.date>{details.birthDate}</f:format.date>
                                          </p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Joining Date </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.date>{details.joiningDate}</f:format.date>
                                          </p>
                                      </div>
                                  </div>
      
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Salary </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.salary}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Country </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.country}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Languages </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.languages}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Bio</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.bio}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Experiance</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.html>{details.experiance}</f:format.html>
                                          </p>
                                      </div>
                                  </div>
                              </div>
                              <div class="">
                                  <h3>Educations</h3>
                                  <f:if condition="{details.educations}">
                                      <div class="row">
                                          <div class="col-2"><strong>Title</strong></div>
                                          <div class="col-2"><strong>Roll Number</strong></div>
                                          <div class="col-2"><strong>CGPA</strong></div>
                                          <div class="col-2"><strong>University</strong></div>
                                          <div class="col-2"><strong>Start Date</strong></div>
                                          <div class="col-2"><strong>End Date</strong></div>
                                      </div>
                                      <f:for each="{details.educations}" as="edu">
                                          <div class="row">
                                              <div class="col-2">
                                                  <p>{edu.title}</p>
                                              </div>
                                              <div class="col-2">
                                                  <p>{edu.rollNumber}</p>
                                              </div>
                                              <div class="col-2">
                                                  <p>{edu.cgpa}</p>
                                              </div>
                                              <div class="col-2">
                                                  <p>{edu.university}</p>
                                              </div>
                                              <div class="col-2">
                                                  <p>
                                                      <f:format.date>{edu.startDate}</f:format.date>
                                                  </p>
                                              </div>
                                              <div class="col-2">
                                                  <p>
                                                      <f:format.date>{edu.endDate}</f:format.date>
                                                  </p>
                                              </div>
                                          </div>
                                      </f:for>
                                  </f:if>
                              </div>
                          </div>
                      </f:then>
                      <f:else>
                          <div class="alert alert-error">
                              Employee not found!!
                          </div>
                      </f:else>
                  </f:if>
              </div>
          </div>
      </div>
      Save the template file, flush the cache, reload the page in the front-end, and click on the Read More button for any of the employees and you will be redirected to the details page of that particular employee as you can see in the below screenshot.
    9. Now let’s make the pageUid to be dynamic in the file – \employee\Resources\Private\Templates\Employ\Index.html. To make that pageUid dynamic, the Flexform should be added. In the below section, Flexform has been introduced.

    FlexForm with Plugin

    Ref. link: ref: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/FlexForms/Index.html,
    https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/FlexForms/T3datastructure/Index.html#t3ds

    1. FlexForms can be used to store the data within an XML structure inside a single DB column.
    2. FlexForms can be used to configure content elements (CE) or plugins, but they are optional so you can create plugins or content elements without using FlexForms.
    3. Most of the configuration below is the same, whether you are adding configuration for a plugin or content element. The main difference is how addPiFlexFormValue() is used.
    4. How the flex form works
      1. In the extension, a configuration schema is defined (you will see this in “Create flex form“) and attached to one or more content elements or plugins.
      2. When the CE or plugin is added to a page, it can be configured as defined by the configuration schema.
      3. The configuration for this content element is automatically saved to tt_content.pi_flexform.
      4. The extension can read the current configuration and act according to the configuration.

    Let’s create the Flexform with xml extension as per the definition

    Create Flext form

    1. Create configuration schema. File path <extension_directory>\Configuration\FlexForms\<any_file_name>.xml
    2. Let’s create employee\Configuration\FlexForms\ListConfigs.xml, the code is given below:
      <T3DataStructure>
      <sheets>
      <sDEF>
      <ROOT>
      <sheetTitle>
      General
      </sheetTitle>
      <type>array</type>
      <el>
      <settings.detailPid>
      <TCEforms>
      <label>Detail Page</label>
      <config>
      <type>group</type>
      <allowed>pages</allowed>
      <size>1</size>
      <maxitems>1</maxitems>
      <minitems>1</minitems>
      </config>
      </TCEforms>
      </settings.detailPid>
      </el>
      </ROOT>
      </sDEF>
      </sheets>
      </T3DataStructure>
    3. Whatever you will write with settings here, for example settings.detailPid, you can get that data in settings, as this is the default variable of TYPO3.
    4. The variable detailPid is used here to store the selected detail page for the plugin.
    5. The type used here is group, and this type is used when you want to select something, here the requirement is to select the page (select the detail page).
    6. Set the min and max limit to allow the page selection. Here it is 1, so only one page can be selected.
    7. Save the file. After saving the file, assign the Flexform to the plugin by adding it into the tt_content.php file (employee/Configuration/TCA/Overrides/tt_content.php)

    Assign the flexform to the plugin

    1. Assign the flexform to the plugin, add the below code in the tt_content.php file (employee/Configuration/TCA/Overrides/tt_content.php)
      $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['employee_employlist'] = 'pi_flexform';
      
          \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue(
              'employee_employlist',
              'FILE:EXT:employee/Configuration/FlexForms/ListConfigs.xml'
          );
    2. After adding the above code, your tt_content.php file will have the code as seen in the screenshot below

      The global variable will contain the pluginSignature and this is the syntax to add the flexform: $GLOBALS[‘TCA’][‘tt_content’][‘types’][‘list’][‘subtypes_addlist’][$pluginSignature] = ‘pi_flexform’; here pluginSignature will contain extensionName_pluginName, so here it is employee_employlist.

      After this, add the flexform for that plugin signature with 2 params:
      – The first one is the plugin signature and
      – The second one is to add the path of the created flexform file for the plugin
    3. Now flush the cache, now open the page Employees > edit the plugin “List Of Employee”
    4. Now go to the Plugin tab as you can see in the screenshots below, click on the “Page” button under the Plugin options and you will see a popup, then select the detail page from the left sidebar where you want to render the details of employees, and where you have added the detail plugin. Here that page is “Employee Detail“.



      And save the page and you will see the selected detail page as seen in the screenshot below:

    Fetch the data of flexform

    1. To fetch the flexform data, go to the EmployController.php file (employee\Classes\Controller\EmployController.php)
    2. You can get the flexform data from settings. Write the below code in indexAction() of the controller
       $settings = $this->settings;
      $this->view->assign('settings', $settings);
    3. The final code of the indexAction() is :
    4. Now you can have all the data of flexform in the variable settings, and you can use that variable in template file to render the flexform data into the template file.
      Please note here that the $settings on left is the variable (you can define any variable here) and the $this->settings on the right side is the pre-defined TYPO3 variable, and we used that pre-defined variable “settings” in flexform at the time of create flexform like “<settings.detailPid>” so the detailPid would store in that pre-defined settings variable, and we are fetching that flexform data from pre-defined settings variable to the new variable settings.

    Use flexform data into the fluid template

    1. Let’s use that variable “settings” in template file to replace the static pageUid.
    2. Go to the List template file: employee\Resources\Private\Templates\Employ\Index.html and replace the static pageUid with “settings.detailPid” as you can see in the screenshot below:
    3. Save the template, flush the cache and you are done with the dynamic pageUid.

    Back-end module in the extension 

    1. After going through the doc. till here, it is now in your knowledge that we need to use the list module to perform the CRUD for employees.
    2. It doesn’t seem user-friendly for users who are not so familiar with TYPO3.
    3. So here the Backend module comes in the picture which is user-friendly, by using this, the user can do the CRUD from admin panel and it will be more convenient.
    4. There are 2 possibilities to create the backend module
      1. Create a module with extbase (extbase will used here). Ref. link: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/HowTo/BackendModule/CreateModuleWithExtbase.html#backend-modules-extbase
      2. Create a module with core functionality (no extbase will used here). Ref. link: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/HowTo/BackendModule/CreateModule.html#backend-modules-template-without-extbase
    5. Here we are going to create the backend module with extbase.

    Register back-end module

    Ref. link: https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ExtensionArchitecture/HowTo/BackendModule/ModuleConfiguration.html#backend-modules-configuration-examples

    1. The configuration of backend modules is placed in the dedicated Configuration/Backend/Modules.php configuration file.
    2. Create the file Modules.php, and the path will be: <extension_directory>\Configuration\Backend\Modules.php, here for the employee extension the path will be employee\Configuration\Backend\Modules.php
    3. Add the below code to the file
      <?php

      use Company\Employee\Controller\EmployController;

      return [
      'tx_employe' => [
      'parent' => 'web',
      'position' => ['after' => 'web_info'],
      'access' => 'user',
      'workspaces' => 'live',
      'path' => '/module/employee',
      'labels' => "LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf",
      'extensionName' => 'Employee',
      'iconIdentifier' => 'employee',
      'controllerActions' => [
      // Define the controller class names as an array key
      EmployController::class => [
      // Define all the actions for backend module
      ],
      ],
      ],
      ];
      The explanation of all the parameters is below
      1. parent: If the module should be a submodule, the parent identifier, for example, web has to be set here. Have a look at the list of available top level modules.
      2. position: The module position. Allowed values are top and bottom as well as the key-value pairs before => <identifier> and after => <identifier>.
      3. access: Can be the user (editor permissions), admin, or systemMaintainer.
      4. workspaces: Can be * (= always), live or offline. If not set, the value of the parent module – if any – is used.
      5. path: Define the path to the default endpoint. The path can be anything, but will fallback to the known /module/<mainModule>/<subModule> pattern, if not set.
      6. labels: The value can either be a static string or a locallang label reference. |, It is also possible to define the path to a locallang file. The referenced file should contain the following label keys:
        1. mlang_tabs_tab (used as module title)
        2. mlang_labels_tabdescr (used as module description)
        3. mlang_labels_tablabel (used as module short description)
      7. extensionName: The extension name in UpperCamelCase for which the module is registered. If the extension key is my_example_extension the extension name would be MyExampleExtension.
      8. iconIdentifier: The module icon identifier.
      9. controllerActions: Define the controller action pair. The array keys are the controller class names and the values are the actions, which can either be defined as an array or comma-separated list.
    4. In the above code, we have called the file locallang_employee.xlf but we haven’t created the file yet. Let’s create the file, and the file path would be employee/Resources/Private/Language/locallang_employee.xlf
      1. After creating the file add the code given below to the file
        <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
        <xliff version="1.0">
        <file source-language="en" datatype="plaintext" date="2023-02-25T07:50:45Z" product-name="t3theme">
        <body>
        <trans-unit id="mlang_tabs_tab" resname="mlang_tabs_tab">
        <source>Employees</source>
        </trans-unit>
        </body>
        </file>
        </xliff>
        You can see in the above code that we have added the id “mlang_tabs_tab” and added the label “Employees” to the source tag.
      2. To get the module’s title, it is necessary to add the id mlang_tabs_tab, as you can see in the explanation in the Modules.php file’s explanation above.
      3. You can find more explanations for the locallang_employee.xlf file in the “How to add multiple languages in TYPO3section, which will be useful if you want to add multiple languages to your TYPO3 site. But for now, you can just carry on with the given information above for the locallang_employee.xlf file.
    5. After saving the file, flush the cache and you will see the module has been added to the admin panel under the Web tab.
    6. Click on the back-end module tab, and you will face the below error.

    Action & Templates for list

    1. To resolve the above error, let’s create an action for the backend module into the EmployController.php file (employee\Classes\Controller\EmployController.php)
    2. Add the code to the EmployController.php file given below
      //import the ModuleTemplateFactory to use this
      use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;

      public function __construct(
      protected readonly ModuleTemplateFactory $moduleTemplateFactory,
      ) {
      }
      Use ModuleTemplateFactory to get the HTML response, and by using this you can have some functionalities, you can check the given reference links to understand the ModuleTemplateFactory.
      Ref. link: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/HowTo/BackendModule/CreateModuleWithExtbase.html#backend-modules-extbase, https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ApiOverview/Backend/ButtonComponents.html
      /**
      * action belist
      */
      public function belistAction(): ResponseInterface
      {
      $employs = $this->employRepository->findAll();
      $this->view->assign('employs', $employs);
      // You can set the title for the backend module with using ModuleTemplateFactory
      $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Employee List");
      $moduleTemplate->setContent($this->view->render());
      return $this->htmlResponse($moduleTemplate->renderContent());
      }
    3. The final code of the EmployController.php file after adding the belist action is:
      <?php

      declare(strict_types=1);

      namespace Company\Employee\Controller;

      use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
      use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
      use Psr\Http\Message\ResponseInterface;


      class EmployController extends ActionController
      {
      /**
      * employRepository
      *
      * @var \Company\Employee\Domain\Repository\EmployRepository
      */
      protected $employRepository = null;
      /**
      * @param \Company\Employee\Domain\Repository\EmployRepository $employRepository
      */
      public function injectEmployRepository(\Company\Employee\Domain\Repository\EmployRepository $employRepository)
      {
      $this->employRepository = $employRepository;
      }
      public function __construct(
      protected readonly ModuleTemplateFactory $moduleTemplateFactory,
      ) {
      }

      /**
      * action list
      *
      * @return string|object|null|void
      */
      public function indexAction(): ResponseInterface
      {
      $employs = $this->employRepository->findAll();
      $this->view->assign('employs', $employs);

      $settings = $this->settings;
      $this->view->assign('settings', $settings);
      return $this->htmlResponse();
      }

      /**
      * action detail
      *
      * @return string|object|null|void
      */
      public function detailAction(): ResponseInterface
      {
      $uid = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('uid');
      if ($uid) {
      $details = $this->employRepository->findByUid($uid);
      $this->view->assign('details', $details);
      } else {
      $this->view->assign('details', null);
      }
      return $this->htmlResponse();
      }

      /**
      * action belist
      */
      public function belistAction(): ResponseInterface
      {
      $employs = $this->employRepository->findAll();
      $this->view->assign('employs', $employs);
      // You can set the title for the backend module with using ModuleTemplateFactory
      $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Employee List");
      $moduleTemplate->setContent($this->view->render());
      return $this->htmlResponse($moduleTemplate->renderContent());
      }
    4. Now allow this belistAction() to the back-end module in employee\Configuration\Backend\Modules.php as you can see in the screenshot below.
    5. Save the file, flush the cache, go to the admin panel, and open the backend module, and you will face the error below:
    6. To resolve the above error, create a template file for the back-end action and before creating the template file, need to add a typoscript to use the template path for the back-end module with a different folder. We are creating the backend module so it is recommended to use the different folders for back-end module template files and not the same one we had used in the front-end plugin.
    7. We already have created the constants.typoscript file on path employee\Configuration\TypoScript\constants.typoscript
      , so put the below code in that file.
      module.tx_employee {
      view {
      # cat=module.tx_employee/file; type=string; label=Path to template root (BE)
      templateRootPath = EXT:employee/Resources/Private/Backend/Templates/
      # cat=module.tx_employee/file; type=string; label=Path to template partials (BE)
      partialRootPath = EXT:employee/Resources/Private/Backend/Partials/
      # cat=module.tx_employee/file; type=string; label=Path to template layouts (BE)
      layoutRootPath = EXT:employee/Resources/Private/Backend/Layouts/
      }
      persistence {
      # cat=module.tx_employee//a; type=string; label=Set Root Page ID
      storagePid =
      }
      }
      By adding the above code in “view”, you are providing the fields to specify the different template file path and specifying the template file paths, and the code added in “persistence” will provide a field to add the ID for the page/folder from which you want to fetch records to the backend module.
    8. Save the file and flush the cache.
    9. To set the file path and StoragePid go to Site Management > Typoscript > select root page > select constant editor and you will be redirected to the screen as seen in the screenshot below:
    10. Set the ID of the folder or page in the Set Root Page ID [module.tx_employee.persistence.storagePid] field. This ID should be for the folder or page where you have stored all your records. Here it is 12, cause all the records are stored in the Employees folder with ID = 12 (Make sure you are storing all the records in one place, it should not be like some records are on one page, and others are on different pages or folders.)
    11. Then, add the below code in the next line to the setup.typoscript file (employee\Configuration\TypoScript\setup.typoscript)
      module.tx_employee {
      persistence {
      // get the value of the ID and use in controller
      storagePid = {$module.tx_employee.persistence.storagePid}
      }
      view {
      templateRootPaths.0 = EXT:employee/Resources/Private/Backend/Templates/
      templateRootPaths.1 = {$module.tx_employee.view.templateRootPath}
      partialRootPaths.0 = EXT:employee/Resources/Private/Backend/Partials/
      partialRootPaths.1 = {$module.tx_employee.view.partialRootPath}
      layoutRootPaths.0 = EXT:employee/Resources/Private/Backend/Layouts/
      layoutRootPaths.1 = {$module.tx_employee.view.layoutRootPath}
      }
      }
      1. Suppose the template file path has not been specified in constants.typoscript file then the path of setup.typoscript file will be considered.
      2. If you are not specifying the path in any of the typoscript from setup.typoscript and constants.typoscript files, then the front-end template file path will be considered.
    12. Now create the layout file on the path <extension_directory>/Resources/Private/Backend/Layouts/, here the path will be employee/Resources/Private/Backend/Layouts/, and add the code below:
      <html
      xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers"
      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
      data-namespace-typo3-fluid="true"
      >
    13. Now create the template file for the back-end module setting from where you will be able to add, edit, or delete employees. The path for the template file is <extension_directory>/Resources/Private/Backend/Templates/Controller/<Action_name>.html, here it will be employee/Resources/Private/Backend/Templates/Controller/Belist.html.
    14. Add the given code to the Belist.html file
       <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
      xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" data-namespace-typo3-fluid="true">
      <f:layout name="Default" />

      <f:section name="Content">
      <div class="section">
      <div class="container">
      <table class="table table-responsive">
      <thead>
      <tr>
      <th>Image</th>
      <th>FirstName</th>
      <th>LastName</th>
      <th>Gender</th>
      <th>Action</th>
      </tr>
      </thead>
      <tbody>
      <f:for each="{employs}" as="emp">
      <tr>
      <td>
      <f:if condition="{emp.image}">
      <f:for each="{emp.image}" as="img">
      <f:image image="{img}" alt="{emp.firstName}" width="100c" height="100c" />
      </f:for>
      </f:if>
      </td>
      <td>{emp.firstName}</td>
      <td>{emp.lastName}</td>
      <td>{emp.gender == 1 ? "Male" :"Female"}</td>
      <td>Edit</td>
      </tr>
      </f:for>
      </tbody>
      </table>
      </div>
      </div>
      </f:section>
      </html
      Flush the cache and click on the back-end module “Employees” and you will see the list of employees you already have added.

      In the above list, we have added the column “Edit“, but that is not working. But for now, let’s keep it as it is. Before Edit, let’s go for the Create new employee.

    Create new employees in the back-end module

    1. Let’s add the option to create a new employee into the template file: employee\Resources\Private\Backend\Templates\Employ\Belist.html
    2. Add the code below to the template file just above the tab
       <div class="d-flex justify-content-end">
      <f:link.action action="new" class="btn btn-primary">Add New Employee</f:link.action>
      </div>
      After adding the above code, flush the cache and reload the admin panel, go to the back-end module “Employees” and you will find the button “Add New Employee” on the screen.
    3. But the action “New” is yet not created, so the Add New Employee button will not work. Let’s create an action “New” in the EmployController.php file. (employee\Classes\Controller\EmployController.php).
    4. Add the below code in EmployController.php to create the “New” action.
      public function newAction(): ResponseInterface
      {
      $newEmploy = new Employ();
      // this 'newEmploy' variable will pass as an object to the template file
      $this->view->assign('newEmploy', $newEmploy);
      $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Add Employee");
      $moduleTemplate->setContent($this->view->render());
      return $this->htmlResponse($moduleTemplate->renderContent());
      }
    5. Now add the newAction() in employee\Configuration\Backend\Modules.php file as a new value of the array EmployController to allow the action or make it available for the back-end module as you can see in the screenshot below
    6. After adding the action new, flush the cache and go to the back-end module and you will see the “Add New Employee” button is linked now.
    7. Click on the button “Add New Employee” and you will see an error as seen in the screenshot below
    8. The above error indicates that the template file is missing to add the new employee. Let’s create the template file for the action new, and the file path will be: employee\Resources\Private\Backend\Templates\Employ\New.html
    9. Create the file New.html on the given path and add the code below to that file
      <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
      xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" data-namespace-typo3-fluid="true">
      <f:layout name="Default" />
      <f:section name="Content">
      <div class="section">
      <div class="container">
      <f:flashMessages />
      <div class="header">
      <h1>New Employ</h1>
      </div>
      <div class="form-wrapper">
      <f:form action="create" enctype="multipart/form-data" name="newEmploy" object="{newEmploy}"> <!-- pass the variable of model instance from controller as an object here -->
      <f:render partial="Employe/FormFields" arguments="{_all}" />
      <div class="d-flex justify-content-end">
      <f:form.submit class="btn btn-primary" value="Create New Employee" />
      </div>
      </f:form>
      </div>
      </div>
      </f:section>
      </html>
    10. In the above code, there is a line of code <f:render partial="Employe/FormFields" arguments="{_all}" />, we are using “partial” to render the form fields.
    11. We can put the form fields directly instead of calling the partial, but we are using the partial so we can reuse the form fields.
    12. Create the folders and template file of the partial. And the file path will be: employee\Resources\Private\Backend\Partials\Employe\FormFields.html, you can give any name to the partials template file.
      <div class="row">
      <div class="col-6">
      <div class="form-group">
      <label class="control-label mb-10" for="firstName">
      First Name
      </label><br />
      <f:form.textfield property="firstName" class="form-control" placeholder="Enter First Name" required="true"
      />
      </div>
      </div>
      <div class="col-6">
      <div class="form-group">
      <label class="control-label mb-10" for="lastName">
      Last Name
      </label><br />
      <f:form.textfield property="lastName" class="form-control" placeholder="Enter Last Name" required="true"
      />
      </div>
      </div>
      </div>
      <div class="row">
      <div class="col-6">
      <div class="form-group">
      <label class="control-label mb-10" for="salary">
      Salary
      </label><br />
      <f:form.textfield property="salary" class="form-control" placeholder="Enter Salary" type="number" required="true"
      />
      </div>
      </div>
      <div class="col-6">
      <div class="form-group">
      <label class="control-label mb-10" for="country">
      Country
      </label><br />
      <f:form.select property="country" class="form-control"options="{usa: 'USA', germany: 'Germany', france:'France'}" />
      </div>
      </div>
      </div>
      <f:comment>
      <div class="row">
      <div class="col-6">
      <div class="form-group">
      <label class="control-label mb-10" for="gender">
      Gender
      </label><br />
      <f:form.radio property="gender" value="{1}" /> Male
      <f:form.radio property="gender" value="{2}" /> FeMale
      </div>
      </div>
      <div class="col-6">
      <div class="form-group">
      Languages<br />
      <f:form.checkbox property="languages" value="en" multiple="1" /> English
      <f:form.checkbox property="languages" value="fr" multiple="1" /> French
      <f:form.checkbox property="languages" value="de" multiple="1" /> German
      </div>
      </div>
      </div>
      </f:comment>
      <div class="row">
      <div class="col-6">
      <div class="form-group">
      <label class="control-label mb-10" for="birthDate">
      Birth Date
      </label><br />
      <f:form.textfield property="birthDate" class="form-control" type="date" required="true" />
      </div>
      </div>
      <div class="col-6">
      <div class="form-group">
      <label class="control-label mb-10" for="joiningDate">
      Joining Date
      </label><br />
      <f:form.textfield property="joiningDate" class="form-control" type="date" required="true" />
      </div>
      </div>
      </div>
      <div class="row">
      <div class="col-12">
      <div class="form-group">
      <label class="control-label mb-10" for="bio">
      Bio
      </label><br />
      <f:form.textarea property="bio" class="form-control" />
      </div>
      </div>
      </div>
      <div class="row">
      <div class="col-12">
      <div class="form-group">
      <label class="control-label mb-10" for="experiance">
      Experiance
      </label><br />
      <f:form.textarea property="experiance" class="form-control" />
      </div>
      </div>
      </div>
      <div class="row">
      <div class="col-6">
      <div class="form-group">
      <label class="control-label mb-10" for="image">
      Image
      </label><br />
      <f:form.upload property="image" class="form-control" />
      </div>
      </div>
      </div>
      Here the fluid form has been used in the above code, and you can check more details from the reference link below.
      Ref.link: https://docs.typo3.org/other/typo3/view-helper-reference/12.4/en-us/Global/Form/Index.html
    13. After adding the above code in partial, now the form will open with the click of “Add New Employee”.
    14. After filling out the form when you click on the “Create New Employee” button, nothing will happen. In the New.html file the action “create” has been called which does not exist in the controller, so let’s create the action to submit the form with the click of the button “Create New Employee“.
    15. Add the below code in employee\Classes\Controller\EmployController.php file to create the createAction().
           /**
      * action create
      * @param Employ $newEmploy
      * @return void
      */
      public function createAction(Employ $newEmploy): ResponseInterface
      {
      $this->employRepository->add($newEmploy);
      $this->addFlashMessage('Employee Added successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
      return $this->redirect('belist');
      }
    16. After creating the action, add the action in employee\Configuration\Backend\Modules.php file to allow it for the backn-end module.

    17. After adding the action to the Modules.php file, now fill-up the form and click on “Create New Employee” button and you will see the below screen:

      An error occurred for the createAction(), but the error is not indicating anything so it can not be identified, but it might be an error of form validation. Let’s check if it is a form validation error or not.
    18. Add the below code in the partial template file: employee\Resources\Private\Backend\Partials\Employe\FormFields.html
      <f:form.validationResults>
          <f:if condition="{validationResults.flattenedErrors}">
              <div class="alert ">
                  <f:for each="{validationResults.flattenedErrors}" as="errors" key="propertyPath">
                      <div class="alert alert-danger"> {propertyPath}
                          <f:for each="{errors}" as="error">
                              <p>{error.code}: {error}</p>
                          </f:for>
                      </div>
                  </f:for>
              </div>
          </f:if>
      </f:form.validationResults>
    19. After adding the above code in the partial template file, flush the cache reload the back-end module, fill up the details for an employee, and submit the data by clicking on the “Create New Employee” button and you will see the below screen:

      Please note here that we are not uploading the image here, to upload the image we need to convert it into the file reference, and will do that later in this documentation.
    20. As you see in the above screenshot, after adding the validation code, an error will appear for the specific fields. This is the error about the datatype conversion as the form is returning the string and the model field needs the date object for those fields. To resolve these errors, it is required to create the initialize action of a createAction() & convert the fields into the datetime datatype add the initialize action just before the create action. Add the initialize action whenever you want to perform anything just before the action calls, for example here we want to convert the data type for “birthdate” and “joiningdate” from string to DateTime so when we call the createAction there should be no error occur, and for that, we have to convert the data type of them before we call the create action, so we should create an initialize action and convert the data type in that action. Add the code below of the initialize action into the file: employee\Classes\Controller\EmployController.php
      public function initializeCreateAction()
      {
      /*
      Here 'newEmploy' which has pass in arguments, is the object that has pass in New.html (employee\Resources\Private\Backend\Templates\Employ\New.html)
      as an object, and that object is the instance of the model Employ defined in the newAction
      */
      if ($this->arguments['newEmploy']) {
      // Get all the fields of form
      $propertyMappingConfiguration = $this->arguments['newEmploy']->getPropertyMappingConfiguration();
      // Allow a list of specific properties.
      $propertyMappingConfiguration->allowAllProperties();
      // Converting the 'birthDate' to the datatime data type
      $propertyMappingConfiguration->forProperty('birthDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
      // Converting the 'birthDate' to the datatime data type
      $propertyMappingConfiguration->forProperty('joiningDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
      }
      }
    21. After adding the above code, save the file, flush the cache, and fill up the form again and you will see the new employee has been added to the employee list from the backend module.
    22. In the above screenshot, you can see that we haven’t uploaded the image. The field is already defined in the model file but we need to update the controller, model, and persistence files to make the image field available.

    Image field in backend module

    Controller updates
    1. Below is the final code after some updates related to adding the image in the back-end module and some others:
      <?php
      declare(strict_types=1);

      namespace Company\Employee\Controller;

      use DateTime;
      use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
      use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
      use Psr\Http\Message\ResponseInterface;
      // use Context which is extending from the Action controller
      use TYPO3\CMS\Core\Context\Context;
      /**
      * USE: to debug (syntax: DebuggerUtility::var_dump($var_to_debug); )
      */
      use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
      /**
      * USE:
      * All methods in this class are meant to be called statically.
      * So use \TYPO3\CMS\Core\Utility\GeneralUtility::[method-name] to refer to the functions,
      * eg. '\TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds()'
      */
      use TYPO3\CMS\Core\Utility\GeneralUtility;
      /**
      * USE:
      * ContextualFeedbackSeverity will display the error msg according to the status you give like 'OK','WARNING' etc.
      */
      use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
      use Company\Employee\Domain\Model\Employ;

      class EmployController extends ActionController
      {
      /**
      * employRepository
      *
      * @var \Company\Employee\Domain\Repository\EmployRepository
      */
      protected $employRepository = null;
      /**
      * @param \Company\Employee\Domain\Repository\EmployRepository $employRepository
      */
      public function injectEmployRepository(\Company\Employee\Domain\Repository\EmployRepository $employRepository)
      {
      $this->employRepository = $employRepository;
      }
      public function __construct(
      protected readonly ModuleTemplateFactory $moduleTemplateFactory,
      Context $context,
      ) {
      $this->context = $context;
      }

      /**
      * action list
      *
      * @return string|object|null|void
      */
      public function indexAction(): ResponseInterface
      {
      $settings = $this->settings;
      $this->view->assign('settings', $settings);
      $employs = $this->employRepository->findAll();
      $this->view->assign('employs', $employs);
      return $this->htmlResponse();
      }


      /**
      * action detail
      *
      * @return string|object|null|void
      */
      public function detailAction(): ResponseInterface
      {
      $uid = GeneralUtility::_GP('uid');
      if ($uid) {
      $details = $this->employRepository->findByUid($uid);
      $this->view->assign('details', $details);
      } else {
      $this->view->assign('details', null);
      }
      return $this->htmlResponse();
      }

      public function belistAction(): ResponseInterface
      {
      $employs = $this->employRepository->findAll();
      $this->view->assign('employs', $employs);
      $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Employee List");
      $moduleTemplate->setContent($this->view->render());
      return $this->htmlResponse($moduleTemplate->renderContent());
      }

      public function newAction(): ResponseInterface
      {
      $newEmploy = new Employ();
      $this->view->assign('newEmploy', $newEmploy);
      $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Add Employee");
      $moduleTemplate->setContent($this->view->render());
      return $this->htmlResponse($moduleTemplate->renderContent());
      }

      public function initializeCreateAction()
      {
      /*
      Here 'newEmploy' which has pass in arguments, is the object that has pass in New.html (employee\Resources\Private\Backend\Templates\Employ\New.html)
      as an object, and that object is the instance of the model Employ defined in the newAction
      */
      if ($this->arguments['newEmploy']) {
      // Get all the fields of form
      $propertyMappingConfiguration = $this->arguments['newEmploy']->getPropertyMappingConfiguration();
      // Allow a list of specific properties.
      $propertyMappingConfiguration->allowAllProperties();
      // Converting the 'birthDate' to the datatime data type
      $propertyMappingConfiguration->forProperty('birthDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
      // Converting the 'birthDate' to the datatime data type
      $propertyMappingConfiguration->forProperty('joiningDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
      // Skip the image to be initialize to avoid any error before converting it to the filerference type into the createAction
      $propertyMappingConfiguration->skipProperties('image');
      }
      }
      /**
      * action create
      * @param Employ $newEmploy
      * @return void
      */
      public function createAction(Employ $newEmploy): ResponseInterface
      {
      /**
      * assigning the data type 'FileReference' to the image to make the image available in employee list
      */

      // Getting the uploaded file
      $uploaded = $this->request->getUploadedFiles();
      if (sizeOf($uploaded) > 0) {
      // Store the image into the var $__imag
      $__imag = $uploaded['newEmploy']['image'];
      // Created instance of the ResourceFactory and get the default storage of resource factory
      $resourceFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
      $storage = $resourceFactory->getDefaultStorage();

      /** Add File in storage with below syntax:
      *
      * $newFile = $storage->addFile(
      * "/tmp/temporary_file_name.ext",
      * $storage->getRootLevelFolder(),
      * "final_file_name.ext",
      * );
      */
      $newFile = $storage->addFile(
      $__imag->getTemporaryFileName(),
      $storage->getRootLevelFolder(),
      $__imag->getClientFilename()
      );

      // Create an instance of the Filereference by using the makeInstance method of GeneralUtility and store it in var $fileReference which
      // haven't any data in it's originalResource
      $fileReference = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\FileReference::class);
      // set the data from $newFile to the $fileReference by setOriginalResource method
      $fileReference->setOriginalResource($newFile);
      // add the image to the model instance $newEmploy by using addImage method and pass the $fileReference var. in that method.
      $newEmploy->addImage($fileReference);
      }
      /**
      * If the data has added success fully then this message will appear,
      * and here we have used 'ContextualFeedbackSeverity' with the status 'OK'
      **/
      $this->addFlashMessage('Employee Added successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
      $this->employRepository->add($newEmploy);
      $redirectPid = (int) $this->settings['listPid'];
      $uriBuilder = $this->uriBuilder;
      $uri = $uriBuilder
      ->setTargetPageUid($redirectPid)
      ->build();
      return $this->redirectToUri($uri);
      }
      }
    FileReference model override
    1. Create the FileReference.php file to utilize the image in the backend module.
      1. We are creating the FileReference.php model to override the default model of file reference.
      2. In the default model of file reference, there is no method like setOriginalResourcehttps://api.typo3.org/main/classes/TYPO3-CMS-Core-Resource-FileReference.html
      3. But if we are using extbase, then we have to use the method setOriginalResource, and to add this method in the model file, we are creating the model file to override the default model file for FileReference – https://api.typo3.org/main/classes/TYPO3-CMS-Extbase-Domain-Model-FileReference.html#method_setOriginalResource
      4. The path of the file is employee\Classes\Domain\Model\FileReference.php. Add the code below to the file:
        <?php

        namespace Company\Employee\Domain\Model;

        use TYPO3\CMS\Core\Resource\ResourceInterface;
        /**
        * File Reference
        */
        class FileReference extends \TYPO3\CMS\Extbase\Domain\Model\FileReference
        {
        public const VIEW_DETAIL_ONLY = 0;
        public const VIEW_LIST_AND_DETAIL = 1;
        public const VIEW_LIST_ONLY = 2;

        /**
        * Obsolete when foreign_selector is supported by ExtBase persistence layer
        *
        * @var int
        */
        protected $uidLocal = 0;

        /**
        * @var string
        */
        protected $title = '';

        /**
        * @var string
        */
        protected $description = '';

        /**
        * @var string
        */
        protected $alternative = '';

        /**
        * @var string
        */
        protected $link = '';

        /**
        * @var int
        */
        protected $showinpreview = 0;

        /**
        * Set File uid
        *
        * @param int $fileUid
        */
        public function setFileUid($fileUid): void
        {
        $this->uidLocal = $fileUid;
        }

        /**
        * Get File UID
        *
        * @return int
        */
        public function getFileUid(): int
        {
        return $this->uidLocal;
        }

        /**
        * Set alternative
        *
        * @param string $alternative
        */
        public function setAlternative($alternative): void
        {
        $this->alternative = $alternative;
        }

        /**
        * Get alternative
        *
        * @return string
        */
        public function getAlternative(): string
        {
        return (string)($this->alternative !== '' ? $this->alternative : $this->getOriginalResource()->getAlternative());
        }

        /**
        * Set description
        *
        * @param string $description
        */
        public function setDescription($description): void
        {
        $this->description = $description;
        }

        /**
        * Get description
        *
        * @return string
        */
        public function getDescription(): string
        {
        return (string)($this->description !== '' ? $this->description : $this->getOriginalResource()->getDescription());
        }

        /**
        * Set link
        *
        * @param string $link
        */
        public function setLink($link): void
        {
        $this->link = $link;
        }

        /**
        * Get link
        *
        * @return mixed
        */
        public function getLink()
        {
        return (string)($this->link !== '' ? $this->link : $this->getOriginalResource()->getLink());
        }

        /**
        * Set title
        *
        * @param string $title
        */
        public function setTitle($title): void
        {
        $this->title = $title;
        }

        /**
        * Get title
        *
        * @return string
        */
        public function getTitle(): string
        {
        return (string)($this->title !== '' ? $this->title : $this->getOriginalResource()->getTitle());
        }

        /**
        * Set showinpreview
        *
        * @param int $showinpreview
        */
        public function setShowinpreview($showinpreview): void
        {
        $this->showinpreview = $showinpreview;
        }

        /**
        * Get showinpreview
        *
        * @return int
        */
        public function getShowinpreview(): int
        {
        return $this->showinpreview;
        }


        // setOriginalResource method is added in this model
        public function setOriginalResource(ResourceInterface $originalResource)
        {
        $this->originalResource = $originalResource;
        $this->uidLocal = (int)$originalResource->getProperty('uid');
        }

        }
    Persistence
    1. Add the below code to the persistence file to establish the connection between the database table (sys_file_reference) and the model FileReference that we created. The file path is: employee\Configuration\Extbase\Persistence\Classes.php
      \Company\Employee\Domain\Model\FileReference::class => [
      'tableName' => 'sys_file_reference',
      ],
      Below is the screenshot of the final code of the persistence file after adding the above code.
    2. Replace the code for image field with the below code in Employ model. The file path is: employee\Classes\Domain\Model\Employ.php
      public function addImage(FileReference $image): void
      {
      $this->image = $this->getImage();
      $this->image->attach($image);
      }

      /**
      * @return ObjectStorage<FileReference>
      */
      public function getImage(): ObjectStorage
      {
      if ($this->image instanceof LazyLoadingProxy) {
      $this->image->_loadRealInstance();
      }

      if ($this->image instanceof ObjectStorage) {
      return $this->image;
      }

      return $this->image = new ObjectStorage();
      }


      public function removeImage(FileReference $image): void
      {
      $this->image = $this->getImage();
      $this->image->detach($image);
      }

      /**
      * @param ObjectStorage<FileReference> $image
      */
      public function setImage(ObjectStorage $image): void
      {
      $this->image = $image;
      }
      After the above updates in the model file, toggle the extension (Deactivate once and then activate again), or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache” and submit the form from the back-end module with an image and you will get the image of the employee in the employee list.

    Delete employee in the back-end module

    Please follow the below steps to implement the delete functionality:

    1. Let’s add the delete button to the template file. The file path is employee\Resources\Private\Backend\Templates\Employ\Belist.html
      Add the code below to the template file just after the edit button.
      <f:link.action action="delete" class="btn btn-primary"
      arguments="{uid:emp.uid}">Delete
      </f:link.action>
    2. After adding the above code, you will see the employee list in the back-end module has a delete button now.
    3. As you can see in the above code, the action that is assigned to the delete button is “delete”, but we need to create that action and add it to the employee\Configuration\Backend\Modules.php file to make it allow or available for the back-end module, add as a new value of the array EmployController array as you can see in the screenshot below
    4. Now let’s create the deleteAction into the employee\Classes\Controller\EmployController.php file. Please add code for the deleteAction as given below:
          public function deleteAction(): ResponseInterface
      {
      // get all the arguments by using getArguments() method.
      $arguments = $this->request->getArguments();
      // get the uid from arguments
      $uid = $arguments['uid'];
      /**
      * Check the condition for uid and use the finByUid method and get all the data
      * for that user you have clicked the delete button for.
      * And store the data in a variable.
      * Then use the remove method to remove all the data for that employee
      * by pass the variable you have stored all the data in the remove method.
      * And use the addFlashMessage to display the message after delete and use ContextualFeedbackSeverity to display the status.
      */
      if ($uid) {
      $emplodata = $this->employRepository->findByUid($uid);
      if ($emplodata) {
      $this->employRepository->remove($emplodata);
      $this->addFlashMessage('Employee Deleted Sucessfully!!', 'Done!!', ContextualFeedbackSeverity::OK, true);
      } else {
      $this->addFlashMessage('Employee Not Found!!', 'Invalid', ContextualFeedbackSeverity::WARNING, true);
      }
      } else {
      $this->addFlashMessage('Employee Not Found!!', 'Invalid', ContextualFeedbackSeverity::WARNING, true);
      }
      // redirect to the employee list.
      return $this->redirect('belist');
      }
    5. After creating the action and allowing the action in modules.php file, you will the Delete button has a link now, and you can click on the button to remove the particular record.

      As you can see in the below screenshot, the last employee “John” is deleted and you will get the message when the delete action is successfully done.

    Edit the employee in the back-end module

    We already have added the “Edit” button into the template file employee\Resources\Private\Backend\Templates\Employ\Belist.html at the time of creating the template.

    But if you check you will get to know that the edit button is not working as we haven’t created the edit action and added the action into the modules.php file to allow it for the back-end module.

    1. Let’s add the action “edit” we have assigned to the edit button to the employee\Configuration\Backend\Modules.php file to allow it for the backend module.
    2. Now create an action for edit, add the below code of the editAction() in the file  employee\Classes\Controller\EmployController.php
          public function editAction(): ResponseInterface
      {
      // get all the arguments by using getArguments() method.
      $arguments = $this->request->getArguments();
      // get the uid from arguments
      $uid = $arguments['uid'];
      /**
      * Check the condition for uid and use the finByUid method and get all the data
      * for that user you have clicked the edit button for.
      * And store the data in a variable.
      * Then check the condition for that variable and assign it to the instance 'employ' and that instance will be pass as an object
      * into the form of Edit.html template file.
      *
      */
      if ($uid) {
      $emplodata = $this->employRepository->findByUid($uid);
      if ($emplodata) {
      $this->view->assign('employ', $emplodata);
      // Here, we used moduleTemplateFactory to get htmlResponse and it gives some functionalities, setTitle is one of them
      $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Edit Employee");
      $moduleTemplate->setContent($this->view->render());
      return $this->htmlResponse($moduleTemplate->renderContent());
      } else {
      $this->addFlashMessage('Employee Not Found!!', 'Invalid', ContextualFeedbackSeverity::WARNING, true);
      return $this->redirect('belist');
      }
      } else {
      $this->addFlashMessage('Employee Not Found!!', 'Invalid', ContextualFeedbackSeverity::WARNING, true);
      return $this->redirect('belist');
      }
      }
    3. Flush the cache and reload the employee list of the back-end module and you will see the edit button is working now.

      Click on the edit button of the record you want to edit, and you will see the below error
    4. To resolve the above error, create the template file for editAction.
      1. Create the Edit.html file. The file path is employee\Resources\Private\Backend\Templates\Employ\Edit.html
      2. Add the below code to the Edit.html file
        <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
        xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
        xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" data-namespace-typo3-fluid="true">
        <f:layout name="Default" />
        <f:section name="Content">
        <div class="section">
        <div class="container">
        <f:flashMessages />
        <div class="header">
        <h1>Edit Employ</h1>
        </div>
        <div class="form-wrapper">
        //pass the object 'employ' assigned to the view in controller file
        <f:form action="update" enctype="multipart/form-data" name="employ" object="{employ}">
        <f:render partial="Employe/FormFields" arguments="{_all}" />
        <div class="d-flex justify-content-end">
        <f:form.submit class="btn btn-primary" value="Update Employee" />
        </div>
        </f:form>
        </div>
        </div>
        </f:section>
        </html>

        Save the file, flush the cache, go to the back-end module, and click on the edit button of any record. You will see the error on the screen as you can see in the screenshot below:

        The above error is occurring for the fields “birth date” and “joining date” as the value of fields with DateTime datatype could not be converted to a string. To resolve the above error, add the piece of code in file employee\Resources\Private\Backend\Partials\Employe\FormFields.html to both the fields “birth date” and “joining date”. For the birthdate field, add value="{employ.birthDate -> f:format.date()}" and for the joining date field, add value="{employ.joiningDate -> f:format.date()}" , below is the screenshot of both fields after adding the value attribute.

        Save the file, flush the cache, and reload the back-end module employee list, click on the edit button of any record and you will see the form with all the details for that particular employee.

        Edit any data in this form and click on the Upload Employee button, but nothing will happen as we haven’t created the “updateAction” yet. We have added the action “update” to the Edit.html template file, but haven’t created the action or added it to the modules.php file to allow the action for the back-end module.
      3. Add the action “update” in the employee\Configuration\Backend\Modules.php file to allow the action for the back-end module.

      4. Now create an updateAction in the controller file, the file path is employee\Classes\Controller\EmployController.php
      5. Add the code below to create the action
         /**
        * action update
        * @param Employ $employ
        * @return void
        */
        public function updateAction(Employ $employ): ResponseInterface
        {
        $this->employRepository->update($employ);
        $this->addFlashMessage('Employee Updated successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
        return $this->redirect('belist');
        }

        Save the controller file, flush the cache, go to the employee list in the back-end module, edit any record, and click on the “Update Employee” button, and you will see the error on the screen as you can see in the screenshot below.
      6. To resolve the above error, let’s initialize the updateAction.Add the initialize action whenever you want to perform anything just before the action calls, for example here we want to convert the data type for “birthdate” and “joiningdate” from string to DateTime so when we call the updateAction there should be no error occur, and for that, we have to convert the data type of them before we call the update action, so we should create an initialize action and convert the data type in that action. Add the below code in employee\Classes\Controller\EmployController.php file just before the updateAction.
        public function initializeUpdateAction()
        {
        if ($this->arguments['employ']) {
        $propertyMappingConfiguration = $this->arguments['employ']->getPropertyMappingConfiguration();
        $propertyMappingConfiguration->allowAllProperties();
        $propertyMappingConfiguration->forProperty('birthDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->forProperty('joiningDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->skipProperties('image');
        }
        }
        To add the image field update the code of the updateAction(), replace the code of updateAction() with the code given below
        /**
        * action create
        * @param Employ $employ
        * @return void
        */
        public function updateAction(Employ $employ): ResponseInterface
        {
        $uploaded = $this->request->getUploadedFiles();
        if (sizeOf($uploaded) > 0) {
        $oldImage = $employ->getImage();
        if($oldImage){
        foreach($oldImage as $im){
        $employ->removeImage($im);
        }
        }
        $__imag = $uploaded['employ']['image'];
        $resourceFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
        $storage = $resourceFactory->getDefaultStorage();
        $newFile = $storage->addFile(
        $__imag->getTemporaryFileName(),
        $storage->getRootLevelFolder(),
        $__imag->getClientFilename()
        );
        $fileReference = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\FileReference::class);
        $fileReference->setOriginalResource($newFile);
        $employ->addImage($fileReference);
        }
        $this->employRepository->update($employ);
        $this->addFlashMessage('Employee Updated successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
        return $this->redirect('belist');
        }
      7. Save the file, flush the cache, and click on the “Update Employee” button, you will see the employee will be updated and you will get the message of success as you can see in the screenshot below.

    Additionals to the extension

    Here we are going to provide a front-end form to apply for the job, and before that, we will add one more table for the “job” into the back-end module of the extension we have created above, and perform the CRUD operation for the job.

    To add the Job in the existing extension add relevant code into the ext_tables.sql file, create its TCA file, create a model file & other pieces of stuff as per requirement. Let’s start to create the custom form in the front-end.

    Create the new table into the extension

    Create a new table for the job

    1. Create a table for the “Job” in the ext_tables.sql file, add the code below to create the table for the job.
    CREATE TABLE tx_employee_domain_model_job ( 
    	job_title varchar(255) DEFAULT '' NOT NULL,
    	vacancy  varchar(255) DEFAULT '' NOT NULL,
    	slug  varchar(255) DEFAULT '' NOT NULL,
    	banner_image  int(11) unsigned DEFAULT '0',
    	job_description text,
    	job_location  varchar(255) DEFAULT '' NOT NULL,
    	publish_date int(11) DEFAULT '0' NOT NULL,
    	job_type varchar(255) DEFAULT '' NOT NULL,
    	years_of_experience varchar(255) DEFAULT '' NOT NULL,
    	working_hrs varchar(255) DEFAULT '' NOT NULL,
    	working_days varchar(255) DEFAULT '' NOT NULL,
    	salary varchar(255) DEFAULT '' NOT NULL,
    	job_requirements text,
    	education_qualification text,
    	perks_benefits text, 
    );

    Create a TCA file for the job table

    1. Create a TCA file for the Job table, and the name of the TCA file should be in this format tx_extensionkey_domain_model_tablename, and here it will be tx_employee_domain_model_job.php.
    2. The path for the TCA file is employee\Configuration\TCA\tx_employee_domain_model_job.php
    3. Add the code below to the TCA file tx_employee_domain_model_job.php
      <?php
      return [
      'ctrl' => [
      'title' => 'Job',
      'label' => 'job_title',
      'tstamp' => 'tstamp',
      'crdate' => 'crdate',
      'versioningWS' => true,
      'label_alt_force' => true,
      'origUid' => 't3_origuid',
      'languageField' => 'sys_language_uid',
      'transOrigPointerField' => 'l10n_parent',
      'transOrigDiffSourceField' => 'l10n_diffsource',
      'delete' => 'deleted',
      'enablecolumns' => [
      'disabled' => 'hidden',
      'starttime' => 'starttime',
      'endtime' => 'endtime',
      ],
      'searchFields' => 'job_title',
      'vacancy',
      'slug',
      'banner_image',
      'job_description',
      'job_location',
      'publish_date',
      'years_of_experience',
      'working_hrs',
      'working_days',
      'salary',
      'job_requirements',
      'education_qualification',
      'perks_benefits',
      'iconfile' => 'EXT:employee/Resources/Public/Icons/Extension.png',
      'security' => [
      'ignorePageTypeRestriction' => true,
      ],
      ],
      'types' => [
      '1' => [
      'showitem' => 'job_title,vacancy,slug,job_type,banner_image,job_description,job_location,publish_date,years_of_experience,working_hrs,working_days,salary,job_requirements,education_qualification,perks_benefits, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime'
      ],
      ],
      'columns' => [
      'sys_language_uid' => [
      'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
      'config' => [
      'type' => 'language',
      ],
      ],
      'l10n_parent' => [
      'displayCond' => 'FIELD:sys_language_uid:>:0',
      'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
      'config' => [
      'type' => 'select',
      'renderType' => 'selectSingle',
      'default' => 0,
      'items' => [
      ['label' => '', 'value' => 0]
      ],
      'foreign_table' => 'tx_employee_domain_model_job',
      'foreign_table_where' => 'AND {#tx_employee_domain_model_job}.{#pid}=###CURRENT_PID### AND {#tx_employee_domain_model_job}.{#sys_language_uid} IN (-1,0)',
      ],
      ],
      'l10n_diffsource' => [
      'config' => [
      'type' => 'passthrough',
      ],
      ],
      'hidden' => [
      'config' => [
      'type' => 'check',
      'items' => [
      ['label' => 'Disable'],
      ],
      ]
      ],
      'starttime' => [
      'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
      'config' => [
      'type' => 'input',
      'renderType' => 'datetime',
      'eval' => 'datetime,int',
      'default' => 0,
      'behaviour' => [
      'allowLanguageSynchronization' => true
      ]
      ],
      ],
      'endtime' => [
      'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
      'config' => [
      'type' => 'input',
      'renderType' => 'datetime',
      'eval' => 'datetime,int',
      'default' => 0,
      'range' => [
      'upper' => mktime(0, 0, 0, 1, 1, 2038)
      ],
      'behaviour' => [
      'allowLanguageSynchronization' => true
      ]
      ],
      ],

      'job_title' => [
      'l10n_mode' => 'prefixLangTitle',
      'label' => 'Job Title',
      'description' => 'Enter Job Title',
      'config' => [
      'type' => 'input',
      'behaviour' => [
      'allowLanguageSynchronization' => true,
      ],
      ]

      ],
      'vacancy' => [
      'l10n_mode' => 'prefixLangTitle',
      'label' => 'Job Vacancy',
      'description' => 'Enter Job Vacancy',
      'config' => [
      'type' => 'input',
      'behaviour' => [
      'allowLanguageSynchronization' => true,
      ],
      ]
      ],
      'slug' => [
      'label' => 'Slug',
      'config' => [
      'type' => 'slug',
      'generatorOptions' => [
      'fields' => ['job_title'],
      'replacements' => [
      '/' => '-',
      ],
      ],
      'fallbackCharacter' => '-',
      'eval' => 'uniqueInSite',
      'uniqueInPid',
      'default' => '',
      'appearance' => [
      'prefix' => Company\Employee\UserFunctions\FormEngine\SlugPrefix::class . '->getPrefix'
      ],

      ],
      ],

      'banner_image' => [
      'label' => 'Banner',
      'config' => [
      'type' => 'file',
      'maxitems' => 1,
      'allowed' => 'common-image-types'
      ],
      ],
      'job_description' => [
      'label' => 'Description',
      'config' => [
      'type' => 'text',
      'enableRichtext' => true,
      ],
      ],
      'job_location' => [
      'label' => 'Location',
      'config' => [
      'type' => 'input',
      ]
      ],
      'publish_date' => [
      'label' => 'Publish Date',
      'config' => [
      'type' => 'datetime',
      'format' => 'date',
      'eval' => 'int',
      'default' => 0,
      ]
      ],
      "job_type" => [
      'label' => 'Job Type',
      'config' => [
      'type' => 'select',
      'renderType' => 'selectSingle',
      'items' => [
      [
      'label' => 'Full Time',
      'value' => 'full-time',
      ],
      [
      'label' => 'Part Time',
      'value' => 'part-time',
      ],
      [
      'label' => 'Work From Home',
      'value' => 'wfh',
      ],
      ],
      ],
      ],
      'years_of_experience' => [
      'label' => 'Experience Year',
      'config' => [
      'type' => 'number',
      'format' => 'decimal'
      ]
      ],
      'working_hrs' => [
      'label' => 'Working hour',
      'config' => [
      'type' => 'input',
      ]
      ],
      'working_days' => [
      'label' => 'Working Days',
      'config' => [
      'type' => 'number',
      'format' => 'decimal'
      ]
      ],
      'salary' => [
      'label' => 'Salary',
      'config' => [
      'type' => 'number',
      'format' => 'decimal'
      ]
      ],
      'job_requirements' => [
      'label' => 'Requirements',
      'config' => [
      'type' => 'text',
      'enableRichtext' => true,
      ],
      ],
      'education_qualification' => [
      'label' => 'Qualification',
      'config' => [
      'type' => 'text',
      'enableRichtext' => true,
      ],
      ],
      'perks_benefits' => [
      'label' => 'Benefits',
      'config' => [
      'type' => 'text',
      'enableRichtext' => true,
      ],
      ],
      ],
      ];
    4. Here we need to use the custom function to add the prefix URL for the slug field, to create that custom function create the file SlugPrefix.php, the path of the file is employee\Classes\UserFunctions\FormEngine\SlugPrefix.php.
      1. Add the code below in the file SlugPrefix.php
        <?php
        declare(strict_types = 1);

        namespace Company\Employee\UserFunctions\FormEngine;

        use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSlug;

        class SlugPrefix
        {
        public function getPrefix(array $parameters, TcaSlug $reference): string
        {
        return '/job';
        }
        }

    Create the model for the job table

    1. Create the model file Job.php for the job table and the file path for the job model is employee\Classes\Domain\Model\Job.php
    2. Add the code below to the Job.php file

      <?php

      declare(strict_types=1);

      namespace Company\Employee\Domain\Model;

      use DateTime;
      use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
      use TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
      use TYPO3\CMS\Extbase\Domain\Model\FileReference;
      use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
      use TYPO3\CMS\Extbase\Persistence\ObjectStorage;

      /**
      * Job Model
      */
      class Job extends AbstractEntity
      {
      /**
      * jobTitle
      *
      * @var string
      */
      protected $jobTitle = '';

      /**
      * vacancy
      *
      * @var string
      */
      protected $vacancy = '';

      /**
      * slug
      *
      * @var string
      */
      protected $slug = '';


      /**
      * @Lazy
      * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
      */
      protected ObjectStorage $bannerImage;


      /**
      * jobDescription
      *
      * @var string
      */
      protected $jobDescription = '';

      /**
      * jobLocation
      *
      * @var string
      */
      protected $jobLocation = '';

      /**
      * publishDate
      *
      * @var DateTime
      */
      protected $publishDate;

      /**
      * jobType
      *
      * @var string
      */
      protected $jobType = '';

      /**
      * yearsOfExperience
      *
      * @var string
      */
      protected $yearsOfExperience = '';

      /**
      * workingHrs
      *
      * @var string
      */
      protected $workingHrs = '';

      /**
      * workingDays
      *
      * @var string
      */
      protected $workingDays = '';

      /**
      * salary
      *
      * @var string
      */
      protected $salary = '';

      /**
      * jobRequirements
      *
      * @var string
      */
      protected $jobRequirements = '';

      /**
      * educationQualification
      *
      * @var string
      */
      protected $educationQualification = '';

      /**
      * perksBenefits
      *
      * @var string
      */
      protected $perksBenefits = '';


      public function __construct()
      {
      $this->initializeObject();

      }

      public function initializeObject(): void
      {
      $this->bannerImage = $this->bannerImage ?? new ObjectStorage();
      }

      /**
      * Returns the jobTitle
      *
      * @return string
      */
      public function getJobTitle(): string
      {
      return $this->jobTitle;
      }

      /**
      * Sets the jobTitle
      *
      * @param string $jobTitle
      * @return void
      */
      public function setJobTitle(string $jobTitle): void
      {
      $this->jobTitle = $jobTitle;
      }


      /**
      * Returns the vacancy
      *
      * @return string
      */
      public function getVacancy(): string
      {
      return $this->vacancy;
      }

      /**
      * Sets the vacancy
      *
      * @param string $vacancy
      * @return void
      */
      public function setVacancy(string $vacancy): void
      {
      $this->vacancy = $vacancy;
      }

      /**
      * Returns the slug
      *
      * @return string
      */
      public function getSlug(): string
      {
      return $this->slug;
      }
      /**
      * Sets the slug
      *
      * @param string $slug
      * @return void
      */
      public function setSlug(string $slug): void
      {
      $this->slug = $slug;
      }

      /**
      * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
      */
      public function getBannerImage(): ObjectStorage
      {
      return $this->bannerImage;
      }

      /**
      * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $bannerImage
      */
      public function setBannerImage(ObjectStorage $bannerImage): self
      {
      $this->bannerImage = $bannerImage;
      return $this;
      }




      /**
      * Returns the jobDescription
      *
      * @return string
      */
      public function getJobDescription(): string
      {
      return $this->jobDescription;
      }
      /**
      * Sets the jobDescription
      *
      * @param string $jobDescription
      * @return void
      */
      public function setJobDescription(string $jobDescription): void
      {
      $this->jobDescription = $jobDescription;
      }

      /**
      * Returns the jobLocation
      *
      * @return string
      */
      public function getJobLocation(): string
      {
      return $this->jobLocation;
      }
      /**
      * Sets the jobLocation
      *
      * @param string $jobLocation
      * @return void
      */
      public function setJobLocation(string $jobLocation): void
      {
      $this->jobLocation = $jobLocation;
      }


      /**
      * Get publishDate
      *
      * @return DateTime|null
      */
      public function getPublishDate(): ?DateTime
      {
      return $this->publishDate;
      }

      /**
      * Set publishDate
      *
      * @param DateTime $publishDate
      */
      public function setPublishDate(DateTime $publishDate): void
      {
      $this->publishDate = $publishDate;
      }

      /**
      * Get year of publishDate
      *
      * @return int
      */
      public function getYearOfPublishDate(): int
      {
      if ($this->getPublishDate()) {
      return (int) $this->getPublishDate()->format('Y');
      }
      return 0;
      }

      /**
      * Get month of publishDate
      *
      * @return int
      */
      public function getMonthOfPublishDate(): int
      {
      if ($this->getPublishDate()) {
      return (int) $this->getPublishDate()->format('m');
      }
      return 0;
      }

      /**
      * Get day of publishDate
      *
      * @return int
      */
      public function getDayOfPublishDate(): int
      {
      if ($this->publishDate) {
      return (int) $this->publishDate->format('d');
      }
      return 0;
      }


      /**
      * Returns the jobType
      *
      * @return string
      */
      public function getJobType(): string
      {
      return $this->jobType;
      }
      /**
      * Sets the jobType
      *
      * @param string $jobType
      * @return void
      */
      public function setJobType(string $jobType): void
      {
      $this->jobType = $jobType;
      }


      /**
      * Returns the yearsOfExperience
      *
      * @return string
      */
      public function getYearsOfExperience(): string
      {
      return $this->yearsOfExperience;
      }
      /**
      * Sets the yearsOfExperience
      *
      * @param string $yearsOfExperience
      * @return void
      */
      public function setYearsOfExperience(string $yearsOfExperience): void
      {
      $this->yearsOfExperience = $yearsOfExperience;
      }

      /**
      * Returns the workingHrs
      *
      * @return string
      */
      public function getWorkingHrs(): string
      {
      return $this->workingHrs;
      }
      /**
      * Sets the workingHrs
      *
      * @param string $workingHrs
      * @return void
      */
      public function setWorkingHrs(string $workingHrs): void
      {
      $this->workingHrs = $workingHrs;
      }

      /**
      * Returns the workingDays
      *
      * @return string
      */
      public function getWorkingDays(): string
      {
      return $this->workingDays;
      }
      /**
      * Sets the workingDays
      *
      * @param string $workingDays
      * @return void
      */
      public function setWorkingDays(string $workingDays): void
      {
      $this->workingDays = $workingDays;
      }
      /**
      * Returns the salary
      *
      * @return string
      */
      public function getSalary(): string
      {
      return $this->salary;
      }
      /**
      * Sets the salary
      *
      * @param string $salary
      * @return void
      */
      public function setSalary(string $salary): void
      {
      $this->salary = $salary;
      }

      /**
      * Returns the jobRequirements
      *
      * @return string
      */
      public function getJobRequirements(): string
      {
      return $this->jobRequirements;
      }
      /**
      * Sets the jobRequirements
      *
      * @param string $jobRequirements
      * @return void
      */
      public function setJobRequirements(string $jobRequirements): void{
      $this->jobRequirements = $jobRequirements;
      }

      /**
      * Returns the educationQualification
      *
      * @return string
      */
      public function getEducationQualification(): string
      {
      return $this->educationQualification;
      }
      /**
      * Sets the educationQualification
      *
      * @param string $educationQualification
      * @return void
      */
      public function setEducationQualification(string $educationQualification): void{
      $this->educationQualification = $educationQualification;
      }
      /**
      * Returns the perksBenefits
      *
      * @return string
      */
      public function getPerksBenefits(): string
      {
      return $this->perksBenefits;
      }
      /**
      * Sets the perksBenefits
      *
      * @param string $perksBenefits
      * @return void
      */
      public function setPerksBenefits(string $perksBenefits): void{
      $this->perksBenefits = $perksBenefits;
      }

      }

    Create a repository for the job model

    1. Let’s create a repository JobRepository.php that can be use to perform any operation on Job from the back-end module or from the front-end plugin.
    2. The file path for the repository is employee\Classes\Domain\Repository\JobRepository.php
    3. Add the code below to the JobRepository.php file
      <?php

      declare(strict_types=1);

      namespace Company\Employee\Domain\Repository;

      use TYPO3\CMS\Extbase\Persistence\Repository;
      use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
      use TYPO3\CMS\Core\Utility\GeneralUtility;
      use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface;

      /**
      * The repository for Job
      */
      class JobRepository extends Repository
      {
      public function initializeObject()
      {
      /** @var QuerySettingsInterface $querySettings */
      $querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class);
      $querySettings->setRespectStoragePage(false);
      $this->setDefaultQuerySettings($querySettings);
      }
      }

    Connection establishment between the model and the database table

    1. Let’s establish the connection between the model and the database table by adding the below code in the file Classes.php. The file path is employee\Configuration\Extbase\Persistence\Classes.php
      \Company\Employee\Domain\Model\Job::class => [
      'tableName' => 'tx_employee_domain_model_job',
      ],
    2. Below is the screenshot after adding the above code in Classes.php file.
    3. Save the file,  toggle the extension (Deactivate once and then activate again), or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache” an the table will be created.

    Add the job

    1. Let’s check if an option has been added for the job details or not. Go to the List > select the folder where you have added all the employees > click the button “Create new record” and you will see the screen below
    2. After selecting the Job, you will see the fields we added to the job table, see the screenshot below
    3. Fill up all the details for the job and, save that job and you will see that job in the list as seen below

    Add the new field to the existing table

    1. Currently, we have 3 tables which are ’employ’, ‘education’ and ‘job’.
    2. Now we want to add an option to select the job when we add an ’employ’, and for that, we need to add a new field for job in the ’employ’ table.
    3. To add the job field in the employ table, it is required to update the files like SQL, TCA, and model.

    Update the SQL file

    1. Add the below line of code into the table tx_employee_domain_model_employ of the SQL file. File path is employee\ext_tables.sql
      job  int(11) DEFAULT 0 NOT NULL

    Below is the screenshot of the tx_employee_domain_model_employ table after adding the job field.

    Update the TCA file

    1. A column of the job is required to be added to the TCA file of the employ table. The file path is employee\Configuration\TCA\tx_employee_domain_model_employ.php
    2. Add the code below as a column in order to add the job column to the TCA file of the employ table.
       "job" => [
      'label' => 'Job',
      'config' => [
      'type' => 'select',
      'renderType' => 'selectSingle',
      'foreign_table' => 'tx_employee_domain_model_job',
      ],
      ],

      Below is the screenshot
    3. Then add the job into the ‘types’ as you can see in the below screenshot

      Save the file, toggle the extension (Deactivate once and then activate again), or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache” and then go to the List > select folder where you have stored all the employees > go to the Employ, select any record and you will see the option to select the job as you can see in the screenshot below

    Update the model file

    1. Now add the job to the model file of employ. The file path is employee\Classes\Domain\Model\Employ.php.
    2. You can replace the model code with the below code to add the job.
      <?php

      declare(strict_types=1);

      namespace Company\Employee\Domain\Model;

      use DateTime;
      use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
      use TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
      use TYPO3\CMS\Extbase\Domain\Model\FileReference;
      use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
      use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
      use Company\Employee\Domain\Model\Education;

      /**
      * Employ Model
      */
      class Employ extends AbstractEntity
      {
      /**
      * firstName
      *
      * @var string
      */
      protected $firstName = '';

      /**
      * lastName
      *
      * @var string
      */
      protected $lastName = '';

      /**
      * gender
      *
      * @var string
      */
      protected $gender = '';

      /**
      * birthDate
      *
      * @var DateTime
      */
      protected $birthDate;

      /**
      * joiningDate
      *
      * @var DateTime
      */
      public $joiningDate;

      /**
      * @Lazy
      * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
      */
      protected ObjectStorage $image;

      /**
      * bio
      *
      * @var string
      */
      protected $bio = '';

      /**
      * experiance
      *
      * @var string
      */
      protected $experiance = '';

      /**
      * salary
      *
      * @var string
      */
      protected $salary = '';

      /**
      * integer
      *
      * @var integer
      */
      protected $languages = '';

      /**
      * country
      *
      * @var string
      */
      protected $country = '';

      /**
      * job
      *
      * @var integer
      */
      protected $job = '';

      /**
      * @Lazy
      * @var ObjectStorage<Education>
      */
      protected $educations;




      public function __construct()
      {
      $this->initializeObject();

      }

      public function initializeObject(): void
      {
      $this->educations = $this->educations ?? new ObjectStorage();
      $this->image = $this->image ?? new ObjectStorage();
      }

      /**
      * Returns the firstName
      *
      * @return string
      */
      public function getFirstName(): string
      {
      return $this->firstName;
      }

      /**
      * Sets the firstName
      *
      * @param string $firstName
      * @return void
      */
      public function setFirstName(string $firstName): void
      {
      $this->firstName = $firstName;
      }


      /**
      * Returns the lastName
      *
      * @return string
      */
      public function getLastName(): string
      {
      return $this->lastName;
      }

      /**
      * Sets the lastName
      *
      * @param string $lastName
      * @return void
      */
      public function setLastName(string $lastName): void
      {
      $this->lastName = $lastName;
      }


      /**
      * Returns the gender
      *
      * @return string
      */
      public function getGender(): string
      {
      return $this->gender;
      }

      /**
      * Sets the gender
      *
      * @param string $gender
      * @return void
      */
      public function setGender(string $gender): void
      {
      $this->gender = $gender;
      }


      /**
      * Get birthDate
      *
      * @return DateTime|null
      */
      public function getBirthDate(): ?DateTime
      {
      return $this->birthDate;
      }

      /**
      * Set birthDate
      *
      * @param DateTime $birthDate
      */
      public function setBirthDate(DateTime $birthDate): void
      {
      $this->birthDate = $birthDate;
      }

      /**
      * Get year of birthDate
      *
      * @return int
      */
      public function getYearOfBirthDate(): int
      {
      if ($this->getBirthDate()) {
      return (int) $this->getBirthDate()->format('Y');
      }
      return 0;
      }

      /**
      * Get month of birthDate
      *
      * @return int
      */
      public function getMonthOfBirthDate(): int
      {
      if ($this->getBirthDate()) {
      return (int) $this->getBirthDate()->format('m');
      }
      return 0;
      }

      /**
      * Get day of birthDate
      *
      * @return int
      */
      public function getDayOfBirthDate(): int
      {
      if ($this->birthDate) {
      return (int) $this->birthDate->format('d');
      }
      return 0;
      }




      /**
      * Set joiningDate
      *
      * @param DateTime $joiningDate
      */
      public function setJoiningDate(DateTime $joiningDate): void
      {
      $this->joiningDate = $joiningDate;
      }

      /**
      * Get year of joiningDate
      *
      * @return int
      */
      public function getYearOfJoiningDate(): int
      {
      if ($this->getJoiningDate()) {
      return (int) $this->getJoiningDate()->format('Y');
      }
      return 0;
      }

      /**
      * Get month of joiningDate
      *
      * @return int
      */
      public function getMonthOfJoiningDate(): int
      {
      if ($this->getJoiningDate()) {
      return (int) $this->getJoiningDate()->format('m');
      }
      return 0;
      }

      /**
      * Get day of joiningDate
      *
      * @return int
      */
      public function getDayOfJoiningDate(): int
      {
      if ($this->joiningDate) {
      return (int) $this->joiningDate->format('d');
      }
      return 0;
      }


      /**
      * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
      */
      public function getImage(): ObjectStorage
      {
      return $this->image;
      }

      /**
      * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $image
      */
      public function setImage(ObjectStorage $image): self
      {
      $this->image = $image;
      return $this;
      }



      /**
      * Returns the bio
      *
      * @return string
      */
      public function getBio(): string
      {
      return $this->bio;
      }
      /**
      * Sets the bio
      *
      * @param string $bio
      * @return void
      */
      public function setBio(string $bio): void
      {
      $this->bio = $bio;
      }

      /**
      * Returns the experiance
      *
      * @return string
      */
      public function getExperiance(): string
      {
      return $this->experiance;
      }
      /**
      * Sets the experiance
      *
      * @param string $experiance
      * @return void
      */
      public function setExperiance(string $experiance): void
      {
      $this->experiance = $experiance;
      }


      /**
      * Returns the salary
      *
      * @return string
      */
      public function getSalary(): string
      {
      return $this->salary;
      }
      /**
      * Sets the salary
      *
      * @param string $salary
      * @return void
      */
      public function setSalary(string $salary): void
      {
      $this->salary = $salary;
      }


      /**
      * Returns the languages
      *
      * @return integer
      */
      public function getLanguages(): int
      {
      return $this->languages;
      }
      /**
      * Sets the languages
      *
      * @param integer $languages
      * @return void
      */
      public function setLanguages(int $languages): void
      {

      $this->languages = $languages;
      }

      /**
      * Returns the country
      *
      * @return string
      */
      public function getCountry(): string
      {
      return $this->country;
      }
      /**
      * Sets the country
      *
      * @param string $country
      * @return void
      */
      public function setCountry(string $country): void
      {
      $this->country = $country;
      }


      public function addEducations(Education $educations): void
      {
      $this->educations = $this->getEducations();
      $this->educations->attach($educations);
      }

      /**
      * @return ObjectStorage<Education>
      */
      public function getEducations(): ObjectStorage
      {
      if ($this->educations instanceof LazyLoadingProxy) {
      $this->educations->_loadRealInstance();
      }

      if ($this->educations instanceof ObjectStorage) {
      return $this->educations;
      }

      return $this->educations = new ObjectStorage();
      }


      public function removeEducations(Education $educations): void
      {
      $this->educations = $this->getEducations();
      $this->educations->detach($educations);
      }

      /**
      * @param ObjectStorage<Education> $educations
      */
      public function setEducations(ObjectStorage $educations): void
      {
      $this->educations = $educations;
      }

      /**
      * Returns the job
      *
      * @return integer
      */
      public function getjob(): int
      {
      return $this->job;
      }
      /**
      * Sets the job
      *
      * @param integer $job
      * @return void
      */
      public function setjob(int $job): void
      {
      $this->job = $job;
      }


      }

    Create a form in the front-end

    1. To add the new form in the front-end of TYPO3, it is required to create a new plugin that will render the form of employee into the front-end.

    Create the plugin for the job listing

    Plugin configuration
    1. Configure the plugin in the ext_localconf.php file. The file path is employee\ext_localconf.php
    2. Add the code below in employee\ext_localconf.php file to configure the job list plugin
      // job list plugin
      ExtensionUtility::configurePlugin(
      // extension key or extension name in UpperCamelCase (if the extension name is my_products then the name here will be MyProducts)
      'Employee',
      // A unique identifier for your plugin in UpperCamel Case that will be the name of your plugin
      'Joblist',
      // all actions
      [JobController::class => 'index'],
      // non-cacheable actions
      [JobController::class => 'index'],
      );
    3. Here a controller and action are defined in the ext_localconf.php file, and you will need to create that controller and action.
    Controller
    1. Create the controller file JobController.php to manage jobs.
    2. The path of the file will be employee\Classes\Controller\JobController.php.
    3. Add the below code to the JobController.php file:
      <?php
      declare(strict_types=1);

      namespace Company\Employee\Controller;

      use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
      use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
      use Psr\Http\Message\ResponseInterface;
      use TYPO3\CMS\Core\Context\Context;

      class JobController extends ActionController
      {
      /**
      * jobRepository
      *
      * @var \Company\Employee\Domain\Repository\JobRepository
      */
      protected $jobRepository = null;
      /**
      * @param \Company\Employee\Domain\Repository\JobRepository $jobRepository
      */
      public function injectJobRepository(\Company\Employee\Domain\Repository\JobRepository $jobRepository)
      {
      $this->jobRepository = $jobRepository;
      }
      public function __construct(
      protected readonly ModuleTemplateFactory $moduleTemplateFactory,
      Context $context,
      ) {
      $this->context = $context;
      }

      /**
      * action list
      *
      * @return string|object|null|void
      */
      public function indexAction(): ResponseInterface
      {
      $settings = $this->settings;
      $this->view->assign('settings', $settings);
      $jobs = $this->jobRepository->findAll();
      $this->view->assign('jobs', $jobs);
      return $this->htmlResponse();
      }



      }
      In the controller file, we have created the indexAction(). Save the controller file and now proceed to plugin registration.
    Plugin Registration
    1. We can register the new plugin from the file tt_content.php. The file path is employee\Configuration\TCA\Overrides\tt_content.php
    2. Add the below code in tt_content.php file
      \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(

      // extension name, matching the PHP namespaces (but without the vendor)
      'Employee',
      // arbitrary, but unique plugin name (not visible in the backend), must be the same as used in configurePlugin()
      'Joblist',
      // plugin title, as visible in the drop-down in the backend, use "LLL:" for localization
      'Job List',
      // icon identifier
      'EXT:employee/Resources/Public/Icons/Extension.png',
      );
    3. After registration, you will be able to use the extension as you will get it in General Plugin, but still, you can not see the plugin in the wizard. To add the plugin in the wizard, you will need to update the tsconfig file. The file path is employee\Configuration\page.tsconfig.
    Add the new plugin in the wizard
    1. Add the code below in the file page.tsconfig to make the plugin job list available in the wizard.
      employee_joblist {
      iconIdentifier = employee
      title = Job List
      description = List of Jobs
      tt_content_defValues {
      CType = list
      list_type = employee_joblist
      }
      }
      // Add the employee_joblist
      show := addToList(employee_employlist,employee_employdetail,employee_joblist)
    2. After updating the tsconfig file, you will see the job list plugin in the wizard, as seen in the screenshot below:
    3. Create the page to add jobs, here we have created the page “Jobs”, then click on the button “+Content” and you will see the wizard, select the plugin “JobList” and save the page.
    4. After adding the job list plugin to the page, check the job page in the front-end, and you will see the error as seen in the screenshot below
    5. The above error is occurring because we still didn’t create the template file for the joblist plugin. Let’s create the template file for the job list plugin. According to the controller and indexAction, create the folder named Job and create the template file index.html into that Job folder. the template file path will be employee\Resources\Private\Templates\Job\Index.html.
    6. Add the code below in the template file employee\Resources\Private\Templates\Job\Index.html.
      <div class="section">
      <div class="container">
      <div class="row my-5 ">
      <f:for each="{jobs}" as="job">
      <div class="flex col-4">
      <div class="card">
      <div class="image-wrapper ">
      <f:if condition="{job.bannerImage}">
      <f:for each="{job.bannerImage}" as="img">
      <f:image class="card-img-top" image="{img}" alt="{job.firstName}" width="400c" height="200c" />
      </f:for>
      </f:if>
      </div>
      <div class="job-details p-3 ">
      <div class="title">
      <h2>
      {job.jobTitle}
      </h2>
      </div>
      <div class="details">
      <div class=""><strong>Vacancy : </strong>{job.vacancy}</div>
      <div class=""><strong>Location : </strong>{job.jobLocation }</div>
      <div class=""><strong>job Type : </strong>{job.jobType }</div>
      <div class=""><strong>Experiance of Years : </strong>{job.yearsOfExperience }</div>
      <div class=""><strong>Working hours : </strong>{job.workingHrs }</div>
      <div class=""><strong>Working days : </strong>{job.workingDays }</div>
      </div>

      </div>
      </div>
      </div>
      </f:for>
      </div>
      </div>
      </div>
    7. Save the file, flush the cache, and reload the front-end page for the job list and you will see the jobs are rendering on front-end according to the template layout and design.
    8. After getting the list of jobs, we need to create the detail page of jobs, so when we click on any of the jobs we can get the form of that particular job, and for that we need to create the job detail plugin, so let’s create the job details plugin. Notify here that still there is no button added in the job listing to open the form of any particular job.

    Create the plugin for the job detail

    Create a new plugin for the job details to render the job details and form to apply for that job.

    Plugin configuration
    1. Configure the plugin in the ext_localconf.php file. The file path is employee\ext_localconf.php
    2. Add the code below in employee\ext_localconf.php file to configure the job detail plugin
      // job detail plugin
      ExtensionUtility::configurePlugin(
      // extension key or extension name in UpperCamelCase (if the extension name is my_products then the name here will be MyProducts)
      'Employee',
      // A unique identifier for your plugin in UpperCamel Case that will be the name of your plugin
      'Jobdetail',
      // all actions
      [JobController::class => 'detail'],
      // non-cacheable actions
      [JobController::class => 'detail'],
      );
    3. Here a controller and action are defined in the ext_localconf.php file, we already created the controller at the time of the job list plugin, so now we just need to create an action for the job detail plugin.
    Jobdetail action
    1. Add the code below to create the detail action in JobController.php file. The file path is employee\Classes\Controller\JobController.php
          /**
           * action detail
           *
           * @return string|object|null|void
           */
          public function detailAction(): ResponseInterface
          {
              $uid = GeneralUtility::_GP('uid');
              if ($uid) {
                  $details = $this->jobRepository->findByUid($uid);
                  $this->view->assign('details', $details);
              } else {
                  $this->view->assign('details', null);
              }
              return $this->htmlResponse();
          }
    Plugin Registration
    1. We can register the new plugin from the file tt_content.php. The file path is employee\Configuration\TCA\Overrides\tt_content.php
    2. Add the below code in tt_content.php file to register the job detail plugin
      \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
              // extension name, matching the PHP namespaces (but without the vendor)
              'Employee',
              // arbitrary, but unique plugin name (not visible in the backend), must be the same as used in configurePlugin() in ext_localconf.php
              'Jobdetail',
              // plugin title, as visible in the drop-down in the backend, use "LLL:" for localization
              'Job Form',
               // icon identifier
              'EXT:employee/Resources/Public/Icons/Extension.png',
          );
    3. After registration, you will be able to use the extension as you will get it in General Plugin, but still, you can not see the plugin in the wizard. To add the plugin in the wizard, you will need to update the tsconfig file.
    Add the new plugin in the wizard
    1. Add the code below in the file page.tsconfig to make the plugin job list available in the wizard. The file path is employee\Configuration\page.tsconfig.
      employee_jobdetail {
              iconIdentifier = employee
              title = Job Form
              description = Detail of Job & Form for apply
              tt_content_defValues {
                CType = list
                list_type = employee_jobdetail
              }
            }
      // Add the employee_jobdetail
      show := addToList(employee_employlist,employee_employdetail,employee_joblist,employee_jobdetail)
      
    2. After updating the tsconfig file, you will see the job list plugin in the wizard, as seen in the screenshot below:
    3. To link the detail page or form with the corresponding job, we need to add the flexform in the job list plugin so we can select the detail page ID and link the detail page/form to the corresponding job.
    4. To add the flexform, add the code below in the file employee\Configuration\TCA\Overrides\tt_content.php
       $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['employee_joblist'] = 'pi_flexform';
      
          \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue(
              'employee_joblist',
              'FILE:EXT:employee/Configuration/FlexForms/JobConfigs.xml'
          );
    5. Now create the XML file, as you know already we can give any name to the XML file, here the file name is JobConfigs.xml, and the file path is  employee\Configuration\FlexForms\JobConfigs.xml
    6. Add the code below to the JobConfigs.xml file
      <T3DataStructure>
      <sheets>
      <sDEF>
      <ROOT>
      <sheetTitle>
      General
      </sheetTitle>
      <type>array</type>
      <el>
      <settings.detailPid>
      <TCEforms>
      <label>Job Detail Page</label>
      <config>
      <type>group</type>
      <allowed>pages</allowed>
      <size>1</size>
      <maxitems>1</maxitems>
      <minitems>1</minitems>
      </config>
      </TCEforms>
      </settings.detailPid>
      </el>
      </ROOT>
      </sDEF>
      </sheets>
      </T3DataStructure>
    7. Now let’s add the job detail page ID to the job list plugin in the page Jobs. Go to Page > Jobs > Plugin tab and you will see a field to add the Page ID. see the screenshot below:
    8. Click on the “Page” button and you will get the option to select the detail page as you can see in the screenshot below:
    9. After selecting, you will see the below screen.
    10. After saving the page, flush the cache, and now let’s add the link to the detail page on the job name in joblist plugin’s template.
    11. Go to the employee\Resources\Private\Templates\Job\Index.html and replace that code with the code given below
      <div class="section">
      <div class="container">
      <div class="row my-5 ">
      <f:for each="{jobs}" as="job">
      <div class="flex col-4">
      <div class="card">
      <div class="image-wrapper ">
      <f:if condition="{job.bannerImage}">
      <f:for each="{job.bannerImage}" as="img">
      <f:image class="card-img-top" image="{img}" alt="{job.firstName}" width="400c" height="200c" />
      </f:for>
      </f:if>
      </div>
      <div class="job-details p-3 ">
      <div class="title">
      <h2>
      <f:link.page pageUid="{settings.detailPid}" additionalParams="{uid: job.uid}">
      {job.jobTitle}
      </f:link.page>
      </h2>
      </div>
      <div class="details">
      <div class=""><strong>Vacancy : </strong>{job.vacancy}</div>
      <div class=""><strong>Location : </strong>{job.jobLocation }</div>
      <div class=""><strong>job Type : </strong>{job.jobType }</div>
      <div class=""><strong>Experiance of Years : </strong>{job.yearsOfExperience }</div>
      <div class=""><strong>Working hours : </strong>{job.workingHrs }</div>
      <div class=""><strong>Working days : </strong>{job.workingDays }</div>
      </div>
      <div class="action">
      <div class="mt-3">
      <f:link.page pageUid="{settings.detailPid}" additionalParams="{uid: job.uid}"
      class="btn btn-primary">
      Apply For Job
      </f:link.page>
      </div>
      </div>
      </div>
      </div>
      </div>
      </f:for>
      </div>
      </div>
      </div>

      In the above code, we have linked the job title to the detail page of the particular job and added a button to apply for the job.
    12. Now flush the cache and check the front-end and you will see the updates on the job listing page.
    13. Now click on the button “Apply For Job” or on the job title, and you will see the error as seen in the screenshot below
    14. The error is occurring because we still don’t have created the template file for the job detail page, let’s create the template file for the detail action from the JobController.php
    15. Create the file Detail.html, the file path is employee\Resources\Private\Templates\Job\Detail.html
    16. Add the code below to the Detail.html file
      <div class="section">
          <div class="container">
              <div class="row">
                  <f:if condition="{details}">
                      <f:then>
                          <h1 class="text-center">{details.jobTitle}</h1>
                          <f:if condition="{details.bannerImage}">
                              <f:for each="{details.bannerImage}" as="img">
                                  <div class="image-wrapper text-center">
                                      <f:image image="{img}" alt="{details.firstName}" />
                                  </div>
                              </f:for>
                          </f:if>
                          <div class="">
                              <div class="">
                                  <h3>Details</h3>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Job Title  </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.jobTitle }</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Vacancy</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.vacancy}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Job Location</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.jobLocation }</p>
                                      </div>
                                  </div> 
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Salary </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.salary}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Country </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.country}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>jobType</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.jobType }</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>yearsOfExperience</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.yearsOfExperience }</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>workingHrs</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.workingHrs}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>workingDays</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.workingDays}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>jobDescription</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.html>{details.jobDescription}</f:format.html>
                                          </p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>jobRequirements</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.html>{details.jobRequirements}</f:format.html>
                                          </p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>educationQualification </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.html>{details.educationQualification }</f:format.html>
                                          </p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>perksBenefits </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.html>{details.perksBenefits }</f:format.html>
                                          </p>
                                      </div>
                                  </div>
                              </div>
                               
                          </div>
                      </f:then>
                      <f:else>
                          <div class="alert alert-error">
                              Job not found!!
                          </div>
                      </f:else>
                  </f:if>
              </div>
          </div>
      </div>
      
    17. Save the file, flush the cache, and reload the front-end, click on the button”Apply For Job” or click on the job title in the job listing and you will see that the detail page does open now.
    18. Let’s add a form to the detail page to apply for the job.

    Create the front-end form

    Let’s create a form in the detail page of the job. Follow the steps given below to create a form to apply for the job.

    1. Add the below code in the template file of the detail page, the file path is employee\Resources\Private\Templates\Job\Detail.html
      <div class="form">
      <div class="form-wrapper">
      <f:form action="create" enctype="multipart/form-data" name="newEmploy" object="{newEmploy}">
      <h2 class="text-center">Apply For Job </h2>
      <f:render partial="Employe/FormFields" arguments="{_all}" />
      <div class="d-flex justify-content-end mt-3">
      <f:form.submit class="btn btn-primary" value="Apply" />
      </div>
      </f:form>
      </div>
      </div>
      Add the above code after the description for the detail page is completed, specifically after the <div class=”job-data”> is completed, as you can see in the screenshot below
    2. Save the file, flush the cache, and open the detail page of any job, you will see an error as in the screenshot below:
    3. The error is occurring because we had added a line of code in Detail.html to render the partial: <f:render partial="Employe/FormFields" arguments="{_all}" />
      But we don’t have the file on that path. Let’s create the file ‘FormFields.html‘ in partial. The file path is employee\Resources\Private\Partials\Employe\FormFields.html
    4. Add the code below to the employee\Resources\Private\Partials\Employe\FormFields.html file.
      <f:form.validationResults>
      <f:if condition="{validationResults.flattenedErrors}">
      <div class="alert ">
      <f:for each="{validationResults.flattenedErrors}" as="errors" key="propertyPath">
      <div class="alert alert-danger"> {propertyPath}
      <f:for each="{errors}" as="error">
      <p>{error.code}: {error}</p>
      </f:for>
      </div>
      </f:for>
      </div>
      </f:if>
      </f:form.validationResults>

      <!-- this hidden field will bind the job and the job form, so it can be identify that the submitted form is applied for which job -->
      <f:form.hidden property="job" class="form-control" value="{details.uid}" />
      <f:form.hidden property="salary" class="form-control" value="0" />
      <f:form.hidden property="pid" class="form-control" value="{details.pid}" />

      <div class="row justify-content-center">
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="firstName">
      First Name
      </label><br />
      <f:form.textfield property="firstName" class="form-control" placeholder="Enter First Name"
      required="true" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="lastName">
      Last Name
      </label><br />
      <f:form.textfield property="lastName" class="form-control" placeholder="Enter Last Name" required="true" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="country">
      Country
      </label><br />
      <f:form.select property="country" class="form-control"
      options="{english: 'English', german: 'German', french:'French'}" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="gender">
      Gender
      </label><br />
      <label class="control-label mb-10" for="male">
      <f:form.radio property="gender" value="1" id="male" /> Male
      </label>
      <label class="control-label mb-10" for="female">
      <f:form.radio property="gender" value="2" id="female" /> FeMale
      </label>
      </div>
      </div>
      <f:comment>
      <div class="col-12 my-2">
      <div class="form-group">
      Languages<br />
      <f:form.checkbox property="languages" value="en" multiple="1" /> English
      <f:form.checkbox property="languages" value="fr" multiple="1" /> French
      <f:form.checkbox property="languages" value="de" multiple="1" /> German
      </div>
      </div>
      </f:comment>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="birthDate">
      Birth Date
      </label><br />
      <f:form.textfield property="birthDate" value="{employ.birthDate -> f:format.date()}" class="form-control"
      type="date" required="true" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="joiningDate">
      Joining Date
      </label><br />
      <f:form.textfield property="joiningDate" value="{employ.joiningDate -> f:format.date()}"
      class="form-control" type="date" required="true" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="bio">
      Bio
      </label><br />
      <f:form.textarea property="bio" class="form-control" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="experiance">
      Experience
      </label><br />
      <f:form.textarea property="experiance" class="form-control" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="image">
      Image
      </label><br />
      <f:form.upload property="image" class="form-control" accept=".png,.jpg,.jpeg" />
      </div>
      </div>
      </div>
      Save the file, flush the cache, and reload the detail page of the job and you will see the error has been resolved and the form is rendering after the job description as you can see in the screenshot below:
    5. Fill up the above form and click on the Apply button to submit the form, but you will see an error as seen in the screenshot below:

      The error is occurring because we didn’t add the action “Create” in ext_localconf.php file to allow it for the form, and we have already added the action attribute with the “Create” action into the job application form.
      Infect we didn’t create the action “create” for the JobController.
    6. Let’s create the action createAction() for the JobController.php and make it available by adding it to the ext_localconf.php file.
    7. Replace the code with the code below in the JobController.php file to create an action createAction().
      <?php
      declare(strict_types=1);

      namespace Company\Employee\Controller;

      use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
      use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
      use Psr\Http\Message\ResponseInterface;
      use TYPO3\CMS\Core\Context\Context;
      use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
      use TYPO3\CMS\Core\Utility\GeneralUtility;
      use Company\Employee\Domain\Model\Employ;
      use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;

      class JobController extends ActionController
      {
      /**
      * jobRepository
      *
      * @var \Company\Employee\Domain\Repository\JobRepository
      */
      protected $jobRepository = null;
      /**
      * @param \Company\Employee\Domain\Repository\JobRepository $jobRepository
      */
      public function injectJobRepository(\Company\Employee\Domain\Repository\JobRepository $jobRepository)
      {
      $this->jobRepository = $jobRepository;
      }


      /**
      * employRepository
      *
      * @var \Company\Employee\Domain\Repository\EmployRepository
      */
      protected $employRepository = null;
      /**
      * @param \Company\Employee\Domain\Repository\EmployRepository $employRepository
      */
      public function injectEmployRepository(\Company\Employee\Domain\Repository\EmployRepository $employRepository)
      {
      $this->employRepository = $employRepository;
      }


      public function __construct(
      protected readonly ModuleTemplateFactory $moduleTemplateFactory,
      Context $context,
      ) {
      $this->context = $context;
      }

      /**
      * action list
      *
      * @return string|object|null|void
      */
      public function indexAction(): ResponseInterface
      {
      $settings = $this->settings;
      $this->view->assign('settings', $settings);
      $jobs = $this->jobRepository->findAll();
      $this->view->assign('jobs', $jobs);
      return $this->htmlResponse();
      }

      /**
      * action detail
      *
      * @return string|object|null|void
      */
      public function detailAction(): ResponseInterface
      {
      $uid = GeneralUtility::_GP('uid');
      if ($uid) {
      $details = $this->jobRepository->findByUid($uid);
      $this->view->assign('details', $details);
      } else {
      $this->view->assign('details', null);
      }
      return $this->htmlResponse();
      }
      /**
      * action create
      * @param Employ $newEmploy
      * @return void
      */
      public function createAction(Employ $newEmploy): ResponseInterface
      {

      $redirectPid = (int) $this->settings['listPid'];
      $uriBuilder = $this->uriBuilder;
      $uri = $uriBuilder
      ->setTargetPageUid($redirectPid)
      ->build();
      $this->addFlashMessage('Employee Added successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
      $this->employRepository->add($newEmploy);
      return $this->redirectToUri($uri);
      }

      }
      Now let’s add the createAction() into the ext_localconf.php file to allow the action or make it available for the JobController. See the screenshot below and add the create action into the existing ExtensionUtility::configurePlugin

      Note: Here we don’t need to write the code for the job plugin specifically as the code has already been written for the employee extension in setup.typoscript and constants.typoscript files.
    8. Now fill up the form and submit the form by clicking on the “Apply” button and you will see the screen below:
    9. Once the form is submitted, the page should be redirected to the job listing page, but we don’t have any configurations to redirect to the job listing page right after the job application for has been submitted. So let’s add the configs to redirect the page after form submission done successfully.
    10. To add the flexform, add the code below in the file employee\Configuration\TCA\Overrides\tt_content.php
          $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['employee_jobdetail'] = 'pi_flexform';
      
          \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue(
              'employee_jobdetail',
              'FILE:EXT:employee/Configuration/FlexForms/Jobdetail.xml'
          );
    11. Now create the XML file, the file name is Jobdetail.xml, and the file path is  employee\Configuration\FlexForms\Jobdetail.xml
    12. Add the code below to the Jobdetail.xml file
      <T3DataStructure>
      <sheets>
      <sDEF>
      <ROOT>
      <sheetTitle>
      General
      </sheetTitle>
      <type>array</type>
      <el>
      <settings.listPid>
      <TCEforms>
      <label>Job List Page</label>
      <config>
      <type>group</type>
      <allowed>pages</allowed>
      <size>1</size>
      <maxitems>1</maxitems>
      <minitems>1</minitems>
      </config>
      </TCEforms>
      </settings.listPid>
      </el>
      </ROOT>
      </sDEF>
      </sheets>
      </T3DataStructure>
      Save the file and now set the page ID in the job detail page to define which page should be shown once the form is submitted successfully.
    13. Go to Page > job-detail > Plugin tab and you will see a field to add the Page ID. see the screenshot below:
    14. Select the page you want to show once the detail page’s form is submitted successfully. Here we are going to select the page “Jobs” to redirect on job list.
    15. Now fill out the form on the job detail page and click on the “Apply” button and you will be redirected to the job listing page instead of “Job not found!!.
    16. Please note here that we have added a line of code into employee\Resources\Private\Partials\Employe\FormFields.html file, to add a hidden field <f:form.hidden property="job" class="form-control" value="{details.uid}" />, so the connection between the form and the job can be established, and it can be identified that the submitted form is applied for which job.
    How to upload an image in front-end form without error

    If you upload an image to the image field from the front-end form and click on the “Apply” button, you will see an error as seen in the screenshot below

    To resolve the above error, we need to update the file of controller, model, and persistence as we already have done in the employee plugin.

    1. Update the controller file, file path is: employee\Classes\Controller\JobController.php
      1. Add the core functionalities/packages, update the createAction code and add the code for initializeCreateAction. You can see the description for initializeAction in the ‘Create new employees in the back-end module’ section.
      2. See the below code and just update the existing code of the JobController.php

        //Add the initializeCreateAction

        /**
        * Set TypeConverter option for image upload
        */
        public function initializeCreateAction()
        {
        if ($this->arguments['newEmploy']) {
        $propertyMappingConfiguration = $this->arguments['newEmploy']->getPropertyMappingConfiguration();
        $propertyMappingConfiguration->allowAllProperties();
        $propertyMappingConfiguration->forProperty('birthDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->forProperty('joiningDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->skipProperties('image');
        }
        }


        //Update the createAction

        /**
        * action create
        * @param Employ $newEmploy
        * @return void
        */
        public function createAction(Employ $newEmploy): ResponseInterface
        {
        $uploaded = $this->request->getUploadedFiles();
        if (sizeOf($uploaded) > 0) {
        $__imag = $uploaded['newEmploy']['image'];
        $resourceFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
        $storage = $resourceFactory->getDefaultStorage();
        $newFile = $storage->addFile(
        $__imag->getTemporaryFileName(),
        $storage->getRootLevelFolder(),
        $__imag->getClientFilename()
        );
        $fileReference = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\FileReference::class);
        $fileReference->setOriginalResource($newFile);
        $newEmploy->addImage($fileReference);
        }
        $this->employRepository->add($newEmploy);
        $this->addFlashMessage('Employee Added successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
        $redirectPid = (int) $this->settings['listPid'];
        $uriBuilder = $this->uriBuilder;
        $uri = $uriBuilder
        ->setTargetPageUid($redirectPid)
        ->build();
        return $this->redirectToUri($uri);
        }
        }
    2. After the controller, it is required to create the filereference model to override the TYPO3 default model, but we already have done this for the employees in the backend module’s ‘FileReference model override‘ section. So there is no need to do the same thing again.
    3. After that, it is required to update the persistence file, but we already have updated that too. You can check it in the “Persistence” section.
    4. Now after the above updates, it is required to update the Employ.php model so the image can be store for the employee. But we already made the changes in the model file for the employees in the backend module.

    Sub-table entry for the front-end form

    1. There is a table of education which has been used as a field for the employ table. So the education table is a sub-table for the employ table.
    2. So we should provide the education field for the front-end job application form.
    3. Let’s add the new field in the employee\Resources\Private\Partials\Employe\FormFields.html file to provide the field for the education of the job applicant.
      Add the below code in the new line to add the field for the education field
      <div class="row justify-content-center">
      <div>
      <h2>Education Details</h2>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="education.title">
      Title
      </label><br />
      <f:form.textfield property="education.title" class="form-control" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="education.rollnumber">
      Roll Number
      </label><br />
      <f:form.textfield property="education.rollnumber" class="form-control" required="true" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="education.cgpa">
      CGPA
      </label><br />
      <f:form.textfield type="number" property="education.cgpa" class="form-control" required="true" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="education.university">
      University
      </label><br />
      <f:form.select property="education.university" class="form-control"
      options="{uni-1: 'University 1', uni-2: 'University 2', uni-3:'University 3'}" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="startDate">
      Start Date
      </label><br />
      <f:form.textfield property="education.startDate" value="{employ.education.startDate -> f:format.date()}" class="form-control"
      type="date" required="true" />
      </div>
      </div>
      <div class="col-12 my-2">
      <div class="form-group">
      <label class="control-label mb-10" for="endDate">
      End Date
      </label><br />
      <f:form.textfield property="education.endDate" value="{employ.education.endDate -> f:format.date()}" class="form-control"
      type="date" required="true" />
      </div>
      </div>

      </div>
    4. Then update the createAction() in employee\Classes\Controller\JobController.php file.
      Replace the initializeCreateAction() code and createAction() code with the below code
          /**
      * Set TypeConverter option for image upload
      */
      public function initializeCreateAction()
      {
      if ($this->arguments['newEmploy']) {
      DebuggerUtility::var_dump($this->request->getArguments('newEmploy'));
      $propertyMappingConfiguration = $this->arguments['newEmploy']->getPropertyMappingConfiguration();
      $propertyMappingConfiguration->allowAllProperties();
      $propertyMappingConfiguration->forProperty('birthDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
      $propertyMappingConfiguration->forProperty('joiningDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
      $propertyMappingConfiguration->skipProperties('image');
      $propertyMappingConfiguration->skipProperties('education');
      }
      }
      /**
      * action create
      * @param Employ $newEmploy
      * @return void
      */
      public function createAction(Employ $newEmploy): ResponseInterface
      {
      $requestData = $this->request->getArguments('newEmploy');
      $uploaded = $this->request->getUploadedFiles();
      if (sizeOf($uploaded) > 0) {
      $__imag = $uploaded['newEmploy']['image'];
      $resourceFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
      $storage = $resourceFactory->getDefaultStorage();
      $newFile = $storage->addFile(
      $__imag->getTemporaryFileName(),
      $storage->getRootLevelFolder(),
      $__imag->getClientFilename()
      );
      $fileReference = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\FileReference::class);
      $fileReference->setOriginalResource($newFile);
      $newEmploy->addImage($fileReference);
      }
      if($requestData['newEmploy']['education']){
      $edu = $requestData['newEmploy']['education'];
      $education = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\Education::class);
      $education->setCgpa((float)$edu['cgpa']);
      $education->setRollnumber($edu['rollnumber']);
      $education->setStartDate(new DateTime($edu['startDate']));
      $education->setEndDate(new DateTime($edu['endDate']));
      $education->setTitle($edu['title']);
      $newEmploy->addEducations($education);
      }
      $this->employRepository->add($newEmploy);
      $this->addFlashMessage('Employee Added successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
      $redirectPid = (int) $this->settings['listPid'];
      $uriBuilder = $this->uriBuilder;
      $uri = $uriBuilder
      ->setTargetPageUid($redirectPid)
      ->build();
      return $this->redirectToUri($uri);
      }
      Save the file, flush the cache, and reload the detail page and you will see the fields for the education details as seen in the screenshot below:
    5. When the form is submitted with all the details, we will get all the details of the form in the list module of the TYPO3 admin panel.

    How do we provide extension configurations/settings?

    1. To provide the extension configurations or extension settings, it is required to create an SQL table, TCA file, model file, repository, and persistence files.
    2. Follow the below steps to create the required files.

    Create a new table for the Configurations

    1. Create a new table “tx_employee_domain_model_conf” for the configurations in the file employee\ext_tables.sql.
    2. Add the code below to create the table
      CREATE TABLE tx_employee_domain_model_conf ( 
      imagelist int(11) unsigned DEFAULT '0',
      imagedetail int(11) unsigned DEFAULT '0',
      );

    Create a TCA file for the conf table

    1. Create the TCA file tx_employee_domain_model_conf.php and the file path is employee\Configuration\TCA\tx_employee_domain_model_conf.php.
    2. Add the code below to the file tx_employee_domain_model_conf.php
      <?php
      return [
          'ctrl' => [
              'title' => 'Employee Configutaion',
              'label' => 'imagelist',
              'tstamp' => 'tstamp',
              'crdate' => 'crdate',
              'versioningWS' => true,
              'label_alt_force' => true,
              'origUid' => 't3_origuid',
              'languageField' => 'sys_language_uid',
              'transOrigPointerField' => 'l10n_parent',
              'transOrigDiffSourceField' => 'l10n_diffsource',
              'delete' => 'deleted',
              'enablecolumns' => [
                  'disabled' => 'hidden',
                  'starttime' => 'starttime',
                  'endtime' => 'endtime',
              ],
              'searchFields' => 'imagelist',
              'iconfile' => 'EXT:employee/Resources/Public/Icons/Extension.png',
              'security' => [
                  'ignorePageTypeRestriction' => true,
              ],
          ],
          'types' => [
              '1' => [
                  'showitem' => 'imagelist,imagedetail'
              ],
          ],
          'columns' => [
              'sys_language_uid' => [
                  'exclude' => true,
                  'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
                  'config' => [
                      'type' => 'language',
                  ],
              ],
              'l10n_parent' => [
                  'displayCond' => 'FIELD:sys_language_uid:>:0',
                  'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
                  'config' => [
                      'type' => 'select',
                      'renderType' => 'selectSingle',
                      'default' => 0,
                      'items' => [
                          ['label' => '', 'value' => 0]
                      ],
                      'foreign_table' => 'tx_employee_domain_model_conf',
                      'foreign_table_where' => 'AND {#tx_employee_domain_model_conf}.{#pid}=###CURRENT_PID### AND {#tx_employee_domain_model_conf}.{#sys_language_uid} IN (-1,0)',
                  ],
              ],
              'l10n_diffsource' => [
                  'config' => [
                      'type' => 'passthrough',
                  ],
              ],
              'hidden' => [
                  'config' => [
                      'type' => 'check',
                      'items' => [
                          ['label' => 'Disable'],
                      ],
                  ]
              ],
              'starttime' => [
                  'exclude' => true,
                  'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
                  'config' => [
                      'type' => 'input',
                      'renderType' => 'datetime',
                      'eval' => 'datetime,int',
                      'default' => 0,
                      'behaviour' => [
                          'allowLanguageSynchronization' => true
                      ]
                  ],
              ],
              'endtime' => [
                  'exclude' => true,
                  'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
                  'config' => [
                      'type' => 'input',
                      'renderType' => 'datetime',
                      'eval' => 'datetime,int',
                      'default' => 0,
                      'range' => [
                          'upper' => mktime(0, 0, 0, 1, 1, 2038)
                      ],
                      'behaviour' => [
                          'allowLanguageSynchronization' => true
                      ]
                  ],
              ],
              'imagelist' => [
                  'exclude' => 1,
                  'label' => 'Show Image in List',
                  'config' => [
                      'type' => 'check',
                      'items' => [ 
                          ['Show Image in List', ''],
                      ],
                  ]
              ],
              'imagedetail' => [
                  'exclude' => 1,
                  'label' => 'Show Image in Detail Page',
                  'config' => [
                      'type' => 'check',
                      'items' => [ 
                          ['Show Image in Detail', ''],
                      ],
                  ]
              ],
      
          ],
      ];
      

    Create the model for the conf table

    1. Let’s create the model file to fetch the records of the conf table.
    2. Create the model file Conf.php and the file path is employee\Classes\Domain\Model\Conf.php
    3. Add the code below to the model file
      <?php

      declare(strict_types=1);

      namespace Company\Employee\Domain\Model;

      use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

      /**
      * Conf Model
      */
      class Conf extends AbstractEntity
      {
      /**
      * imagelist
      *
      * @var string
      */
      protected $imagelist = '';

      /**
      * imagedetail
      *
      * @var string
      */
      protected $imagedetail = '';


      /**
      * Returns the imagelist
      *
      * @return string
      */
      public function getImagelist(): string
      {
      return $this->imagelist;
      }

      /**
      * Sets the imagelist
      *
      * @param string $imagelist
      * @return void
      */
      public function setImagelist(string $imagelist): void
      {
      $this->imagelist = $imagelist;
      }


      /**
      * Returns the imagedetail
      *
      * @return string
      */
      public function getImagedetail(): string
      {
      return $this->imagedetail;
      }

      /**
      * Sets the imagedetail
      *
      * @param string $imagedetail
      * @return void
      */
      public function setImagedetail(string $imagedetail): void
      {
      $this->imagedetail = $imagedetail;
      }


      }

    Create a repository for the conf model

    1. Create the file ConfRepository.php, and the file path is employee\Classes\Domain\Repository\ConfRepository.php
    2. Add the code below to the file ConfRepository.php
      <?php

      declare(strict_types=1);

      namespace Company\Employee\Domain\Repository;

      use TYPO3\CMS\Extbase\Persistence\Repository;
      use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;
      use TYPO3\CMS\Core\Utility\GeneralUtility;
      use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface;

      /**
      * The repository for Employ
      */
      class ConfRepository extends Repository
      {
      public function initializeObject()
      {
      /** @var QuerySettingsInterface $querySettings */
      $querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class);
      $querySettings->setRespectStoragePage(false);
      $this->setDefaultQuerySettings($querySettings);
      }
      }

    Connection establishment between the model and the database table

    1. To establish the connection between the Conf.php model file and the database table, it is required to update the persistence file Classes.php, which is already created, and the file path is employee\Configuration\Extbase\Persistence\Classes.php
    2. Add the code below to the file Classes.php after the code for the job.
       \Company\Employee\Domain\Model\Conf::class => [
      'tableName' => 'tx_employee_domain_model_conf',
      ],
    3. Below is the screenshot of the file Classes.php after adding the above code.

    After creating and updating the required files,  toggle the extension (Deactivate once and then activate again), or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache”.

    Now let’s add the back-end module config options, from where we can use the extension configurations.

    Back-end module configuration options

    1. Let’s add the configuration options in the back-end module so you can change the settings for the extension.
    2. Here we are using the back-end module we already have created, you can create a new back-end module also as per your requirements.
    3. First of all, add the fields for the configurations in the template file: employee\Resources\Private\Backend\Templates\Employ\Belist.html
    4. Replace the code in Belist.html file with the given code below
      <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
      xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" data-namespace-typo3-fluid="true">
      <f:layout name="Default" />

      <f:section name="Content">
      <div class="section">
      <div class="container">
      <f:flashMessages />
      <ul class="nav nav-tabs" id="myTab" role="tablist">
      <li class="nav-item" role="presentation">
      <button class="nav-link active" id="config-tab" data-bs-toggle="tab" data-bs-target="#config"
      type="button" role="tab" aria-controls="config" aria-selected="true">
      Settings
      </button>
      </li>
      <li class="nav-item" role="presentation">
      <button class="nav-link" id="employ-tab" data-bs-toggle="tab" data-bs-target="#employ" type="button"
      role="tab" aria-controls="employ" aria-selected="false">
      Employees
      </button>
      </li>
      </ul>
      <div class="tab-content" id="myTabContent">
      <div class="tab-pane fade show active" id="config" role="tabpanel" aria-labelledby="config-tab">
      <div class="my-3">
      <h1 class="text-center">Settings Regarding Extension</h1>
      <div class="form-wrapper">
      <f:form action="updatesetting" enctype="multipart/form-data" name="confData" object="{confData}">
      <f:render partial="Conf/FormFields" arguments="{_all}" />
      <div class="d-flex justify-content-end">
      <f:form.submit class="btn btn-primary" value= "Update Settings" />
      </div>
      </f:form>
      </div>
      </div>
      </div>
      <div class="tab-pane fade" id="employ" role="tabpanel" aria-labelledby="employ-tab">
      <div class="my-3">
      <div class="d-flex justify-content-end">
      <f:link.action action="new" class="btn btn-primary">Add New Employee</f:link.action>
      </div>
      <table class="table table-responsive">
      <thead>
      <tr>
      <th>Image</th>
      <th>FirstName</th>
      <th>LastName</th>
      <th>Gender</th>
      <th>Action</th>
      </tr>
      </thead>
      <tbody>
      <f:for each="{employs}" as="emp">
      <tr>
      <td>
      <f:if condition="{emp.image}">
      <f:for each="{emp.image}" as="img">
      <f:image image="{img}" alt="{emp.firstName}" width="100c"
      height="100c" />
      </f:for>
      </f:if>
      </td>
      <td>{emp.firstName}</td>
      <td>{emp.lastName}</td>
      <td>{emp.gender == 1 ? "Male" :"Female"}</td>
      <td>
      <f:link.action action="edit" class="btn btn-primary"
      arguments="{uid:emp.uid}">
      Edit
      </f:link.action>
      <f:link.action action="delete" class="btn btn-primary"
      arguments="{uid:emp.uid}">Delete
      </f:link.action>
      </td>
      </tr>
      </f:for>
      </tbody>
      </table>
      </div>
      </div>
      </div>
      </div>
      </div>
      </f:section>
      </html>

      By adding the above code, you are adding the tabs in the back-end module, one tab is for settings and another one is for the employee listing.
    5. After adding the above code in Belist.html file, and then after reloading the back-end module, you will see an error as in the screenshot below:
    6. The above error is occurring as we don’t have the template file in partial and the code for the partial template file has been added in the above code of Belist.html file to render the partial=”Conf/FormFields”.
    7. Let’s create the FormFields.html for the backend module settings, to add the field for the settings we are going to provide.
    8. Create the FormFields.html and the file path is employee\Resources\Private\Backend\Partials\Conf\FormFields.html
    9. Add the code below to the employee\Resources\Private\Backend\Partials\Conf\FormFields.html file
      <f:form.validationResults>
      <f:if condition="{validationResults.flattenedErrors}">
      <div class="alert ">
      <f:for each="{validationResults.flattenedErrors}" as="errors" key="propertyPath">
      <div class="alert alert-danger"> {propertyPath}
      <f:for each="{errors}" as="error">
      <p>{error.code}: {error}</p>
      </f:for>
      </div>
      </f:for>
      </div>
      </f:if>
      </f:form.validationResults>
      <div class="row">
      <div class="col-12">
      <div class="form-group">
      <f:form.checkbox property="imagelist" value="1" checked="{confData.imagelist} == 1" id="imagelist" />
      <label class="control-label mb-10" for="imagelist">
      Show Image in List
      </label>
      </div>
      </div>
      <div class="col-12">
      <div class="form-group">
      <f:form.checkbox property="imagedetail" value="1" checked="{confData.imagedetail} == 1" id="imagedetail" />
      <label class="control-label mb-10" for="imagedetail">
      Show Image in Detail Page
      </label>
      </div>
      </div>
      </div>
      After adding the above code, save the file, flush all the cache, and reload the back-end module, and you will see the tabs are rendering for settings and the employee list, and also the fields are rendering for the settings as seen in the screenshot below:
    10. After this, when you will click on the button “Update Settings”, nothing will happen as still there is no action defined for it. To resolve the issue and make it functional, let’s create an action updatesettingAction() and add the action in the Modules.php file to make it available.
    11. Add the code below in the employee\Classes\Controller\EmployController.php file to create an action updatesettingAction().
      public function updatesettingAction(): ResponseInterface
      {
      $data = $this->request->getArgument('confData');
      $existSettings = $this->confRepository->findByUid(1);
      if($existSettings){
      $existSettings->setImagelist($data['imagelist']);
      $existSettings->setImagedetail($data['imagedetail']);
      $this->confRepository->update($existSettings);
      }else{
      $Conff = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\Conf::class);
      $Conff->setImagelist($data['imagelist']);
      $Conff->setImagedetail($data['imagedetail']);
      $this->confRepository->add($Conff);
      }
      $this->addFlashMessage('Settings Updated successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
      return $this->redirect('belist');
    12. After creating the action, now add this action to the Modules.php file to make it available. The file path is employee\Configuration\Backend\Modules.php

    13. After all the above updates, check/uncheck the checkbox settings and then click on the button “Update Settings” and you will see that it’s working.
    14. Now we need to update it so the changes can be reflected on the front-end.

    How to reflect settings updates on the front-end

    1. After adding the extension setting in back-end module, we have to bind it with the front-end so that when we update the settings of the extension from the back-end module, we can get the updates on front-end.
    2. In order to bind the settings with front-end, we need to pass the data from the controller to the template file of the front-end plugin. Update the controller files, below are the screenshots of the updated things, and after the screenshots, there is the full code of the controller files, so you can check the updated things in screenshots, and then just copy the controller file’s code and replace the existing code in your file with the latest given code.

    EmployController.php

    1. Below are the screenshots from the employee\Classes\Controller\EmployController.php file of the updated data for the table tx_employee_domain_model_conf.
      1. Add the confRepository
      2. Add confData in indexAction()
      3. Add confData in detailAction()
      4. Add confData in belistAction()
      5. Below is the full code of the employee\Classes\Controller\EmployController.php file after adding the things you can see in the above screenshots:
        <?php
        declare(strict_types=1);

        namespace Company\Employee\Controller;

        use DateTime;
        use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
        use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
        use Psr\Http\Message\ResponseInterface;
        use TYPO3\CMS\Core\Context\Context;
        use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
        use TYPO3\CMS\Core\Utility\GeneralUtility;
        use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
        use Company\Employee\Domain\Model\Employ;

        class EmployController extends ActionController
        {
        /**
        * employRepository
        *
        * @var \Company\Employee\Domain\Repository\EmployRepository
        */
        protected $employRepository = null;
        /**
        * @param \Company\Employee\Domain\Repository\EmployRepository $employRepository
        */
        public function injectEmployRepository(\Company\Employee\Domain\Repository\EmployRepository $employRepository)
        {
        $this->employRepository = $employRepository;
        }


        /**
        * confRepository
        *
        * @var \Company\Employee\Domain\Repository\ConfRepository
        */
        protected $confRepository = null;
        /**
        * @param \Company\Employee\Domain\Repository\ConfRepository $confRepository
        */
        public function injectConfRepository(\Company\Employee\Domain\Repository\ConfRepository $confRepository)
        {
        $this->confRepository = $confRepository;
        }
        public function __construct(
        protected readonly ModuleTemplateFactory $moduleTemplateFactory,
        Context $context,
        ) {
        $this->context = $context;
        }

        /**
        * action list
        *
        * @return string|object|null|void
        */
        public function indexAction(): ResponseInterface
        {
        $settings = $this->settings;
        $this->view->assign('settings', $settings);
        $employs = $this->employRepository->findAll();
        $this->view->assign('employs', $employs);
        $confData = $this->confRepository->findByUid(1);
        $this->view->assign('confData', $confData);
        return $this->htmlResponse();
        }


        /**
        * action detail
        *
        * @return string|object|null|void
        */
        public function detailAction(): ResponseInterface
        {
        $uid = GeneralUtility::_GP('uid');
        $confData = $this->confRepository->findByUid(1);
        $this->view->assign('confData', $confData);
        if ($uid) {
        $details = $this->employRepository->findByUid($uid);
        $this->view->assign('details', $details);
        } else {
        $this->view->assign('details', null);
        }

        return $this->htmlResponse();
        }

        public function belistAction(): ResponseInterface
        {
        $employs = $this->employRepository->findAll();
        $this->view->assign('employs', $employs);
        $confData = $this->confRepository->findByUid(1);
        $this->view->assign('confData', $confData);
        $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Employee List");
        $moduleTemplate->setContent($this->view->render());
        return $this->htmlResponse($moduleTemplate->renderContent());
        }

        public function newAction(): ResponseInterface
        {
        $newEmploy = new Employ();
        $this->view->assign('newEmploy', $newEmploy);
        $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Add Employee");
        $moduleTemplate->setContent($this->view->render());
        return $this->htmlResponse($moduleTemplate->renderContent());
        }

        public function initializeCreateAction()
        {
        if ($this->arguments['newEmploy']) {
        $propertyMappingConfiguration = $this->arguments['newEmploy']->getPropertyMappingConfiguration();
        $propertyMappingConfiguration->allowAllProperties();
        $propertyMappingConfiguration->forProperty('birthDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->forProperty('joiningDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->skipProperties('image');
        }
        }
        /**
        * action create
        * @param Employ $newEmploy
        * @return void
        */
        public function createAction(Employ $newEmploy): ResponseInterface
        {
        $uploaded = $this->request->getUploadedFiles();
        if (sizeOf($uploaded) > 0) {
        $__imag = $uploaded['newEmploy']['image'];
        $resourceFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
        $storage = $resourceFactory->getDefaultStorage();
        $newFile = $storage->addFile(
        $__imag->getTemporaryFileName(),
        $storage->getRootLevelFolder(),
        $__imag->getClientFilename()
        );
        $fileReference = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\FileReference::class);
        $fileReference->setOriginalResource($newFile);
        $newEmploy->addImage($fileReference);
        }
        $this->addFlashMessage('Employee Added successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
        $this->employRepository->add($newEmploy);
        $redirectPid = (int) $this->settings['listPid'];
        $uriBuilder = $this->uriBuilder;
        $uri = $uriBuilder
        ->setTargetPageUid($redirectPid)
        ->build();
        return $this->redirectToUri($uri);
        }


        public function deleteAction(): ResponseInterface
        {
        $arguments = $this->request->getArguments();
        $uid = $arguments['uid'];
        if ($uid) {
        $emplodata = $this->employRepository->findByUid($uid);
        // DebuggerUtility::var_dump($this->employRepository);
        // exit();
        if ($emplodata) {
        $delete = $this->employRepository->remove($emplodata);
        $this->addFlashMessage('Employee Deleted Sucessfully!!', 'Done!!', ContextualFeedbackSeverity::OK, true);
        } else {
        $this->addFlashMessage('Employee Not Found!!', 'Invalid', ContextualFeedbackSeverity::WARNING, true);
        }
        } else {
        $this->addFlashMessage('Employee Not Found!!', 'Invalid', ContextualFeedbackSeverity::WARNING, true);
        }
        return $this->redirect('belist');
        }



        public function editAction(): ResponseInterface
        {
        $arguments = $this->request->getArguments();
        $uid = $arguments['uid'];
        if ($uid) {
        $emplodata = $this->employRepository->findByUid($uid);
        if ($emplodata) {
        $this->view->assign('employ', $emplodata);
        $moduleTemplate = $this->moduleTemplateFactory->create($this->request)->setTitle("Edit Employee");
        $moduleTemplate->setContent($this->view->render());
        return $this->htmlResponse($moduleTemplate->renderContent());
        } else {
        $this->addFlashMessage('Employee Not Found!!', 'Invalid', ContextualFeedbackSeverity::WARNING, true);
        return $this->redirect('belist');
        }
        } else {
        $this->addFlashMessage('Employee Not Found!!', 'Invalid', ContextualFeedbackSeverity::WARNING, true);
        return $this->redirect('belist');
        }
        }


        public function initializeUpdateAction()
        {
        if ($this->arguments['employ']) {
        $propertyMappingConfiguration = $this->arguments['employ']->getPropertyMappingConfiguration();
        $propertyMappingConfiguration->allowAllProperties();
        $propertyMappingConfiguration->forProperty('birthDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->forProperty('joiningDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->skipProperties('image');
        }
        }

        /**
        * action create
        * @param Employ $employ
        * @return void
        */
        public function updateAction(Employ $employ): ResponseInterface
        {
        $uploaded = $this->request->getUploadedFiles();
        if (sizeOf($uploaded) > 0) {
        $oldImage = $employ->getImage();
        if($oldImage){
        foreach($oldImage as $im){
        $employ->removeImage($im);
        }
        }
        $__imag = $uploaded['employ']['image'];
        $resourceFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
        $storage = $resourceFactory->getDefaultStorage();
        $newFile = $storage->addFile(
        $__imag->getTemporaryFileName(),
        $storage->getRootLevelFolder(),
        $__imag->getClientFilename()
        );
        $fileReference = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\FileReference::class);
        $fileReference->setOriginalResource($newFile);
        $employ->addImage($fileReference);
        }
        $this->employRepository->update($employ);
        $this->addFlashMessage('Employee Updated successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
        return $this->redirect('belist');
        }

        public function updatesettingAction(): ResponseInterface
        {
        $data = $this->request->getArgument('confData');
        $existSettings = $this->confRepository->findByUid(1);
        if($existSettings){
        $existSettings->setImagelist($data['imagelist']);
        $existSettings->setImagedetail($data['imagedetail']);
        $this->confRepository->update($existSettings);
        }else{
        $Conff = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\Conf::class);
        $Conff->setImagelist($data['imagelist']);
        $Conff->setImagedetail($data['imagedetail']);
        $this->confRepository->add($Conff);
        }
        $this->addFlashMessage('Settings Updated successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
        return $this->redirect('belist');
        }

        }

    JobController.php

    1. Below are the screenshots from the employee\Classes\Controller\JobController.php file of the updated data for the table tx_employee_domain_model_conf.
      1. Add the confRepository
      2. Add confData in indexAction()
      3. Add confData in detailAction()
      4. Below is the full code of the employee\Classes\Controller\JobController.php file after adding the things you can see in the above screenshots:
        <?php
        declare(strict_types=1);

        namespace Company\Employee\Controller;

        use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
        use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
        use Psr\Http\Message\ResponseInterface;
        use TYPO3\CMS\Core\Context\Context;
        use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
        use TYPO3\CMS\Core\Utility\GeneralUtility;
        use Company\Employee\Domain\Model\Employ;
        use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
        use DateTime;

        class JobController extends ActionController
        {
        /**
        * jobRepository
        *
        * @var \Company\Employee\Domain\Repository\JobRepository
        */
        protected $jobRepository = null;
        /**
        * @param \Company\Employee\Domain\Repository\JobRepository $jobRepository
        */
        public function injectJobRepository(\Company\Employee\Domain\Repository\JobRepository $jobRepository)
        {
        $this->jobRepository = $jobRepository;
        }


        /**
        * employRepository
        *
        * @var \Company\Employee\Domain\Repository\EmployRepository
        */
        protected $employRepository = null;
        /**
        * @param \Company\Employee\Domain\Repository\EmployRepository $employRepository
        */
        public function injectEmployRepository(\Company\Employee\Domain\Repository\EmployRepository $employRepository)
        {
        $this->employRepository = $employRepository;
        }


        /**
        * confRepository
        *
        * @var \Company\Employee\Domain\Repository\ConfRepository
        */
        protected $confRepository = null;
        /**
        * @param \Company\Employee\Domain\Repository\ConfRepository $confRepository
        */
        public function injectConfRepository(\Company\Employee\Domain\Repository\ConfRepository $confRepository)
        {
        $this->confRepository = $confRepository;
        }

        public function __construct(
        protected readonly ModuleTemplateFactory $moduleTemplateFactory,
        Context $context,
        ) {
        $this->context = $context;
        }

        /**
        * action list
        *
        * @return string|object|null|void
        */
        public function indexAction(): ResponseInterface
        {
        $settings = $this->settings;
        $this->view->assign('settings', $settings);
        $jobs = $this->jobRepository->findAll();
        $this->view->assign('jobs', $jobs);
        $confData = $this->confRepository->findByUid(1);
        $this->view->assign('confData', $confData);
        return $this->htmlResponse();
        }

        /**
        * action detail
        *
        * @return string|object|null|void
        */
        public function detailAction(): ResponseInterface
        {
        $uid = GeneralUtility::_GP('uid');
        $confData = $this->confRepository->findByUid(1);
        $this->view->assign('confData', $confData);
        if ($uid) {
        $details = $this->jobRepository->findByUid($uid);
        $this->view->assign('details', $details);
        } else {
        $this->view->assign('details', null);
        }
        return $this->htmlResponse();
        }


        /**
        * Set TypeConverter option for image upload
        */
        public function initializeCreateAction()
        {
        if ($this->arguments['newEmploy']) {
        DebuggerUtility::var_dump($this->request->getArguments('newEmploy'));
        $propertyMappingConfiguration = $this->arguments['newEmploy']->getPropertyMappingConfiguration();
        $propertyMappingConfiguration->allowAllProperties();
        $propertyMappingConfiguration->forProperty('birthDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->forProperty('joiningDate')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
        $propertyMappingConfiguration->skipProperties('image');
        $propertyMappingConfiguration->skipProperties('education');
        }
        }
        /**
        * action create
        * @param Employ $newEmploy
        * @return void
        */
        public function createAction(Employ $newEmploy): ResponseInterface
        {
        $requestData = $this->request->getArguments('newEmploy');
        $uploaded = $this->request->getUploadedFiles();
        if (sizeOf($uploaded) > 0) {
        $__imag = $uploaded['newEmploy']['image'];
        $resourceFactory = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceFactory::class);
        $storage = $resourceFactory->getDefaultStorage();
        $newFile = $storage->addFile(
        $__imag->getTemporaryFileName(),
        $storage->getRootLevelFolder(),
        $__imag->getClientFilename()
        );
        $fileReference = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\FileReference::class);
        $fileReference->setOriginalResource($newFile);
        $newEmploy->addImage($fileReference);
        }
        if ($requestData['newEmploy']['education']) {
        $edu = $requestData['newEmploy']['education'];
        $education = GeneralUtility::makeInstance(\Company\Employee\Domain\Model\Education::class);
        $education->setCgpa((float) $edu['cgpa']);
        $education->setRollnumber($edu['rollnumber']);
        $education->setStartDate(new DateTime($edu['startDate']));
        $education->setEndDate(new DateTime($edu['endDate']));
        $education->setTitle($edu['title']);
        $newEmploy->addEducations($education);
        }
        $this->employRepository->add($newEmploy);
        $this->addFlashMessage('Employee Added successfully!!', 'Success', ContextualFeedbackSeverity::OK, true);
        $redirectPid = (int) $this->settings['listPid'];
        $uriBuilder = $this->uriBuilder;
        $uri = $uriBuilder
        ->setTargetPageUid($redirectPid)
        ->build();
        return $this->redirectToUri($uri);
        }

        }

    employee\Resources\Private\Templates\Employ\Index.html

    1. Below is the full code of the template file after updates related to the settings of back-end module, the file path is: employee\Resources\Private\Templates\Employ\Index.html
      <div class="employee_list_section">
      <div class="container">
      <div class="employee_list">
      <f:for each="{employs}" as="emp">
      <div class="employee row">
      <f:if condition="{confData.imagelist}">
      <div class="emp_img">
      <f:if condition="{emp.image}">
      <f:for each="{emp.image}" as="img">
      <f:image image="{img}" alt="{emp.firstName}" width="200c" height="200c" />
      </f:for>
      </f:if>
      </div>
      </f:if>
      <div class="emp_info">
      <div class="">
      <p><strong>Name : </strong>{emp.firstName} {emp.lastName}</p>
      </div>
      <div class="">
      <p><strong>Gender : </strong>{emp.gender == 1 ? "Male" :"Female"}</p>
      </div>
      <div class="emp_bio">
      <p><strong></strong>{emp.bio}</p>
      </div>
      <div class="emp_read_more">
      <f:link.page pageUid="{settings.detailPid}" additionalParams="{uid: emp.uid}">Read More
      </f:link.page>
      </div>
      </div>
      </div>
      </f:for>
      </div>
      </div>
      </div>

      In the above code, there is a condition of confData that binds the settings of the image with the template file, and the condition is <f:if condition="{confData.imagelist}">, you will see the same condition in the below template files.

    employee\Resources\Private\Templates\Employ\Detail.html

    1. Below is the full code of the template file: employee\Resources\Private\Templates\Employ\Detail.html
      <div class="section">
          <div class="container">
              <div class="row">
                  <f:if condition="{details}">
                      <f:then>
                          <h1 class="text-center">{details.firstName} {details.lastName}</h1>
                          <f:if condition="{confData.imagedetail}">
                              <f:if condition="{details.image}">
                                  <f:for each="{details.image}" as="img">
                                      <div class="image-wrapper text-center">
                                          <f:image image="{img}" alt="{details.firstName}" />
                                      </div>
                                  </f:for>
                              </f:if>
                          </f:if>
                          <div class="">
                              <div class="">
                                  <h3>Basic Details</h3>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>First name </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.firstName}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Last name </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.lastName}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Gender </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.gender == 1 ? "Male":"Female"}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Birth Date </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.date>{details.birthDate}</f:format.date>
                                          </p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Joining Date </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.date>{details.joiningDate}</f:format.date>
                                          </p>
                                      </div>
                                  </div>
      
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Salary </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.salary}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Country </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.country}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Languages </strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.languages}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Bio</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>{details.bio}</p>
                                      </div>
                                  </div>
                                  <div class="row">
                                      <div class="col-3">
                                          <strong>Experiance</strong>
                                      </div>
                                      <div class="col-9">
                                          <p>
                                              <f:format.html>{details.experiance}</f:format.html>
                                          </p>
                                      </div>
                                  </div>
                              </div>
                              <div class="">
                                  <h3>Educations</h3>
                                  <f:if condition="{details.educations}">
                                      <div class="row">
                                          <div class="col-2"><strong>Title</strong></div>
                                          <div class="col-2"><strong>Roll Number</strong></div>
                                          <div class="col-2"><strong>CGPA</strong></div>
                                          <div class="col-2"><strong>University</strong></div>
                                          <div class="col-2"><strong>Start Date</strong></div>
                                          <div class="col-2"><strong>End Date</strong></div>
                                      </div>
                                      <f:for each="{details.educations}" as="edu">
                                          <div class="row">
                                              <div class="col-2">
                                                  <p>{edu.title}</p>
                                              </div>
                                              <div class="col-2">
                                                  <p>{edu.rollNumber}</p>
                                              </div>
                                              <div class="col-2">
                                                  <p>{edu.cgpa}</p>
                                              </div>
                                              <div class="col-2">
                                                  <p>{edu.university}</p>
                                              </div>
                                              <div class="col-2">
                                                  <p>
                                                      <f:format.date>{edu.startDate}</f:format.date>
                                                  </p>
                                              </div>
                                              <div class="col-2">
                                                  <p>
                                                      <f:format.date>{edu.endDate}</f:format.date>
                                                  </p>
                                              </div>
                                          </div>
                                      </f:for>
                                  </f:if>
                              </div>
                          </div>
                      </f:then>
                      <f:else>
                          <div class="alert alert-error">
                              Employee not found!!
                          </div>
                      </f:else>
                  </f:if>
              </div>
          </div>
      </div>

    employee\Resources\Private\Templates\Job\Index.html

    1. Below is the full code of the template file:employee\Resources\Private\Templates\Job\Index.html
      <div class="section">
      <div class="container">
      <f:flashMessages />
      <div class="row my-5 ">
      <f:for each="{jobs}" as="job">
      <div class="flex col-4">
      <div class="card">
      <f:if condition="{confData.imagelist}">
      <div class="image-wrapper ">
      <f:if condition="{job.bannerImage}">
      <f:for each="{job.bannerImage}" as="img">
      <f:image class="card-img-top" image="{img}" alt="{job.firstName}" width="400c"
      height="200c" />
      </f:for>
      </f:if>
      </div>
      </f:if>
      <div class="job-details p-3 ">
      <div class="title">
      <h2>
      <f:link.page pageUid="{settings.detailPid}" additionalParams="{uid: job.uid}">
      {job.jobTitle}
      </f:link.page>
      </h2>
      </div>
      <div class="details">
      <div class=""><strong>Vacancy : </strong>{job.vacancy}</div>
      <div class=""><strong>Location : </strong>{job.jobLocation }</div>
      <div class=""><strong>job type" /> : </strong>{job.jobType }</div>
      <div class=""><strong>Experience of Years : </strong>{job.yearsOfExperience }</div>
      <div class=""><strong>Working hours : </strong>{job.workingHrs }</div>
      <div class=""><strong>Working days : </strong>{job.workingDays }</div>
      </div>
      <div class="action">
      <div class="mt-3">
      <f:link.page pageUid="{settings.detailPid}" additionalParams="{uid: job.uid}"
      class="btn btn-primary">
      Apply For Job
      </f:link.page>
      </div>
      </div>
      </div>
      </div>
      </div>
      </f:for>
      </div>
      </div>
      </div>

    employee\Resources\Private\Templates\Job\Detail.html

    1. Below is the full code of the template file: employee\Resources\Private\Templates\Job\Detail.html
      <div class="section">
      <div class="container">
      <div class="row">
      <f:if condition="{details}">
      <f:then>
      <div class="job-data">
      <h1 class="text-center">{details.jobTitle}</h1>
      <f:if condition="{confData.imagedetail}">
      <f:if condition="{details.bannerImage}">
      <f:for each="{details.bannerImage}" as="img">
      <div class="image-wrapper text-center">
      <f:image image="{img}" alt="{details.firstName}" />
      </div>
      </f:for>
      </f:if>
      </f:if>
      <div class="">
      <h3>Details</h3>
      <div class="row">
      <div class="col-3">
      <strong>Job Title </strong>
      </div>
      <div class="col-9">
      <p>{details.jobTitle }</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>Vacancy</strong>
      </div>
      <div class="col-9">
      <p>{details.vacancy}</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>Job Location</strong>
      </div>
      <div class="col-9">
      <p>{details.jobLocation }</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>Salary </strong>
      </div>
      <div class="col-9">
      <p>{details.salary}</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>Country </strong>
      </div>
      <div class="col-9">
      <p>{details.country}</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>jobType</strong>
      </div>
      <div class="col-9">
      <p>{details.jobType }</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>yearsOfExperience</strong>
      </div>
      <div class="col-9">
      <p>{details.yearsOfExperience }</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>workingHrs</strong>
      </div>
      <div class="col-9">
      <p>{details.workingHrs}</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>workingDays</strong>
      </div>
      <div class="col-9">
      <p>{details.workingDays}</p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>jobDescription</strong>
      </div>
      <div class="col-9">
      <p>
      <f:format.html>{details.jobDescription}</f:format.html>
      </p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>jobRequirements</strong>
      </div>
      <div class="col-9">
      <p>
      <f:format.html>{details.jobRequirements}</f:format.html>
      </p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>educationQualification </strong>
      </div>
      <div class="col-9">
      <p>
      <f:format.html>{details.educationQualification }</f:format.html>
      </p>
      </div>
      </div>
      <div class="row">
      <div class="col-3">
      <strong>perksBenefits </strong>
      </div>
      <div class="col-9">
      <p>
      <f:format.html>{details.perksBenefits }</f:format.html>
      </p>
      </div>
      </div>
      </div>
      </div>
      <div class="form">
      <div class="form-wrapper">
      <f:form action="create" enctype="multipart/form-data" name="newEmploy" object="{newEmploy}">
      <h2 class="text-center">Apply For Job </h2>
      <f:render partial="Employe/FormFields" arguments="{_all}" />
      <div class="d-flex justify-content-end mt-3">
      <f:form.submit class="btn btn-primary" value="Apply" />
      </div>
      </f:form>
      </div>
      </div>

      </f:then>
      <f:else>
      <div class="alert alert-error">
      Job not found!!
      </div>
      </f:else>
      </f:if>
      </div>
      </div>
      </div>

      After all the above updates in controllers and templates, let’s check if the settings are working or not.

    Test the extension settings

    1. Go to the back-end module and select the first option “Show image in list page”
    2. After selecting this option you will see the list of employees and the list of jobs containing images as you can see in the screenshots below.
      Employee List:

      Job list:
    3. Now uncheck the “Show Image in List” checkbox and click on the “Update Settings” button and you will see the images are not showing on any of the list pages.
    4. After updating the settings you will see the images are not showing now on the list of employees and the list of jobs.
      Employee list:

      Job list:
    5. Let’s check same for the “Show Image in Detail Page” option. Check the option “Show Image in Detail Page” and click the button “Update Settings”.
    6. Now check the employee detail page and job detail page and you will see the pages containing the images, as you can see in the screenshots below
      Employee detail page:

      Job detail page:
    7. Now let’s uncheck the checkbox “Show Image in Detail Page”, and then check the employee detail page and job detail page.
    8. After updating the settings, let’s check the detail page for employee and job, and you will see the images disappear on these pages as we have unchecked the checkbox “Show Image in Detail Page”. Below are the screenshots of the detail pages of employee and job.
      Employee detail page:

      Job detail page:
    9. So after testing, the conclusion is that the settings are working properly for both the list and the detail page.

    How to add multiple languages in TYPO3

    TYPO3 provides an option to add multiple languages. We can add the languages separately for the TYPO3 back-end and the front-end.
    To add the multi-language in the back-end, we can add languages to custom extensions, back-end modules, font-end plugins, etc.
    And to add the multi-language in the front-end, we can add the language for the front end and then add the language menu for front-end, it also requires adding the pages for multi-language, and then translating the extension records and the front-end plugins.

    Let’s start with the back-end multi-language.

    Add multi-language in the back-end

    1. When we install the TYPO3 setup, it comes with the English language only, if you want to make a multi-language site, then you will have to add the languages in TYPO3 back-end.
    2. Let’s check the steps to add multi-language to our setup with the extension, back-end module, and front-end plugin.

    Add back-end language

    To add the new languages in TYPO3 back-end, please follow the steps given below:

    1. Go to Admin tools > Maintenance > Manage Language Packs and click the button “Manage languages” as seen in the screenshot below.
    2. When you click the button “Manage languages”, you will see a popup with the list of active languages if you have any active language, otherwise, the list will be empty. Check the screenshot below.
    3. To add any new language, click the button “Add language”, and you will see the list of languages with add, remove, and download options as you can see in the screenshot below.
    4. To add any language, just click the plus (‘+’) icon beside whatever language you want to add. For example, here we are going to add the “French” language, so we need to click the + icon beside the French language as you can see in the screenshot below.
    5. Now, if you click the + icon for the French language, you will see the message about the language that has been already activated and also you will see the French language in your list of activated languages.
    6. After adding the language, now download the language by clicking on the download button of that language from the list of the activated languages.
    7. After downloading the language, you will see the message and also the date in the “Last update” column.

    Set Backend Language

    After adding the language, let’s set the language.

    1. To change the language of the admin panel, go to the profile and click on the user settings button as seen in the screenshot below.
    2. In user settings, there is an option to change the language in the “Personal data” tab, select the French language there and save the settings by clicking on the save button.
    3. After saving the user settings, you will see the back-end with the selected language. Below is the screenshot of the back-end after adding the French language.

    Add multi-language in the custom extension

    Follow the steps below to add the multi-language in the custom extension

    1. In the created extension, the CRUD was implemented and to add the new record for the employee extension we used the “List” module.
    2. Go to the List but here the language is French for now so go to the “Liste” > select the folder “Employees” or select whatever folder you have stored your records in > then click the button “Créer un nouvel enregistrement” to create a new record.
    3. After clicking that button, you will see the options to add the records.
    4. As you can see in the above screenshot, the options to add the records are displayed in the English language while the currently selected language is French. So to show these options in French, create the language files in employee/Resources/Private/Language folder.
    5. Create different files for different languages with the prefix of language code.
      1. E.g. If the main language file of the extension is “locallang_employee.xlf” and now we want to add the language file for the French language, the file name should be fr.locallang_employee.xlf.
      2. If the main language file’s name is extensionlangaugefile.xlf, then the French language file name should be fr.extensionlangaugefile.xlf. And if you want to add any other language with French, let’s say you want to add the German language too, then the language file for the German would be de.extensionlangaugefile.xlf
    6. Here in this example, the main language file is “locallang_employee.xlf” and we are creating a new language file for the French language so that the new file name will be  “fr.locallang_employee.xlf
      1. The file path is employee\Resources\Private\Language\fr.locallang_employee.xlf
      2. Below is the code of the file  fr.locallang_employee.xlf
        <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
        <xliff version="1.0" approved="yes" >
        <file source-language="en" target-language="fr" datatype="plaintext" date="2023-02-25T07:50:45Z" product-name="employee" original="EXT:employee/Resources/Private/Language/locallang_employee.xlf" >
        <body>
        </body>
        </file>
        </xliff>
      3. The above code shows the basic language file. Below is the explanation of the file
        1. source-language=”en”, it does mean that we are converting the English language as it is the original language here.
        2. target-language=”fr”, it does mean that we are converting to the French language.
        3. product-name=”employee”, it contains the key of the extension.
        4. The “original” will contain the base language path, and the base language or original language is English here, so set the path of the file “locallang_exployee.xlf”
        5. The body part of the file will contain the changes for the language file based on the key.
          1. Here we can set the value for the French language by adding the original content in <source></source> and the translated content in <target></target> (here you will have to translate the content manually for the extension ) as you can see in the code below
            <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
            <xliff version="1.0" approved="yes" >
            <file source-language="en" target-language="fr" datatype="plaintext" date="2023-02-25T07:50:45Z" product-name="employee" original="EXT:employee/Resources/Private/Language/locallang_employee.xlf" >
            <body>
            <trans-unit id="employee" resname="employee" approved="yes">
            <source>Employee</source>
            <target>Employée</target>
            </trans-unit>
            </body>
            </file>
            </xliff>

            After any changes in language files, please toggle the extension (Deactivate once and then activate again) or go to Maintainance > Flush TYPO3 and PHP cache and click on the button “Flush cache”
    7. We have 4 types of records and currently, they are not rendering language-wise as you can see in the screenshot below.
    8. To set these titles for multi-language, set the labels for them in the language file, and before that, the TCA file must be updated.
    9. Let’s try to understand this for the first title “Employee Configuration”.
      1. As we know the first title “Employee Configuration” is for the extension configuration, open the TCA file for the configuration – ext\employee\Configuration\TCA\tx_employee_domain_model_conf.php
      2. See the below screenshot, we have the static title “‘Employee Configuration'” for now, so it will be the same for all the languages if we don’t make it available language-wise.
      3. Remove that static title and put this line instead –'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:conf'
      4. Below is the screenshot after adding the line for the translation.
      5. As you can see in the above screenshot, we called the main language file “locallang_employee.xlf” which is the original language file with the path of the file, and at last, we have added the “:conf”, here the “conf” is the key for that title. Now let’s check the back-end after updating the TCA file, below is the screenshot.
      6. As you can see in the above screenshot, the title is not rendering, this is an issue that is occurring because we have added the key “conf” in TCA file’s title but we didn’t define it in the “locallang_employee.xlf” file. Let’s define the key conf. Add the code below in the locallang_employee.xlf file.
        <trans-unit id="conf" resname="conf">
        <source>Employee Extension Configuration</source>
        </trans-unit>
      7. By adding the above code, we have added the title “Employee Extension Configuration” for the key conf, now check the back-end and you will see as seen in the screenshot below:
      8. But still, the title is rendered in the English language while the rendered language is French for now in the back-end, so the title should be rendered in the French language because we didn’t add the title into the fr.locallang_employee.xlf, replace the code of the file fr.locallang_employee.xlf file with the given code below:
        <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
        <xliff version="1.0" approved="yes" >
        <file source-language="en" target-language="fr" datatype="plaintext" date="2023-02-25T07:50:45Z" product-name="employee" original="EXT:employee/Resources/Private/Language/locallang_employee.xlf" >
        <body>
        <trans-unit id="employee" resname="employee" approved="yes">
        <source>Employee</source>
        <target>Employée</target>
        </trans-unit>
        <trans-unit id="conf" resname="conf" approved="yes">
        <source>Employee Extension Configuration</source>
        <target>Configuration des extensions d'employé</target>
        </trans-unit>
        </body>
        </file>
        </xliff>
      9. Now check the back-end and you will see the title extension configuration in the French language, as seen in the screenshot below:
      10. Do the same for all the other 4 record titles and they all will be translated as seen in the screenshot below
    10. After completing the 4 record titles, let’s move to the detail page or the page that is open after clicking on any of them, let’s click on “Employer” which means “Employee” in English and you will see the details/field names rendered in English as seen in the screenshot below
      1. To convert the field titles into French, it is required to update the TCA file and the language file, the same as we have done for the record titles.
      2. Let’s understand this by converting the field name “First Name”.
      3. Open the TCA file employee\Configuration\TCA\tx_employee_domain_model_employ.php find the field name ‘first_name’, and replace the static “label” and “description” of the ”first_name’ field with the original language file path and key, you just need to replace the code for the “first_name” field with the code given below:
        'first_name' => [
        'l10n_mode' => 'prefixLangTitle',
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.first_name',
        'description' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.first_name.description',
        'config' => [
        'type' => 'input',
        'behaviour' => [
        'allowLanguageSynchronization' => true,
        ],
        ]
        ],
      4. Please note in the above code that the key for the label is “employ.first_name” and the key for the description field is “employ.first_name.description“.
      5. Now open the language file employee\Resources\Private\Language\locallang_employee.xlf and add the given code below
        <trans-unit id="employ.first_name" resname="employ.first_name">
            <source>First Name</source>
        </trans-unit>
        <trans-unit id="employ.first_name" resname="employ.first_name.description">
            <source>Enter First Name</source>
        </trans-unit>
      6. Then open the file employee\Resources\Private\Language\fr.locallang_employee.xlf and add the given code below
        <trans-unit id="employ.first_name" resname="employ.first_name" approved="yes">
        <source>First Name</source>
        <target>Prénom</target>
        </trans-unit>
        <trans-unit id="employ.first_name.description" resname="employ.first_name.description" approved="yes">
        <source>Enter First Name</source>
        <target>Entrez votre prénom</target>
        </trans-unit>
      7. After the above changes for the field “first_name”, you can see the updated/converted field name as seen in the screenshot below:
    11. Do the same for the remaining fields. Below is the final code for the Employee TCA file and language files after converting all the things mentioned above.
      1. open the file employee\Configuration\TCA\tx_employee_domain_model_employ.php and replace the code with the code below to convert all the fields

        <?php
        return [
        'ctrl' => [
        'title' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ',
        'label' => 'first_name',
        'tstamp' => 'tstamp',
        'crdate' => 'crdate',
        'versioningWS' => true,
        'label_alt_force' => true,
        'origUid' => 't3_origuid',
        'languageField' => 'sys_language_uid',
        'transOrigPointerField' => 'l10n_parent',
        'transOrigDiffSourceField' => 'l10n_diffsource',
        'delete' => 'deleted',
        'enablecolumns' => [
        'disabled' => 'hidden',
        'starttime' => 'starttime',
        'endtime' => 'endtime',
        ],
        'searchFields' => 'first_name',
        'last_name',
        'bio',
        'iconfile' => 'EXT:employee/Resources/Public/Icons/Extension.png',
        'security' => [
        'ignorePageTypeRestriction' => true,
        ],
        ],
        'types' => [
        '1' => [
        'showitem' => 'first_name,last_name,gender,birth_date,joining_date,image,bio,experiance,salary,languages,country,job,educations, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime'
        ],
        ],
        'columns' => [
        'sys_language_uid' => [
        'exclude' => true,
        'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
        'config' => [
        'type' => 'language',
        ],
        ],
        'l10n_parent' => [
        'displayCond' => 'FIELD:sys_language_uid:>:0',
        'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
        'config' => [
        'type' => 'select',
        'renderType' => 'selectSingle',
        'default' => 0,
        'items' => [
        ['label' => '', 'value' => 0]
        ],
        'foreign_table' => 'tx_employee_domain_model_employ',
        'foreign_table_where' => 'AND {#tx_employee_domain_model_employ}.{#pid}=###CURRENT_PID### AND {#tx_employee_domain_model_employ}.{#sys_language_uid} IN (-1,0)',
        ],
        ],
        'l10n_diffsource' => [
        'config' => [
        'type' => 'passthrough',
        ],
        ],
        'hidden' => [
        'config' => [
        'type' => 'check',
        'items' => [
        ['label' => 'Disable'],
        ],
        ]
        ],
        'starttime' => [
        'exclude' => true,
        'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
        'config' => [
        'type' => 'input',
        'renderType' => 'datetime',
        'eval' => 'datetime,int',
        'default' => 0,
        'behaviour' => [
        'allowLanguageSynchronization' => true
        ]
        ],
        ],
        'endtime' => [
        'exclude' => true,
        'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
        'config' => [
        'type' => 'input',
        'renderType' => 'datetime',
        'eval' => 'datetime,int',
        'default' => 0,
        'range' => [
        'upper' => mktime(0, 0, 0, 1, 1, 2038)
        ],
        'behaviour' => [
        'allowLanguageSynchronization' => true
        ]
        ],
        ],

        'first_name' => [
        'l10n_mode' => 'prefixLangTitle',
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.first_name',
        'description' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.first_name.description',
        'config' => [
        'type' => 'input',
        'behaviour' => [
        'allowLanguageSynchronization' => true,
        ],
        ]
        ],
        'last_name' => [
        'l10n_mode' => 'prefixLangTitle',
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.last_name',
        'description' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.last_name.description',
        'config' => [
        'type' => 'input',
        'behaviour' => [
        'allowLanguageSynchronization' => true,
        ],
        ]
        ],
        'gender' => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.gender',
        'description' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.gender.description',
        'config' => [
        'type' => 'radio',
        'items' => [
        [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.male',
        'value' => 1,
        ],
        [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.female',
        'value' => 2,
        ]
        ],
        ],
        ],
        'birth_date' => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.birth_date',
        'config' => [
        'type' => 'datetime',
        'format' => 'date',
        'eval' => 'int',
        'default' => 0,
        ]
        ],
        'joining_date' => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.joining_date',
        'config' => [
        'type' => 'datetime',
        'format' => 'datetime',
        'eval' => 'int',
        'default' => 0,
        ]
        ],
        'image' => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.image',
        'config' => [
        'type' => 'file',
        'allowed' => 'common-image-types'
        ],
        ],
        "bio" => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.bio',
        'config' => [
        'type' => 'text',
        'cols' => 20,
        'rows' => 2,
        ],
        ],
        "experiance" => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.experiance',
        'config' => [
        'type' => 'text',
        'enableRichtext' => true,
        ],
        ],
        "salary" => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.salary',
        'config' => [
        'type' => 'number',
        'format' => 'decimal'
        ]
        ],
        "job" => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:job',
        'config' => [
        'type' => 'select',
        'renderType' => 'selectSingle',
        'foreign_table' => 'tx_employee_domain_model_job',
        ],
        ],
        "languages" => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.languages',
        'config' => [
        'type' => 'check',
        'items' => [
        [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.languages.english',
        'value' => 'en',
        ],
        [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.languages.german',
        'value' => 'de',
        ],
        [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.languages.french',
        'value' => 'fr',
        ],
        ],
        ],
        ],
        "country" => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.country',
        'config' => [
        'type' => 'select',
        'renderType' => 'selectSingle',
        'items' => [
        [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.country.us',
        'value' => 'US',
        ],
        [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.country.germany',
        'value' => 'Germany',
        ],
        [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.country.france',
        'value' => 'France',
        ],
        ],
        ],
        ],
        'educations' => [
        'label' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.educations',
        'description' => 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employ.educations.add',
        'config' => [
        'type' => 'inline',
        'foreign_table' => 'tx_employee_domain_model_education',
        'foreign_field' => 'employ',
        'appearance' => [
        'showSynchronizationLink' => true,
        'showAllLocalizationLink' => true,
        'showPossibleLocalizationRecords' => true,
        ],
        ],
        ],
        ],
        ];

      2. Now open the employee\Resources\Private\Language\locallang_employee.xlf file and replace the code with the given code below
        <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
        <xliff version="1.0">
        <file source-language="en" datatype="plaintext" date="2023-02-25T07:50:45Z" product-name="t3theme">
        <body>
        <trans-unit id="mlang_tabs_tab" resname="mlang_tabs_tab">
        <source>Employees</source>
        </trans-unit>
        <trans-unit id="conf" resname="conf">
        <source>Employee Extension Configuration</source>
        </trans-unit>
        <trans-unit id="education" resname="education">
        <source>Education</source>
        </trans-unit>
        <trans-unit id="employ" resname="employ">
        <source>Employ</source>
        </trans-unit>
        <trans-unit id="job" resname="job">
        <source>Job</source>
        </trans-unit>
        <trans-unit id="employ.first_name" resname="employ.first_name">
        <source>First Name</source>
        </trans-unit>
        <trans-unit id="employ.first_name" resname="employ.first_name.description">
        <source>Enter First Name</source>
        </trans-unit>
        <trans-unit id="employ.last_name" resname="employ.last_name">
        <source>Last Name</source>
        </trans-unit>
        <trans-unit id="employ.last_name" resname="employ.last_name.description">
        <source>Enter Last Name</source>
        </trans-unit>
        <trans-unit id="employ.gender" resname="employ.gender">
        <source>Gender</source>
        </trans-unit>
        <trans-unit id="employ.gender" resname="employ.gender.description">
        <source>Select Gender</source>
        </trans-unit>
        <trans-unit id="employ.male" resname="employ.male">
        <source>Male</source>
        </trans-unit>
        <trans-unit id="employ.female" resname="employ.female">
        <source>Female</source>
        </trans-unit>
        <trans-unit id="employ.birth_date" resname="employ.birth_date">
        <source>Birth Date</source>
        </trans-unit>
        <trans-unit id="employ.joining_date" resname="employ.joining_date">
        <source>Joining Date</source>
        </trans-unit>
        <trans-unit id="employ.image" resname="employ.image">
        <source>Image</source>
        </trans-unit>
        <trans-unit id="employ.bio" resname="employ.bio">
        <source>Bio</source>
        </trans-unit>
        <trans-unit id="employ.experiance" resname="employ.experiance">
        <source>Experiance</source>
        </trans-unit>
        <trans-unit id="employ.salary" resname="employ.salary">
        <source>Salary</source>
        </trans-unit>
        <trans-unit id="employ.languages" resname="employ.languages">
        <source>Languages</source>
        </trans-unit>
        <trans-unit id="employ.languages.english" resname="employ.languages.english">
        <source>English</source>
        </trans-unit>
        <trans-unit id="employ.languages.german" resname="employ.languages.german">
        <source>German</source>
        </trans-unit>
        <trans-unit id="employ.languages.french" resname="employ.languages.french">
        <source>French</source>
        </trans-unit>
        <trans-unit id="employ.country" resname="employ.country">
        <source>Country</source>
        </trans-unit>
        <trans-unit id="employ.country.us" resname="employ.country.us">
        <source>United State</source>
        </trans-unit>
        <trans-unit id="employ.country.germany" resname="employ.country.germany">
        <source>Germany</source>
        </trans-unit>
        <trans-unit id="employ.country.france" resname="employ.country.france">
        <source>France</source>
        </trans-unit>
        <trans-unit id="employ.educations" resname="employ.educations">
        <source>Educations</source>
        </trans-unit>
        <trans-unit id="employ.educations.add" resname="employ.educations.add">
        <source>Add Educations</source>
        </trans-unit>
        </body>
        </file>
        </xliff>

      3. Now open the employee\Resources\Private\Language\fr.locallang_employee.xlf file and replace the code with the given code below
        <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
        <xliff version="1.0" approved="yes" >
        <file source-language="en" target-language="fr" datatype="plaintext" date="2023-02-25T07:50:45Z" product-name="employee" original="EXT:employee/Resources/Private/Language/locallang_employee.xlf" >
        <body>
        <trans-unit id="employee" resname="employee" approved="yes">
        <source>Employee</source>
        <target>Employée</target>
        </trans-unit>
        <trans-unit id="conf" resname="conf" approved="yes">
        <source>Employee Extension Configuration</source>
        <target>Configuration des extensions d'employé</target>
        </trans-unit>
        <trans-unit id="education" resname="education" approved="yes">
        <source>Education</source>
        <target>éducation</target>
        </trans-unit>
        <trans-unit id="employ" resname="employ" approved="yes">
        <source>Employ</source>
        <target>Employer</target>
        </trans-unit>
        <trans-unit id="job" resname="job" approved="yes">
        <source>Job</source>
        <target>Emploi</target>
        </trans-unit>
        <trans-unit id="employ.first_name" resname="employ.first_name" approved="yes">
        <source>First Name</source>
        <target>Prénom</target>
        </trans-unit>
        <trans-unit id="employ.first_name.description" resname="employ.first_name.description" approved="yes">
        <source>Enter First Name</source>
        <target>Entrez votre prénom</target>
        </trans-unit>
        <trans-unit id="employ.last_name" resname="employ.last_name" approved="yes">
        <source>Last Name</source>
        <target>Nom de famille</target>
        </trans-unit>
        <trans-unit id="employ.last_name.description" resname="employ.last_name.description" approved="yes">
        <source>Enter Last Name</source>
        <target>Entrer le nom de famille</target>
        </trans-unit>
        <trans-unit id="employ.gender" resname="employ.gender" approved="yes">
        <source>Gender</source>
        <target>Genre</target>
        </trans-unit>
        <trans-unit id="employ.gender.description" resname="employ.gender.description" approved="yes">
        <source>Select Gender</source>
        <target>sélectionnez le sexe</target>
        </trans-unit>
        <trans-unit id="employ.male" resname="employ.male" approved="yes">
        <source>Male</source>
        <target>Hommes</target>
        </trans-unit>
        <trans-unit id="employ.female" resname="employ.female" approved="yes">
        <source>Female</source>
        <target>femmes</target>
        </trans-unit>
        <trans-unit id="employ.birth_date" resname="employ.birth_date" approved="yes">
        <source>Birth Date</source>
        <target>Date de naissance</target>
        </trans-unit>
        <trans-unit id="employ.joining_date" resname="employ.joining_date" approved="yes">
        <source>Joining Date</source>
        <target>Date d'inscription</target>
        </trans-unit>
        <trans-unit id="employ.image" resname="employ.image" approved="yes">
        <source>Image</source>
        <target>Image</target>
        </trans-unit>
        <trans-unit id="employ.bio" resname="employ.bio" approved="yes">
        <source>Bio</source>
        <target>biographie</target>
        </trans-unit>
        <trans-unit id="employ.experiance" resname="employ.experiance" approved="yes">
        <source>Experiance</source>
        <target>Expérience</target>
        </trans-unit>
        <trans-unit id="employ.salary" resname="employ.salary" approved="yes">
        <source>Salary</source>
        <target>Salaire</target>
        </trans-unit>
        <trans-unit id="employ.languages" resname="employ.languages" approved="yes">
        <source>Languages</source>
        <target>Langues</target>
        </trans-unit>
        <trans-unit id="employ.languages.english" resname="employ.languages.english" approved="yes">
        <source>English</source>
        <target>Anglais</target>
        </trans-unit>
        <trans-unit id="employ.languages.german" resname="employ.languages.german" approved="yes">
        <source>German</source>
        <target>Allemand</target>
        </trans-unit>
        <trans-unit id="employ.languages.french" resname="employ.languages.french" approved="yes">
        <source>French</source>
        <target>Français</target>
        </trans-unit>
        <trans-unit id="employ.country" resname="employ.country" approved="yes">
        <source>Country</source>
        <target>Pays</target>
        </trans-unit>
        <trans-unit id="employ.country.us" resname="employ.country.us" approved="yes">
        <source>United State</source>
        <target>Etats Unis</target>
        </trans-unit>
        <trans-unit id="employ.country.german" resname="employ.country.german" approved="yes">
        <source>German</source>
        <target>Allemande</target>
        </trans-unit>
        <trans-unit id="employ.country.france" resname="employ.country.france" approved="yes">
        <source>France</source>
        <target>France</target>
        </trans-unit>
        <trans-unit id="employ.educations" resname="employ.educations" approved="yes">
        <source>Educations</source>
        <target>éducation</target>
        </trans-unit>
        <trans-unit id="employ.educations.add" resname="employ.educations.add" approved="yes">
        <source>Add Educations</source>
        <target>Ajouter une formation</target>
        </trans-unit>
        </body>
        </file>
        </xliff>
    12. After updating TCA file and the language files, flush the TYPO3 and PHP Cache and check in the back-end and you will see that all the field names are converted as seen in the screenshot below.
    13. You can take reference to the above and do the same for the other 3 records.
    14. Now, let’s see how to implement multi-language to the back-end module.

    Add multi-language in the back-end module

    1. As you can see in the above screenshot, the titles of all the modules of TYPO3 (on the left sidebar) are translated into French but the title of the custom back-end module “Employees” is still not translated.
    2. As per the back-end module configuration, we just called the language file name without any key, because the labels for the back-end modules are rendering based on its config as defined in the reference link – https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Backend/BackendModules/ModuleConfiguration/Index.html#backend-modules-configuration
    3. In the main language file employee/Resources/Private/Language/locallang_employee.xlf we defined the key mlang_tabs_tab with the value “Employees” so the title will be rendered with this value only, for all the languages.
    4. If we want to convert the back-end module’s title for the French language, we need to add it to the French language file, let’s convert it.
    5. Open the file employee\Resources\Private\Language\fr.locallang_employee.xlf and add the code below
      <trans-unit id="mlang_tabs_tab" resname="mlang_tabs_tab" approved="yes">
      	<source>Employees</source>
      	<target>Employées</target>
      </trans-unit>
      
    6. After adding the above code flush the cache and check in the back-end and you will see the back-end module’s title is converted for the French language.
    7. Click on the back-end module and you will see the configurations/settings of the back-end module on the screen, you can see that the settings are rendered in English only while the selected language is French.
    8. Let’s convert the settings into French.
      1. The labels for settings are written statically in the fluid template file employee\Resources\Private\Backend\Templates\Employ\Belist.html as you can see in the screenshot below
      2. So they are always rendered static until we translate.
      3. Now, to make translations in a fluid template, we can use in TYPO3 the viewhelper of the fluid. Here is the reference link – https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/HowTo/Localization/Fluid.html
      4. By using the viewhelper, we just have to call the language file with an identifier and we will get the translated text from the language file according to the current language.
      5. Let’s use the translate viewhelper into the fluid template file to render the text based on the selected language.
        And please note here that we will not use this only for the settings, but will use this in the employee\Resources\Private\Backend\Templates\Employ\Belist.html file to translate each static text in the file.
      6. Below is the code of the employee\Resources\Private\Backend\Templates\Employ\Belist.html after translating the static text by using the viewhelper.
        <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
        xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
        xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" data-namespace-typo3-fluid="true">
        <f:layout name="Default" />

        <f:section name="Content">
        <div class="section">
        <div class="container">
        <f:flashMessages />

        <ul class="nav nav-tabs" id="myTab" role="tablist">
        <li class="nav-item" role="presentation">
        <button class="nav-link active" id="config-tab" data-bs-toggle="tab" data-bs-target="#config"
        type="button" role="tab" aria-controls="config" aria-selected="true">
        <f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:settings" />
        </button>
        </li>
        <li class="nav-item" role="presentation">
        <button class="nav-link" id="employ-tab" data-bs-toggle="tab" data-bs-target="#employ" type="button"
        role="tab" aria-controls="employ" aria-selected="false">
        <f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employee" />
        </button>
        </li>
        </ul>
        <div class="tab-content" id="myTabContent">
        <div class="tab-pane fade show active" id="config" role="tabpanel" aria-labelledby="config-tab">
        <div class="my-3">
        <h1 class="text-center"><f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:settings.header" /></h1>
        <div class="form-wrapper">
        <f:form action="updatesetting" enctype="multipart/form-data" name="confData" object="{confData}">
        <f:render partial="Conf/FormFields" arguments="{_all}" />
        <div class="d-flex justify-content-end">
        <f:form.submit class="btn btn-primary" value="{f:translate(key: 'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:settings.savebutton')}" />
        </div>
        </f:form>
        </div>
        </div>
        </div>
        <div class="tab-pane fade" id="employ" role="tabpanel" aria-labelledby="employ-tab">
        <div class="my-3">
        <div class="d-flex justify-content-end">
        <f:link.action action="new" class="btn btn-primary">Add New Employee</f:link.action>
        </div>
        <table class="table table-responsive">
        <thead>
        <tr>
        <th>Image</th>
        <th>FirstName</th>
        <th>LastName</th>
        <th>Gender</th>
        <th>Action</th>
        </tr>
        </thead>
        <tbody>
        <f:for each="{employs}" as="emp">
        <tr>
        <td>
        <f:if condition="{emp.image}">
        <f:for each="{emp.image}" as="img">
        <f:image image="{img}" alt="{emp.firstName}" width="100c"
        height="100c" />
        </f:for>
        </f:if>
        </td>
        <td>{emp.firstName}</td>
        <td>{emp.lastName}</td>
        <td>{emp.gender == 1 ? "Male" :"Female"}</td>
        <td>
        <f:link.action action="edit" class="btn btn-primary"
        arguments="{uid:emp.uid}">
        Edit
        </f:link.action>
        <f:link.action action="delete" class="btn btn-primary"
        arguments="{uid:emp.uid}">Delete
        </f:link.action>
        </td>
        </tr>
        </f:for>
        </tbody>
        </table>
        </div>
        </div>
        </div>
        </div>
        </div>
        </f:section>

        </html>
        Below is the screenshot to highlight the code of translation into the employee\Resources\Private\Backend\Templates\Employ\Belist.html
      7. Add the given code below in employee\Resources\Private\Language\locallang_employee.xlf file
        <trans-unit id="settings" resname="settings">
        <source>Settings</source>
        </trans-unit>
        <trans-unit id="employee" resname="employee">
        <source>Employee</source>
        </trans-unit>
        <trans-unit id="settings.header" resname="settings.header">
        <source>Settings Regarding Extension</source>
        </trans-unit>
        <trans-unit id="settings.savebutton" resname="settings.savebutton">
        <source>Update Settings</source>
        </trans-unit>
      8. Add the given code below in employee\Resources\Private\Language\fr.locallang_employee.xlf
        <trans-unit id="settings" resname="settings"  approved="yes">
        <source>Settings</source>
        <target>Paramètres</target>
        </trans-unit>
        <trans-unit id="employee" resname="employee" approved="yes">
        <source>Employee</source>
        <target>Employée</target>
        </trans-unit>
        <trans-unit id="settings.header" resname="settings.header" approved="yes">
        <source>Settings Regarding Extension</source>
        <target>Paramètres concernant l'extension</target>
        </trans-unit>
        <trans-unit id="settings.savebutton" resname="settings.savebutton" approved="yes">
        <source>Update Settings</source>
        <target>Mettre à jour les paramètres</target>
        </trans-unit>
      9. You can see the updates in the below screenshot, note here that everything else has been updated but the settings checkbox titles are still not updated.
        1. To update these settings we have to update the file employee\Resources\Private\Backend\Partials\Conf\FormFields.html as those fields are residing in this file.
        2. Below is the screenshot of the file with highlighted the areas we need to update.
        3. Replace the code in employee\Resources\Private\Backend\Partials\Conf\FormFields.html file with the code given below
          <f:form.validationResults>
          <f:if condition="{validationResults.flattenedErrors}">
          <div class="alert ">
          <f:for each="{validationResults.flattenedErrors}" as="errors" key="propertyPath">
          <div class="alert alert-danger"> {propertyPath}
          <f:for each="{errors}" as="error">
          <p>{error.code}: {error}</p>
          </f:for>
          </div>
          </f:for>
          </div>
          </f:if>
          </f:form.validationResults>
          <div class="row">
          <div class="col-12">
          <div class="form-group">
          <f:form.checkbox property="imagelist" value="1" checked="{confData.imagelist} == 1" id="imagelist" />
          <label class="control-label mb-10" for="imagelist">
          <f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:conf.imagelist" />
          </label>
          </div>
          </div>
          <div class="col-12">
          <div class="form-group">
          <f:form.checkbox property="imagedetail" value="1" checked="{confData.imagedetail} == 1" id="imagedetail" />
          <label class="control-label mb-10" for="imagedetail">
          <f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:conf.imagedetail" />

          </label>
          </div>
          </div>
          </div>
        4. Add the below code to the file employee\Resources\Private\Language\locallang_employee.xlf
          <trans-unit id="conf.imagelist" resname="conf.imagelist">
          <source>Show Image in List</source>
          </trans-unit>
          <trans-unit id="conf.imagedetail" resname="conf.imagedetail">
          <source>Show Image in Detail Page</source>
          </trans-unit>
        5. Add the below code to the file employee\Resources\Private\Language\fr.locallang_employee.xlf
          <trans-unit id="conf.imagelist" resname="conf.imagelist"  approved="yes">
              <source>Show Image in List</source>
              <target>Afficher l'image dans la liste</target>
          </trans-unit>
          <trans-unit id="conf.imagedetail" resname="conf.imagedetail"  approved="yes">
              <source>Show Image in Detail Page</source>
              <target>Afficher l'image dans la page de détail</target>
          </trans-unit>
          
        6. You can see in the screenshot below that the settings fields are rendering in French now.

    Add multi-language in the front-end plugin and flexform

    In the front-end plugin, there are some areas that need to be translated. Let’s check them one by one and translate all of them for the plugin JobList. Please note here that we are going to translate only for the JobList plugin.

    1. Wizard:
      1. When you go on any page and click on the button “+content”, you will see a wizard, go to plugins (Modules in French) and you will see all the front-end plugins are rendering in English only as seen in the screenshot below.
      2. Let’s translate the plugin title and description in wizard. To make updates in wizard, update the tsconfig file.
      3. The file path is employee\Configuration\page.tsconfig.
      4. Below is the screenshot of the employee\Configuration\page.tsconfig file with the highlighted area which needs to be updated.
      5. Update the code for employee_joblist in employee\Configuration\page.tsconfig file with the given code below
        employee_joblist {
          iconIdentifier = employee
          title = LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employee_joblist_title
          description = LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employee_joblist_description
          tt_content_defValues {
            CType = list
            list_type = employee_joblist
          }
        }
        
      6. Update the file locallang_employee.xlf by adding key and text for the title and description of employee_joblist. Add the given code below to the file employee\Resources\Private\Language\locallang_employee.xlf
        <trans-unit id="employee_joblist_title" resname="employee_joblist_title">
            <source>JobList</source>
        </trans-unit>
        <trans-unit id="employee_joblist_description" resname="employee_joblist_description">
            <source>List of Jobs</source>
        </trans-unit>
      7. Update the file fr.locallang_employee.xlf to add the text in French for the title and description of employee_joblist. Add the given code below to the file employee\Resources\Private\Language\fr.locallang_employee.xlf
        <trans-unit id="employee_joblist_title" resname="employee_joblist_title" approved="yes">
            <source>JobList</source>
            <target>Liste d'emplois</target>
        </trans-unit>
        <trans-unit id="employee_joblist_description" resname="employee_joblist_description" approved="yes">
            <source>List of Jobs</source>
            <target>Liste des emplois</target>
        </trans-unit>
        
        
      8. After the above updates, you can see in the screenshot below that the plugin title and description for the “Job List” plugin are updated language-wise in the wizard.
    2. Update the plugin title on the page in back-end after adding the plugin
      1. Below is the screenshot where the title needs to be updated.
      2. To update the title seen in the above screenshot, update the file tt_content.php where there is the fixed text for the title, below is the screenshot of tt_content.php file with the highlighted area that needs to be updated.
      3. Update the highlighted title “Job List” with the line “LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employee_joblist_title“, the full code for the Job List plugin is below
        \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
        'Employee',
        'Joblist',
        'LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:employee_joblist_title',
        'EXT:employee/Resources/Public/Icons/Extension.png',
        );
      4. Save the file, flush the cache and you will see that the title has been updated language-wise.
    3. Update the title of the field to select the detail page at the time of editing the plugin.
      1. After adding the plugin when you click on the edit icon to open it, go to the plugin tab to select the detail page for the job and you will see the title in English while the selected language is French as you can see in the screenshot below
      2. What you can see in the above screenshot is the part of a flexform. Let’s update the flexform for the job configs to translate the highlighted title.
      3. Open the file employee\Configuration\FlexForms\JobConfigs.xml, below is the screenshot with the highlighted area that needs to be updated in order to translate the title from the above screenshot.
      4. Replace the “Job Detail Page” with the line “LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:detail_page_id” to translate the title. Below is the full code of the file employee\Configuration\FlexForms\JobConfigs.xml file after translation, you can replace the code.

        <T3DataStructure>
        <sheets>
        <sDEF>
        <ROOT>
        <sheetTitle>
        General
        </sheetTitle>
        <type>array</type>
        <el>
        <settings.detailPid>
        <TCEforms>
        <label>LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:detail_page_id</label>
        <config>
        <type>group</type>
        <allowed>pages</allowed>
        <size>1</size>
        <maxitems>1</maxitems>
        <minitems>1</minitems>
        </config>
        </TCEforms>
        </settings.detailPid>
        </el>
        </ROOT>
        </sDEF>
        </sheets>
        </T3DataStructure>
      5. After updating the flexform, update the file employee\Resources\Private\Language\locallang_employee.xlf to add the key and text for the above title. Add the code given below to the file employee\Resources\Private\Language\locallang_employee.xlf
        <trans-unit id="detail_page_id" resname="detail_page_id">
        <source>Job DEtail page</source>
        </trans-unit>
      6. Add the code given below to the file employee\Resources\Private\Language\fr.locallang_employee.xlf to add the language-wise text for the title or label “Job Detail Page”.
        <trans-unit id="detail_page_id" resname="detail_page_id"  approved="yes">
        <source>Job Detail page</source>
        <target>Page de détails du travail</target>
        </trans-unit>
      7. Save the file and flush the cache and reload the back-end and you will see the updated title which is rendering now language-wise.

    With the above updates, the translation for the front-end plugin and flexform for the Job List plugin is now complete, and with that, the section “Add multi-language in the back-end” is also completed, now let’s go for the “Add multi-language in the front-end“.

    Add multi-language in the front-end

    To make the site multi-language it is not enough to add multi-language in the back-end only, you have to add multi-language for the front-end too so the user can see the content of your website in multi-language. After learning how to add multi-language in the back-end, let’s start to learn how to add multi-language in the front-end. To add multi-language in the front-end is a totally different concept from the back-end multi-language.

    The default language for the front-end in TYPO3 is English. First, update the language in the back-end from French to English so it can be easier to understand how to add multi-language for the front-end. You can check in the “Set Backend Language” section to learn how to select the English language.

    Add new language in the front-end

    1. To add the multi-language in front-end go to the Site Management > Sites and you will see the screen as seen in the screenshot below, then click on the edit button:
    2. After clicking on the edit button, you will see the below screen. There are 2 options you can use to add the new language and those options are i) Create new language and ii) Choose a preset….

    3. By clicking on the “Create new language” button to add a new language, you will get a form to set the configs for the new language as seen in the screenshot below:
    4. And if you click on the “Choose a preset..” button you will get options to select the language, and after selecting the language, you will get a prefilled form with the configs of that language. You can see in the screenshots below:

    5. After saving the language you will see the language in the list as seen in the screenshot below
    6. To select the language option in front-end, let’s create the language menu.

    Add the language menu

    1. To switch the language in front-end and get the front-end content with the selected language, it needs to create the language menu. Here is the reference link for the language menu – https://docs.typo3.org/m/typo3/guide-frontendlocalization/main/en-us/LanguageMenu/Index.html#language-selection-menu
    2. To add the language menu, write the typoscript. Update the existing setup.typoscript file of the extension “Employee”.
    3. The file path is employee\Configuration\TypoScript\setup.typoscript
    4. Add the given code below to the file setup.typoscript under “dataProcessing”
      40 = TYPO3\CMS\Frontend\DataProcessing\LanguageMenuProcessor
      40 {
      as = languagenavigation
      }
    5. Below is the screenshot of the employee\Configuration\TypoScript\setup.typoscript file after adding the above code under “dataProcessing”.
    6. After adding the code to the setup.typoscript, now need to call the “typoscript languagenavigation object” into the template file.
    7. Here please note that we want to add the language menu for all the pages, so we have to add the language menu to the extension’s page template file – employee\Resources\Private\Templates\Page\Default.html
    8. Add the below code or replace the existing code with the code given below to the file – employee\Resources\Private\Templates\Page\Default.html
      <f:layout name="Default" />
      <f:section name="Main">
        <f:if condition="{languagenavigation}">
          <ul id="language_menu" class="language-menu">
              <f:for each="{languagenavigation}" as="item">
                  <li class="{f:if(condition: item.active, then: 'active')} {f:if(condition: item.available, else: 'text-muted')}">
                      <f:if condition="{item.available}">
                          <f:then>
                              <a href="{item.link}" hreflang="{item.hreflang}" title="{item.title}">
                                  <span>{item.navigationTitle}</span>
                              </a>
                          </f:then>
                          <f:else>
                              <span>{item.navigationTitle}</span>
                          </f:else>
                      </f:if>
                  </li>
              </f:for>
          </ul>
      </f:if>
        <f:cObject typoscriptObjectPath="lib.dynamicContent" data="{colPos: 0}" />
      </f:section>
      
    9. After adding the above code Flush TYPO3 and PHP Cache and after reloading the job page you will see the language menu there as you can see in the screenshot below:
    10. As you can see in the above screenshot, the language menu is rendering but the German language is still not enabled, if you try to switch the language, you will not be able to translate the page into the German language.
    11. To enable the language now, it is required to add the multi-language pages. Let’s see how to add multi-language pages.

    Add multi-language pages

    Follow the given steps below:

    1. Go to the Page and select the page you want to translate, here we want to translate the Jobs page. Click on the page you want to translate, then go to the dropdown at the top-left and select “Language Comparision” as you can see in the screenshot below:
    2. After selecting “Language Comparision” you will see a dropdown as seen in the screenshot below, select the language you want to translate, from that dropdown.

    3. As you select “German”, you will be redirected to the below page:
    4. Save the page and you will see the option to translate the page as you can see below, if your German page is disabled then enable it and click on the translate button to translate the page into German.
    5. When you click on the “Translate” button, you will see 2 options i)Translate and ii) Copy
      1. Translate: Use this when your workflow demands a strict translation workflow.
      2. Copy: Use this when you want freedom in designing to your translated website.
    6. Here we are using the “Translate” option by clicking on it and then clicking on the “Next” button.
    7. Then select the content that needs to be translated and click on the “Next” button.
    8. And you will see the translated page now, but it will be the hidden page. Toggle the un-hide button to un-hide the translated page.
    9. Then flush the cache, open the jobs page in front-end, and try to switch the language from the language menu, it will work but when you switch to the German language there will be no content on the page as seen in the screenshot below, but when you switch to the English language you will see the content on the page, and this happens because we didn’t translate the records of extension to render in the German language.
    10. Let’s see how to translate extension records.

    How to translate extension record

    1. To get the records for the German language in front-end, translate the records from the List module.
    2. Go to Web > List and select the folder you have stored your records in, here that folder is “Employees”.

    3. Here we don’t have an option to translate the records. Before translating the records, we should translate the folder.
    4. To translate the folder do the same process as we did at the time of page translation.
    5. Then you will get the options for the German translation, set all the options and save the page.
    6. After Saving the translated folder, go to the List module and select the folder of records again and you will see the options to translate the records as seen in the screenshot below.
    7. Now click on the German flag highlighted in the above screenshot to translate the records and you will get a form to translate the fields as seen in the screenshot below.
    8. You can set a custom field name for the German language as seen in the screenshot below.
    9. Set the values for all the fields for the German language here (or for whatever language you want to add) and save the page.
    10. Reload or re-open the folder “Employees”, and you will see the translated records as seen in the screenshot below:
    11. As you can see in the above screenshot, we still could convert just one record to the German language and getting just one record in German language in front-end as you can see in the screenshot below:
    12. To translate all the records and get all the record on front-end, you will have to translate all the records as we did for the first record. After translating all the records, you will find the translated records in admin panel and also in front-end.

    13. As you can see in the above screenshot, the values get translated but the labels are still not translated. To translate them we need to use viewhelper in the template file for the plugin template. Let’s check how to translate labels from the plugin template.

    How to translate the plugin template

    1. Let’s use viewhelper to translate the labels from the plugin template.
    2. Open the job plugin template file – employee\Resources\Private\Templates\Job\Index.html
    3. Below is the screenshot with highlighted areas where you have to use viewhelper to translate.
    4. Replace the highlighted code with the given code below:
      <div class=""><strong><f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:vacancy" /> : </strong>{job.vacancy}</div>
      <div class=""><strong><f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:location" /> : </strong>{job.jobLocation }</div>
      <div class=""><strong><f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:jobtype" /> : </strong>{job.jobType }</div>
      <div class=""><strong><f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:yesr_experiance" /> : </strong>{job.yearsOfExperience }</div>
      <div class=""><strong><f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:hours" /> : </strong>{job.workingHrs }</div>
      <div class=""><strong><f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:days" /> : </strong>{job.workingDays }</div>
    5. Save the file after adding the above code and reload the front-end and you will see the labels are not displaying as still we didn’t updated the language files.
    6. Add the given code below to the employee\Resources\Private\Language\locallang_employee.xlf file to add the English text with the Ids we added in the above code of employee\Resources\Private\Templates\Job\Index.html
      <trans-unit id="vacancy" resname="vacancy">
      <source>Vacancy</source>
      </trans-unit>
      <trans-unit id="location" resname="location">
      <source>Location</source>
      </trans-unit>
      <trans-unit id="jobtype" resname="jobtype">
      <source>Job Type</source>
      </trans-unit>
      <trans-unit id="yesr_experiance" resname="yesr_experiance">
      <source>Experience in years</source>
      </trans-unit>
      <trans-unit id="hours" resname="hours">
      <source>Working hours</source>
      </trans-unit>
      <trans-unit id="days" resname="days">
      <source>Working days</source>
      </trans-unit>
    7. After above changes, save the file , flush the cache and reload the front-end and you will see the labels are appearing but in English language as seen in the screenshot below, because we haven’t added the German language file yet.
    8. Now we need to create the language file for the language German. create the file de.locallang_employee.xlf, the file path is: employee\Resources\Private\Language\de.locallang_employee.xlf
      <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
      <xliff version="1.0" approved="yes" >
      <file source-language="en" target-language="de" datatype="plaintext" date="2023-02-25T07:50:45Z" product-name="employee" original="EXT:employee/Resources/Private/Language/locallang_employee.xlf" >
      <body>
      <trans-unit id="vacancy" resname="vacancy" approved="yes">
      <source>Vacancy</source>
      <target>Stellenangebot</target>
      </trans-unit>
      <trans-unit id="location" resname="location" approved="yes">
      <source>Location</source>
      <target>Standort</target>
      </trans-unit>
      <trans-unit id="jobtype" resname="jobtype" approved="yes">
      <source>Job Type</source>
      <target>Auftragstyp</target>
      </trans-unit>
      <trans-unit id="yesr_experiance" resname="yesr_experiance" approved="yes">
      <source>Experience in years</source>
      <target>Erfahrung in Jahren</target>
      </trans-unit>
      <trans-unit id="hours" resname="hours" approved="yes">
      <source>Working hours</source>
      <target>Arbeitszeit</target>
      </trans-unit>
      <trans-unit id="days" resname="days" approved="yes">
      <source>Working days</source>
      <target>Arbeitstage</target>
      </trans-unit>
      </body>
      </file>
      </xliff>
    9. After save the above file flush the cache and reload the front-end and you will see the labels are translated now as you can see in the screenshot below:
    10. In the above screenshot, there is one issue that needs to be fixed, that is the button “Apply For Job” is still not rendering in German as we haven’t translated it in the template file for the Job plugin.
    11. Below is the screenshot of the template file with the highlighted area that needs to be update. The file is employee\Resources\Private\Language\locallang_employee.xlf
    12. Replace the text “Apply For Job” with the given code below:
      <f:translate key="LLL:EXT:employee/Resources/Private/Language/locallang_employee.xlf:applyforjob" />
    13. Then add the below code to the employee\Resources\Private\Language\locallang_employee.xlf to add the text and Id.
      <trans-unit id="applyforjob" resname="applyforjob">
          <source>Apply now</source>
      </trans-unit>
      
    14. Add the below code to the employee\Resources\Private\Language\de.locallang_employee.xlf file we already created.
      <trans-unit id="applyforjob" resname="applyforjob" approved="yes">
      <source>Apply for Job</source>
      <target>Für einen Job bewerben</target>
      </trans-unit>
    15. Flush the cache, reload the front-end and you will see the button is also translated now in German when the selected language is German.
    16. But still we can not open the detail page in German language as we didn’t translated the job detail page. Go to the Web > Page > Jobs > job-detail and translated the page by following the same procedure we did for the Jobs page.
    17. After translation of the job-detail page, flush the cache and reload the front-end and you will see the links for the detail pages.

    Share this Doc

    TYPO3 extension

    Or copy link

    CONTENTS