---
source_hash: "2d56a0a0"
title: "Dynamic Forms and Extrafields"
weight: 990
---

# Dynamic Forms and Extrafields

> Dolibarr extrafields allow adding custom fields to business objects. SmartMaker provides a complete system to expose them to the React application with automatic form generation.

## Overview

The SmartMaker extrafields system works in three steps:

```
1. Dolibarr Admin: Configure which extrafields to expose (RO/RW)
              ↓
2. PHP Mapper: Read configuration, expose metadata
              ↓
3. React: Dynamically generate forms
```

## 1. Admin Configuration

### Configuration Page

SmartBoot generates an admin page `admin/smartmaker.php` that allows configuring which extrafields are exposed to the application.

```php
// Define Dolibarr objects to configure
$elementsToConfig = array(
    'fichinter' => 'Interventions',
    'societe' => 'Third Parties',
    'projet_task' => 'Tasks',
);
```

This page displays all extrafields of each object with two columns:

| Option | Description |
| --- | --- |
| **RO** (Read-Only) | The field is visible in the application but not editable |
| **RW** (Read-Write) | The field is visible AND editable |

### Configuration Storage

The configuration is stored in Dolibarr constants:

```
MONMODULE_SMARTMAKER_EXTRAFIELDS_RO = "field1,field2,field3"
MONMODULE_SMARTMAKER_EXTRAFIELDS_RW = "field4,field5"
```

## 2. PHP Mapper (dmGenericObject)

### Basic Structure

The mapper inherits from `dmBase` and uses `dmTrait` for extrafields management:

```php
<?php
namespace MonModule\Api;

use SmartAuth\DolibarrMapping\dmBase;
use SmartAuth\DolibarrMapping\dmTrait;

class dmItem extends dmBase
{
    use dmTrait;

    // Dolibarr source class
    protected $parentClassName = 'Fichinter';

    // Element for extrafields (llx_extrafields table)
    protected $parentElementToUseForExtraFields = 'fichinter';

    // Mapping of standard fields
    protected $listOfPublishedFields = [
        'rowid'       => 'id',
        'ref'         => 'ref',
        'description' => 'description',
        'fk_statut'   => 'status',
    ];

    // Editable fields via API
    protected $writableFields = [];
}
```

### Automatic Loading of Extrafields

The constructor loads configuration from Dolibarr:

```php
public function __construct()
{
    global $db;
    $this->db = $db;

    // Read-only extrafields
    $extRO = getDolGlobalString('MONMODULE_SMARTMAKER_EXTRAFIELDS_RO');
    if (!empty($extRO)) {
        foreach (explode(',', $extRO) as $field) {
            $field = trim($field);
            if (!empty($field)) {
                $key = 'options_' . $field;
                $this->listOfPublishedFields[$key] = $key;
            }
        }
    }

    // Read-write extrafields
    $extRW = getDolGlobalString('MONMODULE_SMARTMAKER_EXTRAFIELDS_RW');
    if (!empty($extRW)) {
        foreach (explode(',', $extRW) as $field) {
            $field = trim($field);
            if (!empty($field)) {
                $key = 'options_' . $field;
                $this->listOfPublishedFields[$key] = $key;
                $this->writableFields[] = $key;
            }
        }
    }

    $this->boot();
}
```

### objectDesc() Method

The `objectDesc()` method (provided by `dmTrait`) returns field metadata for the frontend:

```php
// In the controller
$mapping = new dmItem();
$config = $mapping->objectDesc();

// Returns an array with info for each field:
// [
//     'options_niveau' => [
//         'type' => 'select',
//         'label' => 'Level',
//         'required' => true,
//         'options' => ['1' => 'Easy', '2' => 'Medium', '3' => 'Hard'],
//         'writable' => true,
//     ],
//     ...
// ]
```

## 3. Controller: Expose Configuration

The controller must return the configuration to the frontend:

```php
class ItemController
{
    private $mapping;

    public function __construct()
    {
        $this->mapping = new dmItem();
    }

    // GET /items (list + config)
    public function index($arr = null)
    {
        // ... load items ...

        return [[
            'statusCode' => 200,
            'items' => $items,
            'config' => $this->mapping->objectDesc(),  // Field metadata
        ], 200];
    }

    // GET /items/123 (detail + config)
    public function show($arr = null)
    {
        $id = $arr['id'] ?? 0;
        // ... load item ...

        return [[
            'statusCode' => 200,
            'item' => $this->mapping->map($dolibarrObject),
            'config' => $this->mapping->objectDesc(),
        ], 200];
    }
}
```

## 4. Frontend: Dynamic Forms

### Store Configuration

Store the configuration received from the API in Redux or local state:

```jsx
// In component or via Redux
const [config, setConfig] = useState({});

useEffect(() => {
    api.private.get('items')
        .then(response => {
            setConfig(response.config || {});
            // ... process items ...
        });
}, []);
```

### FormComponentsMap

