Skip to content

Custom fields management in Admin API#111

Draft
Jeremie-Kiwik wants to merge 2 commits into
PrestaShop:devfrom
Jeremie-Kiwik:pr-custom-fields
Draft

Custom fields management in Admin API#111
Jeremie-Kiwik wants to merge 2 commits into
PrestaShop:devfrom
Jeremie-Kiwik:pr-custom-fields

Conversation

@Jeremie-Kiwik
Copy link
Copy Markdown
Contributor

@Jeremie-Kiwik Jeremie-Kiwik commented Nov 21, 2025

Questions Answers
Description? Feature proposal:*custom fields management in Admin API🎉
Type? new feature
BC breaks? no
Deprecations? no
Fixed ticket?
Sponsor company KIWIK
How to test? see below

Description

This PR adds support for custom fields in API Resources entities, allowing modules to extend entities with additional data that is automatically serialized/deserialized and persisted in dedicated SQL tables. The implementation integrates seamlessly into the existing API Platform serialization flow without requiring modifications to core entity classes.

EDIT (2025-11-25)

Important change in persistence mechanism:

The persistence and retrieval of custom fields data is no longer handled by the core module itself. Instead, it is now the responsibility of modules implementing the hooks. This change provides better separation of concerns and allows modules to have full control over their custom data storage.

New hooks added:

  • loadApiResourcesCustomFields: Called when serializing entities to load custom fields data
  • persistApiResourcesCustomFields: Called after entity creation/update to persist custom fields data

The core module now only handles:

  • Metadata collection via addApiResourcesCustomFields hook
  • Extraction of custom fields from requests
  • Injection of custom fields into responses (via hooks)
  • Calling hooks for persistence (modules handle the actual database operations)

Experimental Work & Community Contribution

This PR represents a Proof of Concept (POC) for custom fields support in the Admin API. This is experimental work that I'm sharing with the community to gather feedback and encourage collaboration.

