This page explains the concept of metadata in AtroCore and how developers can work with it to define and extend entity structures.

What is Metadata?

Metadata stores various useful data in the system that can be extended by any other modules, like data about entities, including options, fields, relationships, frontend controllers, custom services and more. These data are defined in JSON format in the Resources/metadata folder, located in the root directory of an application or custom module. Metadata is loaded and merged into a single structure during system initialization.

AtroCore uses metadata in two main layers:

  • Application Layer: Metadata tells the system how to render fields in the UI (e.g., varchar → input, text → textarea), how to filter/search, and how to manage relationships.
  • Database Layer: Metadata defines which fields and indexes should exist in the database.

Common Metadata

Here are some commonly used metadata available in AtroCore:

Data Description
scopes Defines entity parameters, such as the type. Each file is name as {EntityName}.json (e.g. Product.json)
entityDefs Describes the structure of an entity: its fields, relationships, indexes, and collection behavior. Each file is name as {EntityName}.json
clientDefs Stores data about frontend controllers, views, panels, and dashlets. This data determines the frontend behavior for entities. each file is name as {EntityName}.json
twig Contains filters.json and functions.json, which register filters and functions used in script fields. Learn more in Twig Templating.
app Defines application-level data. with data like acl.json or adminPanels listing the links in the Admin panel

Storing new data

Let's store a new data by creating the file Resources/metadata/examples/images.json with the content:

{
    "image1": "image1-url",
    "image2": "image2-url",
    "thumbnail":{
        "iphone": "iphone-thumbnail-url"
    },
    "sizes" : [20, 40]
}

Accessing Metadata

To access data from metadata, get metadata from the service container:

/** @var \Atro\Core\Utils\Metadata::class $metadata */
$metadata = $container->get('metadata')

$image1 = $metadata->get(['examples', 'images', 'image1'])
// $image1 is "image1-url"
$image2 = $metadata->get(['examples', 'images', 'image2'])
// $$image2 is image2-url
$image3 = $metadata->get(['examples', 'images', 'image3'])
// $image3 is null
$image3 = $metadata->get(['examples', 'images', 'image3'], 'default-image3-url')
// $image3 is 'default-image3-url'
$thumbnail =  $image3 = $metadata->get(['examples', 'images', 'thumbnail', 'iphone'])
// is iphone-thumbnail-url

The PhpStorm plugin AtroCore Toolkit can help you with the autocompletion of metadata keys.

In the frontend the metadata can be accessed from any view like this:

let image1 = this.getMetadata().get(['examples', 'images', 'image1'])

Extending Metadata

Core metadata is loaded first, followed by metadata from modules in their defined loading order. This means that a later module can extend or override any existing metadata.

This powerful extending ability of metadata is frequently used by custom module to add new fields on existing entity

There are two ways to extend metadata:

Static Extension

Metadata content can be extended by any module by following the same path. For example, a module can overwrite an existing key or add new ones. For arrays, you can use the __APPEND__ keyword to add new values instead of overwriting the entire array.

Example: A module adds the file Resources/metadata/examples/images.json with the following content:

{
    "image2": "new-content",
    "image3": "https://picsum.photos/id/2/50/50",
    "sizes": [
        "__APPEND__",
        50
    ]
}

This file will be merged with the original definition. Now, accessing the data:

/** @var \Atro\Core\Utils\Metadata::class $metadata */
$metadata = $container->get('metadata')
$image1 = $metadata->get(['examples', 'images', 'image1'], 'defaultImageValue')
// $image1 is "image1-url"
$image2 = $metadata->get(['examples', 'images', 'image2'])
// $image2 is "new-content"
$image3 = $metadata->get(['examples', 'images', 'image3'])
// $image3 is https://picsum.photos/id/2/50/50
$sizes = $metadata->get(['examples', 'images', 'sizes'])
// $sizes is [20, 40, 50]

Dynamic Extension via Listener

Use a Listeners\Metadata.php listener to modify metadata programmatically. This is useful when:

  • Metadata should be added conditionally
  • Complex merging logic is required

Example:

namespace {ModuleNamespace}\Listeners;

use Atro\Listeners\AbstractListener;

class Metadata extends AbstractListener
{
    public function modify(Event $event): void
    {
         $data = $event->getArgument('data');
         $data['examples']['images']['image1'] = 'modify-programmatically'
    }
}

Now, accessing the data:

/** @var \Atro\Core\Utils\Metadata::class $metadata */
$metadata = $container->get('metadata')

$image1 = $metadata->get(['examples', 'images', 'image1'])
// $image1 is modify-programmatically

You can check a real life example in Metadata.php in AtroPim module

Metadata entityDefs

entityDefs metadata is used to define fields, relationships, indexes, and collection behavior. This information helps the system to:

  • Generate database schemas.
  • Determine which fields and links exist.
  • Define how fields are rendered in the UI.
  • Enable filtering and sorting.
  • Ensure consistency across modules.

Example: Brand.json The Brand entity is defined in the Pim module. You can find its metadata at /app/Resources/metadata/entityDefs/Brand.json.

{
    "fields": {
        "name": {
            "type": "varchar",
            "required": true,
            "trim": true,
            "isMultilang": true
        },
        "description": {
            "type": "text",
            "required": false,
            "rowsMax": 4,
            "lengthOfCut": 400,
            "seeMoreDisabled": false,
            "readOnly": false,
            "tooltip": false,
            "isMultilang": true
        },
        "files": {
            "type": "linkMultiple",
            "layoutDetailDisabled": true,
            "massUpdateDisabled": true,
            "noLoad": true
        },
        "createdAt": {
            "type": "datetime",
            "readOnly": true
        },
        "modifiedAt": {
            "type": "datetime",
            "readOnly": true
        },
        "createdBy": {
            "type": "link",
            "readOnly": true,
            "view": "views/fields/user"
        },
        "modifiedBy": {
            "type": "link",
            "readOnly": true,
            "view": "views/fields/user"
        },
        "products": {
            "type": "linkMultiple",
            "layoutDetailDisabled": true,
            "layoutListDisabled": true,
            "massUpdateDisabled": true,
            "noLoad": true,
            "importDisabled": true
        },
        "code": {
            "type": "varchar",
            "trim": true,
            "unique": true
        }
    },
    "links": {
        "files": {
            "type": "hasMany",
            "relationName": "brandFile",
            "foreign": "brands",
            "entity": "File"
        },
        "createdBy": {
            "type": "belongsTo",
            "entity": "User"
        },
        "modifiedBy": {
            "type": "belongsTo",
            "entity": "User"
        },
        "products": {
            "type": "hasMany",
            "foreign": "brand",
            "entity": "Product"
        }
    },
    "collection": {
        "sortBy": "createdAt",
        "asc": false,
        "textFilterFields": [
            "name",
            "code"
        ]
    },
    "indexes": {
        "name": {
            "columns": [
                "name",
                "deleted"
            ]
        }
    }
}

Let breaks down this.

Fields

As you may have noticed, each field of your entity is defined as a key in fields, and it values are options about the field like type, required or isMultilang for multilingual fields. the available supported types are int, float, bool, varchar, text, color, markdown, wysiwyg, url, email, password, date, datetime, enum, multiEnum, extensibleEnum, extensibleMultiEnum, measure, file, link, linkMultiple

Fields should be named in camel case format.

Any field that is a relation should be defined in links to give more information about the relation.

After doing any change in the entityDefs folder, it directly as and impact on the database layer, to see the change to make your database consistent with your modification, run the command php console.php sql diff --show the system will generate SQL queries. To apply the query to make the database and the App consistent run php console.php sql diff --run

Entity Management and custom Metadata

AtroCore Allow in the administration any users with appropriate rights on Entity Entity to:

  • Add fields and links to existing entities
  • Create entirely new entities

These changes are stored as metadata files in: data/metadata/entityDefs/, where data is in the root of your application in your server.

Getting Metadata

The metadata are loaded, merged and cached in the backend to data/cache/metadata.json. This file is recreated everytime the system cache is cleared. When doing any modifications on metadata files inResources/metadata, make sure to clear the system cache, by running the command php console.php clear cache.

Disabling the useCache setting in data/config.php prevents the application from utilizing the cache. When this option is set to false, metadata is rebuilt with every request. This feature is highly beneficial for developers, as it streamlines the development workflow by removing the requirement to repeatedly clear the cache.

You can also get the metadata using the API GET route /api/v1/Metadata. You can learn more about on how to interact with the API here.

Summary

  • Metadata define data available in the backend and frontend
  • Metadata defines entity structure for both DB and UI.
  • It is stored in JSON files and merged across modules.
  • You can extend metadata statically or dynamically.
  • Entity Manager-generated metadata is stored in /data/metadata and loaded last.