SmartCommon provides `FormComponentsMap` which maps Dolibarr types to React components:

```jsx
import { FormComponentsMap } from "@cap-rel/smartcommon";

// FormComponentsMap returns the appropriate component based on type:
// 'varchar'  -> Input
// 'text'     -> Textarea
// 'int'      -> Input type="number"
// 'double'   -> Input type="number" step="0.01"
// 'date'     -> DatePicker
// 'datetime' -> DateTimePicker
// 'boolean'  -> Checkbox
// 'select'   -> Select
// 'sellist'  -> Select (with options from DB)
// 'radio'    -> RadioGroup
// 'checkbox' -> CheckboxGroup
// 'link'     -> SearchSelect (link to another Dolibarr object)
```

### Dynamic Form Generation

```jsx
const DynamicForm = ({ config, values, onChange }) => {
    return (
        <div className="flex flex-col gap-4">
            {Object.entries(config).map(([fieldName, fieldConfig]) => {
                // Get component based on type
                const Component = FormComponentsMap(fieldConfig.type);

                if (!Component) return null;

                return (
                    <Component
                        key={fieldName}
                        label={fieldConfig.label}
                        value={values[fieldName] || ''}
                        onChange={(val) => onChange(fieldName, val)}
                        required={fieldConfig.required}
                        disabled={!fieldConfig.writable}
                        options={fieldConfig.options}  // For select/radio
                    />
                );
            })}
        </div>
    );
};
```

### Complete Example with useApi

```jsx
import { useStates, useApi, Page, Button, Block } from "@cap-rel/smartcommon";
import { FormComponentsMap } from "@cap-rel/smartcommon";

export const ItemEditPage = ({ itemId }) => {
    const { states, set } = useStates({
        item: null,
        config: {},
        isLoading: true,
        isSaving: false,
    });

    const { item, config, isLoading, isSaving } = states;
    const api = useApi();

    // Load item and its configuration
    useEffect(() => {
        api.private.get(`items/${itemId}`)
            .then(response => {
                set("item", response.item);
                set("config", response.config);
            })
            .finally(() => set("isLoading", false));
    }, [itemId]);

    // Change a value
    const handleFieldChange = (fieldName, value) => {
        set("item", { ...item, [fieldName]: value });
    };

    // Save
    const handleSave = () => {
        set("isSaving", true);
        api.private.put(`items/${itemId}`, item)
            .then(() => {
                // Success
            })
            .finally(() => set("isSaving", false));
    };

    if (isLoading) return <div>Loading...</div>;

    return (
        <Page>
            <Block title="Edit item">
                {/* Standard fields */}
                <Input
                    label="Reference"
                    value={item?.ref || ''}
                    disabled
                />

                {/* Dynamic extrafields */}
                {Object.entries(config).map(([fieldName, fieldConfig]) => {
                    // Only show extrafields (start with options_)
                    if (!fieldName.startsWith('options_')) return null;

                    const Component = FormComponentsMap(fieldConfig.type);
                    if (!Component) return null;

                    return (
                        <Component
                            key={fieldName}
                            label={fieldConfig.label}
                            value={item?.[fieldName] || ''}
                            onChange={(val) => handleFieldChange(fieldName, val)}
                            disabled={!fieldConfig.writable}
                            required={fieldConfig.required}
                            options={fieldConfig.options}
                        />
                    );
                })}

                <Button
                    onClick={handleSave}
                    loading={isSaving}
                >
                    Save
                </Button>
            </Block>
        </Page>
    );
};
```

## Supported Extrafield Types

| Dolibarr Type | React Component | Notes |
| --- | --- | --- |
| varchar | Input | Short text |
| text | Textarea | Long text |
| int | Input (number) | Integer |
| double | Input (number) | Decimal with step |
| date | DatePicker | Date only |
| datetime | DateTimePicker | Date and time |
| boolean | Checkbox | Yes/No |
| select | Select | Dropdown |
| sellist | Select | List from SQL |
| radio | RadioGroup | Radio buttons |
| checkbox | CheckboxGroup | Multiple checkboxes |
| link | SearchSelect | Link to Dolibarr object |
| price | Input (number) | Price with formatting |

## Best Practices

### Security

- Never expose sensitive extrafields (passwords, tokens)
- Verify permissions server-side before modification
- Validate data received from frontend

### Performance

- Cache configuration if it doesn't change often
- Only load necessary extrafields

### UX

- Group extrafields by category if numerous
- Use clear and translated labels
- Display required fields distinctly

## Summary

The SmartMaker extrafields system allows:

1. **Configure** via Dolibarr admin which fields to expose
2. **Map** automatically with dmGenericObject constructor
3. **Expose** metadata via objectDesc()
4. **Generate** dynamic React forms with FormComponentsMap

This system avoids modifying code each time an extrafield is added: admin configuration is sufficient.

[Mappers](/training/module8-backend-api/mappers) | [Module 9: Integration ->](/training/module9-integration)