I would love receive feedback on:

  • The overall approach and architecture
  • The hook structure and metadata format
  • The JSON format for custom fields (base, localized, shop-specific)
  • Any edge cases or use cases I might have missed (I'm sure there are)
  • Suggestions for improvements or alternative approaches

I would be delighted if this PR could be picked up, improved, or modified by other contributors. My hope is that, with community feedback and collaboration, we can turn this experimental POC into a robust and efficient mechanism for handling custom fields in the Admin API.

Custom Field Types

Custom fields can be of three types:

  • Base fields: Stored in custom tables (e.g., {entity}_extra). These fields are directly accessible in JSON at the root level: "stringField": "value".

  • Localized fields: Stored in custom tables with language support (e.g., {entity}_lang_extra), using locale-based JSON format:

    "attributeGroupLangExtra": {
      "stringLangField": {
        "fr-FR": "Valeur en français",
        "en-GB": "Value in English"
      }
    }
  • Shop-specific fields: Stored in custom tables with shop support (e.g., {entity}_shop_extra), using shop ID-based JSON format:

    "attributeGroupShopExtra": {
      "intShopField": {
        "1": 100
      }
    }

Table Naming

Note that table names are completely free - the _extra, _lang_extra, and _shop_extra suffixes are just naming conventions suggested in the example module, but modules can use any table names they want.

How It Works

Modules declare custom fields via the addApiResourcesCustomFields hook, and implement two additional hooks to handle persistence:

  • loadApiResourcesCustomFields: Load custom fields data from database when serializing entities
  • persistApiResourcesCustomFields: Persist custom fields data to database after entity creation/update

The core module handles:

  • Extraction from incoming requests (POST/PATCH)
  • Calling hooks for loading/persisting data
  • Injection into API responses (GET/POST/PATCH)

This feature enables modules to extend any API Resources entity without modifying core code, with full control over their data storage.

Hook Implementation Example

A complete example module implementation is available in modules/ps_apiresources/docs/custom-fields-hook-example.php. This file contains a fully functional module demonstrating all three hooks with base fields, localized fields, and shop-specific fields.

Here's a minimal example of how to implement the three hooks:

public function install()
{
    return parent::install()
        && $this->registerHook('addApiResourcesCustomFields')
        && $this->registerHook('loadApiResourcesCustomFields')
        && $this->registerHook('persistApiResourcesCustomFields')
        && $this->installTables();
}

// Declare custom fields metadata
public function hookAddApiResourcesCustomFields(array $params)
{
    $entity = $params['entity'];
    $customFields = &$params['customFields'];

    if ($entity === 'AttributeGroup') {
        $customFields['fields']['attribute_group_extra'] = [
            'stringField' => [
                'type' => 'string',
                'column' => 'string_field',
                'nullable' => false,
            ],
        ];
    }

    return $customFields;
}

// Load custom fields from database
public function hookLoadApiResourcesCustomFields(array $params)
{
    $entity = $params['entity'];
    $entityId = (int) $params['entityId'];
    $customFields = &$params['customFields'];

    if ($entity === 'AttributeGroup') {
        $sql = 'SELECT string_field FROM `' . _DB_PREFIX_ . 'attribute_group_extra`
                WHERE id_attribute_group = ' . (int) $entityId;
        $row = Db::getInstance()->getRow($sql);
        if ($row) {
            $customFields['stringField'] = (string) $row['string_field'];
        }
    }

    return $customFields;
}

// Persist custom fields to database
public function hookPersistApiResourcesCustomFields(array $params)
{
    $entity = $params['entity'];
    $entityId = (int) $params['entityId'];
    $customFieldsData = $params['customFieldsData'];
    $entityData = $params['entityData'] ?? []; // Native entity data

    if ($entity === 'AttributeGroup' && isset($customFieldsData['stringField'])) {
        $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'attribute_group_extra`
                (id_attribute_group, string_field)
                VALUES (' . (int) $entityId . ', \'' . pSQL($customFieldsData['stringField']) . '\')
                ON DUPLICATE KEY UPDATE string_field = \'' . pSQL($customFieldsData['stringField']) . '\'';
        Db::getInstance()->execute($sql);
    }
}

How to test

Step-by-Step Testing Procedure

Step 1: Create the example module

  1. Create the module directory:

    mkdir -p modules/ps_apiresources_extra
  2. Copy the example file:

    cp modules/ps_apiresources/docs/custom-fields-hook-example.php modules/ps_apiresources_extra/ps_apiresources_extra.php

Step 2: Install the module

  1. Go to the PrestaShop Back Office
  2. Navigate to Modules > Module Manager
  3. Search for "ps_apiresources_extra" or find it in the module list
  4. Click Install
  5. Verify that the installation was successful

Step 3: Verify database tables creation

  1. Connect to your database

  2. Check that the following tables have been created:

    SHOW TABLES LIKE '%attribute_group%extra%';

    You should see:

    • ps_attribute_group_extra (base fields)
    • ps_attribute_group_lang_extra (localized fields)
    • ps_attribute_group_shop_extra (shop-specific fields)

Step 4: Test POST request (Create entity with custom fields)

  1. Send a POST request to /admin-api/attribute-groups with the following JSON body:
{
  "names": {
    "fr-FR": "Couleurs",
    "en-GB": "Colors"
  },
  "publicNames": {
    "fr-FR": "Couleurs",
    "en-GB": "Colors"
  },
  "type": "select",
  "shopIds": [1],
  "position": 0,
  "stringField": "My custom string",
  "intField": 42,
  "boolField": true,
  "enumField": "value1",
  "attributeGroupLangExtra": {
    "stringLangField": {
      "fr-FR": "Champ personnalisé en français",
      "en-GB": "Custom field in English"
    }
  },
  "attributeGroupShopExtra": {
    "intShopField": {
      "1": 100
    }
  }
}
  1. Verify the response:

    • The response should include all custom fields in the JSON body
    • Check that you receive a 201 Created status code
    • Note the attributeGroupId from the response (you'll need it for the next steps)
  2. Verify database persistence:

    -- Check base fields
    SELECT * FROM ps_attribute_group_extra WHERE id_attribute_group = <ID_FROM_RESPONSE>;
    
    -- Check lang fields
    SELECT * FROM ps_attribute_group_lang_extra WHERE id_attribute_group = <ID_FROM_RESPONSE>;
    
    -- Check shop fields
    SELECT * FROM ps_attribute_group_shop_extra WHERE id_attribute_group = <ID_FROM_RESPONSE>;

    You should see:

    • One row in ps_attribute_group_extra with the base custom fields
    • Two rows in ps_attribute_group_lang_extra (one for fr-FR, one for en-GB)
    • One row in ps_attribute_group_shop_extra (for shop ID 1)

Step 5: Test GET request (Retrieve entity with custom fields)

  1. Retrieve the created entity via GET /admin-pi/attribute-groups/{id} (replace {id} with the attributeGroupId from Step 4).

  2. Verify the response:

    • Custom fields should be included in the response
    • Base fields (stringField, intField, boolField, enumField) should appear at the root level
    • Lang fields should use locale format: "attributeGroupLangExtra": {"stringLangField": {"fr-FR": "...", "en-GB": "..."}}
    • Shop fields should use shop ID format: "attributeGroupShopExtra": {"intShopField": {"1": 100}}

Step 6: Test PATCH request (Partial update)

  1. Update an entity with partial custom fields data via PATCH /api/attribute-groups/{id}:
{
  "attributeGroupLangExtra": {
    "stringLangField": {
      "fr-FR": "Nouvelle valeur française"
    }
  }
}
  1. Verify the response:

    • Only the provided field (fr-FR locale) should be updated
    • Existing values for other locales (e.g., en-GB) should be preserved
    • The response should reflect the updated values
  2. Verify database update:

    SELECT * FROM ps_attribute_group_lang_extra WHERE id_attribute_group = <ID>;

    You should see:

    • The fr-FR row should have the new value "Nouvelle valeur française"
    • The en-GB row should still have the original value "Custom field in English"

@ps-jarvis
Copy link
Copy Markdown

ps-jarvis commented Nov 24, 2025

This pull request seems to contain new translation strings. I have summarized them below to ease up review:

  • Modules.Psapiresourcesextra.Admin ⚠️
    • Add custom fields to Admin API Resources
    • Add custom fields to Admin API Resources entities

(Note: this is an automated message, but answering it will reach a real human)

Copy link
Copy Markdown

@Hlavtox Hlavtox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge commit

@ps-jarvis ps-jarvis moved this from Ready for review to Waiting for author in PR Dashboard Nov 24, 2025
Copy link
Copy Markdown
Contributor

@jolelievre jolelievre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Jeremie-Kiwik

thanks for the contribution, but I belive I laready explained on the OS slacks The admin API is not made to be extended.

Each endpoint today is built on CQRS commands that reflect the business logic of the Core, and only the core. If you have extra fields, they should be handled separately by your modules in a dedicated endpoint. But the core API contract shouldn't be modified in anyway by external modules.

This is what ensures the API stability and consistency across any shop.

@Jeremie-Kiwik
Copy link
Copy Markdown
Contributor Author

I understand your point of view. At the same time, in the real day-to-day life of agencies, every project requires custom fields. As a result, the native endpoints will almost never be used as-is, because they don’t meet the actual needs. Each developer, in every agency, will therefore need to reimplement the native logic / adapt it to handle some new fields. This leads to:

  • duplicating existing core logic,
  • losing compatibility as soon as the core evolves (or even worse: it keeps working, but introduces undetected bugs),
  • no longer benefiting from future improvements brought by the core.

If I take the example of my current customer project alone, I already need to redefine the endpoints for customers, groups, features, feature values, attributes groups, attribute, specific prices, products, combinations, cetegories and addresses.
Each of them potentially becoming a source of issues in the future.

This is why I think having a standard extension mechanism is important. Without one, every agency will implement its own version of the Admin API, resulting in a fragmented ecosystem with hundreds of incompatible implementations.

The proposal in this PR has the advantage of relying directly on native code, and of following PrestaShop's principles of interacting with the processing flow through hooks. Hooks are already used everywhere in Prestashop. Why not in the Admin API?

To avoid altering the native API contract, custom fields could even be isolated under a dedicated key, such as extraFields. This would ensure native data remains untouched and fully stable.

My personal though is that if the Admin API does not support the reality of actual shops, developers simply won't be able to use it in real projects.

This proposal aims to allow safe, controlled extensibility without changing the native API structur, by using the hook system already widely used in others parts of the software.

@jf-viguier
Copy link
Copy Markdown
Contributor

But the core API contract shouldn't be modified in anyway by external modules.
In theory, that's fine, but we're talking about adding fields, not changing behavior or breaking anything.

By example, you have 3 modules that add extra datas to customer : a reference, a paiement preferences, ...
The ERP will have to know and to ask 4 endpoints to get all datas @jolelievre ?

@kpodemski
Copy link
Copy Markdown
Contributor

The need is real, it’s also about APIs performance, number of requests needed, etc. I think we should try to think this through.

@Hlavtox
Copy link
Copy Markdown

Hlavtox commented Nov 27, 2025

I absolutely get the need, but I think the topic of custom fields reaches beyond the API context. The need is real and almost every ERP I got my hands on has to ability to create them. Or even CMS systems like Processwire.

For example, in our ERP system, for every entity, I can define custom fields. It's name, data type, value, default value, note. After creation, it automatically creates a new column in the database for a given entity. Then I can use it like any of the default fields in all listing tables, in filters, I can export/import it, print templates, anywhere.

@jf-viguier
Copy link
Copy Markdown
Contributor

Let's take a look at this, it's native in sylius:
https://docs.sylius.com/the-customization-guide/customizing-serialization-of-api#how-to-add-a-custom-field-to-a-response

@PrestaEdit
Copy link
Copy Markdown
Contributor

PrestaEdit commented Dec 1, 2025

Maybe we will need to transform thois PR into a discussion.

But, first of all : thanks and wow, @Jeremie-Kiwik. You take the time to analyze, comment, make the dev and document it with example. It's a good things and a real thanks from me, really.

PrestaShop is made to be customized. It's always be there and done like this.

Maybe we want to remove override but you all know that PrestaShop will and will be customized with third party modules and/or development. It's a fact.

It's not only one merchant, it's not only one agency. It's all the PrestaShop instances.

This new Admin API is made to be more robust, more easier than the legacy webservice. PrestaShop gives already somes hooks but the CQRS is not so easy to handle to extend it.

Admin API need to be more flexible, as I think, to handle theses customs sides. It's the powerfull of PrestaShop and of this Admin API.

I hope that, as a open source side of the project, this will be discuss as it.
And, maybe, in an additionnal way of the council as it's surely done now.
I hope that we could make something as this proposal, one day : PrestaShop/open-source#122 (Edit : not really like this proposal but like the RFC voting system in PHP, with more than five guys ;-))

(My english is not so good, but I hope it's understood there.)

@ivan-kiwik
Copy link
Copy Markdown

The way Jérémie implemented the handling of custom fields respects, in my opinion, the PrestaShop philosophy.

Modules declare their fields and manage persistence using hooks.

As already mentioned, in a real-world scenario, we need to minimize web service calls as much as possible for performance reasons. Otherwise, this API will not be usable or used.

@jolelievre : What is blocking this PR?

@nico-leb
Copy link
Copy Markdown

nico-leb commented Dec 3, 2025

Hello everyone, I'm joining the discussion as both this API and custom fields are subjects that matter a lot.

Custom fields in this new API will indeed be very useful and a serious improvement for day-to-day workflows.

In other APIs I use, you can add parameters to "embed" additional data in the response.
In any case, a way to extend standard responses with hooks seems like a natural fit for PrestaShop.

If not through the proposed approach, perhaps we could have a way to extend standard responses by adding "links" to simplify discovery. For example, for products:

{
  "_extra": {
    "mymodule": {
      "href": "/admin-api/my-customroute/{product_id}"
    }
  },

@kpodemski
Copy link
Copy Markdown
Contributor

Hello @jolelievre

Could this be discussed with the @PrestaShop/tech-council?

@kpodemski kpodemski marked this pull request as draft January 23, 2026 08:28
@kpodemski
Copy link
Copy Markdown
Contributor

I know this project is being discussed, so I'm putting this PR into the "Draft".

@jolelievre jolelievre closed this Apr 22, 2026
@github-project-automation github-project-automation Bot moved this from Waiting for author to Closed in PR Dashboard Apr 22, 2026
@jolelievre jolelievre reopened this Apr 22, 2026
@github-project-automation github-project-automation Bot moved this from Closed to Reopened in PR Dashboard Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Reopened

Development

Successfully merging this pull request may close these issues.

9 participants