Joomla Custom Fields

This content was generated with the assistance of AI and should be verified against the official Joomla documentation before being relied on in production.

This sub-section’s Joomla bibliography lists the reference material consulted while preparing these pages.

Custom fields let a site builder add structured data to Joomla content without writing a component: a distance in kilometres on a trail report, a phone number on a user, a GPX file on a contact. This page was checked on 2026-10-30 against Joomla 6.2.x and PHP 8.3 or later. It covers the screens an editor and a site builder use, the plugin and helper classes that make them work, how a value travels from the edit form to the database and back to the page, and how a themer or developer changes the output.

The people involved are different at each step. A site builder creates field groups and fields and chooses where each one appears. An editor or author fills the fields in while editing an article, a contact or a user profile. A themer controls the markup through layout and plugin overrides in a template such as cassiopeia_tallpine. A developer makes a component such as com_tallpine field-aware and, when the built-in types are not enough, writes a new field-type plugin. The running example is the Tallpine Hiking Club trail report, which carries the fields distance-km, elevation-gain, difficulty, gpx-file and season.

Core custom fields are an old feature (Joomla 3.7), but they have kept changing. Joomla 6.0 added the Note and Number field types, and Joomla 6.1 added Audio, Video and Document variants of the Media field. The version sections below say which behaviour belongs to which release.

How custom fields are built

Custom fields are not part of the articles component. They are a separate component, com_fields, which stores the definitions, plus a family of plugins that supply the field types, plus one system plugin that wires the component into the host forms and pages.

Part Role

com_fields

Administrator screens Content → Fields and Content → Field Groups, the FieldModel and GroupModel, the FieldsHelper class and the site layouts used for output. The definitions live in the #__fields and #__fields_groups tables, the category assignment of each field in #__fields_categories and the entered values in #__fields_values (columns field_id, item_id and value).

Plugin group fields

One plugin per field type: calendar, checkboxes, color, editor, imagelist, integer, list, media, note, number, radio, sql, subform, text, textarea, url, user and usergrouplist. A plugin supplies a params/{type}.xml form with the type-specific settings and a tmpl/{type}.php file that renders the value.

System plugin System - Fields

Adds the com_fields group to the edit forms of supported components, saves the values after the item is saved and prints the fields after the title or before and after the content.

Content plugin Content - Fields

Replaces the tags {field ID} and {fieldgroup ID} in article text with the rendered field or the whole group.

Editors-xtd plugin Button - Fields

An editor button that inserts those tags.

The host component

Declares that it supports fields and says which contexts it offers. Articles, contacts, users and categories do this in core; com_tallpine does it in the example below.

The sequence is the same for every field type and is drawn in the figure below. The definition decides which fields a form gets, the type plugin builds the form field and renders the value, and the layouts wrap the result.

Custom fields from definition to form, database and page

The figure shows the three lanes: definitions, edit and save, and rendering. Everything a themer can override sits in the third lane.

Contexts

Every field belongs to exactly one context, written component.section. The context decides which edit form gets the field and which content item supplies the values. Core provides these contexts:

Context Component Where the field appears

com_content.article

Articles

The article edit form in the administrator and the front-end edit form, and the article, category blog and list views.

com_content.categories

Articles

The category edit form for article categories. Category fields can be shown on category views.

com_contact.contact

Contacts

The contact edit form and the contact view.

com_contact.mail

Contacts

The contact form that visitors fill in on the site.

com_contact.categories

Contacts

The category edit form for contact categories.

com_users.user

Users

The user edit form in the administrator, the registration form and the profile edit form on the site.

On the site, the same context is reached from several URLs. The component maps those sections for you: in com_content, the front-end sections form, featured and category all resolve to article; in com_users, the sections registration and profile resolve to user; and com_contact maps the contact form to mail and the other front-end sections to contact. A component that does not implement the fields service simply has no contexts, which is why fields never appear in, say, banner or news feed forms.

The Options of Articles and Contacts also contain Enable Custom Fields (the custom_fields_enable parameter, default Yes). Switching it off hides the feature for that component without deleting any data.

Which context a field is created for is fixed when it is created, by the context filter at the top of the Fields screen: the Fields list is always filtered by one context, and New creates a field for the selected one. A field cannot be moved to another context afterwards, so create the field again if you picked the wrong context. Field groups follow the same rule.

Contexts of third-party components

Contexts are open. Any component whose extension class implements FieldsServiceInterface (or FieldsFormServiceInterface) appears in the context drop-down of the Fields screen. The section on making a component field-aware shows the code. Tags are a special case: the context for items shown in a tag view is taken from the item’s own type alias, so a trail tagged family-friendly renders the same fields in a tag list as in its own list.

Field groups

A field group collects fields under a heading and a tab. Open Content → Field Groups, choose the context and select New. A group has a title, a description (shown as inline help under the group when Toggle Inline Help is on), a status (published, unpublished, archived or trashed), an access level, a language and a note. The Options tab holds Display When Read-Only, which fields inherit by default. The Permissions tab has its own ACL (see permissions and access levels).

A field assigned to a group appears in a tab named after the group on the edit form. Fields without a group are put in a general Fields tab. Groups appear in the order shown in the Field Groups list, which you change by dragging the rows, and fields are ordered inside the group by the order of the Fields list. For the Tallpine site:

  • group Trail data with the fields distance-km, elevation-gain, difficulty

  • group Season and files with season and gpx-file

Groups also work in the output: the tag {fieldgroup 3} in an article prints every field of group 3, and the system plugin’s output groups follow the same field order.

Creating a field

Open Content → Fields, select the context and choose New. The edit form has four tabs.

General tab

Setting Meaning

Type

One of the installed and enabled field-type plugins. The type cannot be changed after the field is saved, because the type decides the shape of the stored value. Create a new field and migrate the data instead.

Title, Name and Label

The Title is shown in the administrator list. The Name is the code identifier: it is used in jcfields overrides, in the Web Services API and in the stored data keys. Joomla creates it from the title when it is blank, and it is made URL-safe (lower case, hyphens). The Label is what the editor and the visitor see; it is the title when empty and it can be a language key.

Description

Tooltip text for the editor. It is not translatable and is not shown on the front end.

Required

The field must have a value before the item can be saved.

Only Use In Subform

Marks the field as a building block for a Subform field. Such fields are left out of the normal form and output.

Default Value

The initial value in a new item. When an existing item has no stored value the default is used for display as well.

Filter and Maximum Length

Appear on text-like types. The filter validates and cleans the input.

Status

Published, Unpublished, Archived or Trashed. Only published fields are loaded on the edit form and in the output. Trashed fields remain in the database until emptied.

Field Group

None or one of the groups of the same context.

Category

The article or contact categories in which the field appears. All means every category, and picking categories limits the field to them and their subcategories.

Access

The viewing access level that can see the field.

Language

The language of the field, * for all. On a multilingual site, fields are only offered for items of the same language, and * items show fields of the active site language only.

Note

A private note for administrators.

The category assignment is evaluated when the form loads. When an author changes the category of a new article the form reloads and shows the fields of the new category, which is why a page that "loses" typed text on a category change is behaving as designed. A field with no assigned category appears for every category. The Tallpine site assigns distance-km, elevation-gain and gpx-file to the category Trails, so they show for Trails → Alpine and Trails → Valley and are hidden for News.

Options tab

The Options tab is split into three panels. The settings are stored in the params column of the field.

Form Options

  • Placeholder (hint text in the backend form, never shown on the front end), Field Class and Label Class (Form) (CSS classes for the input and its label)

  • Editable In: Site, Administrator or Both. A field restricted to the administrator is not shown on the front-end edit form, which suits an internal field such as an editorial rating.

  • Showon Attribute: conditional display based on another field of the same form. The syntax is the one of any Joomla form: name:value shows the field when another field has that value, name!:value when it does not, [OR] and [AND] combine conditions, and name!: means "has any value". For fields in a subform use the field ID where the name would be.

Display Options

  • Display Class, Value Class, Label (show or hide) and Label Class (Output): CSS classes for the container, the value and the label of the rendered field

  • Automatic Display: where the system plugin inserts the field: After Title, Before Display Content, After Display Content or Do not automatically display. The default is before the content.

  • Prefix and Suffix: fixed text around the value, for example the unit km

  • Layout: an alternative layout file from the component or from a template (see choosing a layout per field)

  • Display When Read-Only: Inherit, Yes or No; whether a user without the Edit Value permission still sees the field, read-only, in the form

Smart Search

  • Search Index: whether the field value is indexed by Smart Search. The values are Don’t index, Make searchable, Add as taxonomy or Both. Indexing uses the permissions of the item, so a value indexed as searchable can reach visitors who cannot see the field itself. Check the field’s access level before turning it on. See Smart Search.

Publishing and Permissions tabs

Publishing shows the creation and modification data of the field, and Permissions has the ACL for the individual field (see below).

Field types

Each type is a plugin of the fields group in plugins/fields. A plugin can be disabled in System → Plugins to remove a type from the list; existing fields of that type then stop rendering.

Type Stored value Notes and options

Text

One string

Single-line text. Options: Filter (for example raw, safe HTML, text, alphanumeric, integer, float or telephone) and Maximum Length. The output is encoded with htmlentities.

Textarea

One string

Multi-line text with Rows, Columns, Maximum Length and a filter.

Editor

HTML

Opens the user’s default editor. Options: Buttons (editor buttons on or off), Hide (buttons to leave out), Width, Height and Filter, which should stay on the text filter of the user’s group so that the Text Filters configuration applies.

Integer

A whole number, or several

A drop-down generated from First, Last and Step, with Multiple. An integer always has a value, so it always renders. The User Guide suggests a plugin override or an out-of-range value to hide it.

Number (6.0)

A decimal number

An input with up and down arrows and Minimum, Maximum and Step Increment (decimal steps such as 0.01 are accepted). Format as Currency adds a Currency Symbol, a Symbol Position (before or after) and the number of decimals. The output formats the number with the language’s thousands and decimal separators.

List

A value or several values

A select list with a name/value pair per option, with Multiple and a Form Layout of basic or fancy select. The output shows the option text, translated with Text::_.

Radio and Checkboxes

One value, or several

The same option list in the form of radio buttons or checkboxes. Only the ticked options are printed.

Calendar

A date string

Date picker with the Show Time option.

Color

A hex colour

Colour chooser; the value is printed as hex text unless an override draws a swatch.

Url

A URL

Options: Schemes (allowed protocols), Relative and Show Url. The output is an anchor. External links get rel="nofollow noopener noreferrer" and target="_blank", and the link text is the generic Visit website unless Show Url is on.

Media

An image, or an object with the file and an alt text

Chooses a file with the Media Manager. See the next section for the 6.1 variants.

List of Images (imagelist)

File name

A list of the files of one folder, with Directory, Multiple and Image class.

SQL

One value, or several

A list filled by a query, with Query, Header and Multiple. The query must return value and text columns.

Subform

A JSON list of rows

A repeatable group of other fields. See the subform section.

User

A user ID, or several

A user selection modal. The output prints the user’s name.

Usergrouplist

A group ID, or several

A drop-down of the user groups, with Multiple. The output prints the group titles.

Note (6.0)

Nothing stored

A display-only block. See the Note section.

The User Guide documents these types on separate pages, linked from the references. The source of each type lives in plugins/fields/{type} in the source tree.

The Number field and the Integer field

Number and Integer differ in storage and in input. Integer offers a list of whole numbers generated from a first value, a last value and a step, and the selected value is stored as a number; because a select list always has a selection, an Integer field always renders. Number is a real number input that accepts decimals, can be left empty and can show currency formatting. For the Tallpine trail, distance-km is a Number field with step 0.1 and the suffix km, whereas elevation-gain is an Integer field with First 0, Last 3000 and Step 50.

Older books such as Joomla! 4 Masterclass (2023) describe the field types of Joomla 4 and have no Number or Note type; they suggest a Text or Integer field with a filter for decimal data. Since Joomla 6.0, use Number.

The Note field

Note (added in 6.0) is a layout element rather than a data field. It stores nothing: it prints a heading and a text into the edit form and, when Display in Frontend is on, into the output. Use it to put instructions between fields, for example a hint above the trail data: "Enter the distance as measured by the GPX track". The settings are:

  • Note Heading and Note Content (the content is an editor field, filtered by the text filter)

  • Note Class, which is alert alert-info by default, producing a Bootstrap alert box

  • Heading Tag, from h1 to h6 (default h4)

  • Show Close Button, which accepts true for an alert or another dismiss value

  • Display in Frontend, off by default

  • Automatic Display and Layout as for the other types

Because a Note holds no data, there is no default value to set. Several Notes can be used to give a long form some structure; they are ordered like any other field.

Media fields and the 6.1 media types

The Media field stores a reference to a file chosen in the Media Manager. Up to Joomla 6.0 it was an image picker, and the edit form offered the options Directory, Preview and Image class. The output was an img element built with the layout joomla.html.image; the stored value is a JSON object that carries the image file and its alt text, and older plain-string values are converted on the fly.

Joomla 6.1 extended the plugin with three more types, announced as the "Three New Media Custom Field Options: Audio, Video & Documents". When you create a field, the type list now contains Media, Audio, Document and Video, which are the layouts media, media_audio, media_document and media_video of the same plugin. Each type restricts the Media Manager modal to files of the matching kind, taken from the Media Manager options for image, audio, video and document extensions.

Type Output and options

Media (images)

img element. Directory, Preview and Image class.

Audio

An audio element. Options: Directory, Preview, a CSS class, Controls, Autoplay, Muted (only with autoplay), Loop and Preload.

Document

A download link whose text is entered by the author per item; the default is "Download {filename}" and the placeholder {filename} is replaced by the file name. Options: Directory, Preview and a CSS class. A local file that no longer exists produces no output.

Video

A video element. Options: Poster (let the author pick a poster image), Controls, Autoplay, Muted (only with autoplay), Loop, Play inline and Preload (auto, none or metadata, only without autoplay). The video plugin sends the controls and playsinline attributes by default.

Two practical consequences follow. First, the files a field can choose from are exactly the extensions allowed in the Media Manager options (image_extensions, audio_extensions, video_extensions and doc_extensions), and an upload is rejected when the extension is not in restrict_uploads_extensions or the MIME type is not in upload_mime (while Check MIME is on). The Tallpine gpx-file field is a Document field, and a GPX track is not in the default list, so the club’s administrator adds gpx as a document extension and application/gpx+xml as an allowed MIME type before authors can attach tracks. Second, the files come from the Media Manager, so the access of an author to upload follows the Media Manager’s Create permission: an author without it can choose existing files but cannot upload.

Because the 6.1 types are layouts of the plugin, a site that upgrades from 6.0 keeps its existing image fields unchanged. The 6.1 series also fixed the behaviour of the form when the type of a Media field is switched and the form reloads. See Media Manager for the options.

Subform fields

A Subform field groups other fields into a repeatable list. Create the member fields first, set Only Use In Subform to Yes on each, and then add the Subform field: select Repeat (more than one row), an optional Maximum Rows, and add the member fields one by one, with Render Values deciding whether the member is printed. The form layout can be the default, a repeatable form, a grid or a table (the Form Layout setting on the Subform field). Rows are stored as JSON in the single value of the Subform field and are printed as ul.fields-container with one li per row.

Two pitfalls are documented by the User Guide and the changelog. A Calendar field inside a subform can raise a fatal error when the article is saved a second time unless Show Time is set to Yes for it. And duplicate subform fields are removed on save, with a warning in the 5.4 and later series. The Tallpine site uses a subform called waypoints, with a Text field waypoint-name and a Number field waypoint-altitude, to list the passes of a multi-day route.

Permissions and access levels

Three mechanisms control who sees and changes a field.

  • Access level of the field and of the group controls who can see the rendered value and the field in the form.

  • Permissions of the field and the group are ordinary Joomla ACL: Create, Delete, Edit and Edit State manage the definition, and Edit Value (core.edit.value) controls whether a user may change the field’s value in an item. Permissions can be set globally in the options of the Fields component, per context, per group and per field. The assets are named by component, for example com_content.field.12 for the field with ID 12 and com_content.fieldgroup.3 for the group with ID 3. See Users, groups, access levels and ACL.

  • Editable In selects the application (site, administrator or both) in which the form shows the field.

A user without Edit Value never overwrites a value: the field is either hidden or shown as read-only, depending on the Display When Read-Only setting of the field, which by default is inherited from its group (whose default is Yes). When values are saved, the system plugin skips fields the user cannot edit, so a crafted request cannot change them either. For the Tallpine club, the field warden-notes (a Textarea in the Trail Warden group) gives Edit Value only to the custom group Trail Warden with the access level used for member-only trail conditions, so editors see nothing and the wardens can write.

The access level and the permission are two separate controls. A field with access level Registered is still editable by an Author with Edit Value in the administrator, even though visitors cannot see it. Test both as a Registered member, an Author and a Trail Warden before launching.

The Tallpine trail fields

The club’s trail report is an article in the category Trails. The fields below are defined for the context com_content.article:

Name Type Group Settings

distance-km

Number

Trail data

Required; minimum 0, maximum 200, step 0.1; suffix km; Automatic Display: Do not automatically display.

elevation-gain

Integer

Trail data

First 0, last 3000, step 50; suffix m; Automatic Display: Do not automatically display.

difficulty

List

Trail data

Options easy, moderate, hard, alpine (value) with translated names; Required; Layout: trail-badge.

season

Checkboxes

Season and files

Options spring, summer, autumn, winter; Label Class (Output) visually-hidden.

gpx-file

Document (6.1)

Season and files

Directory trails/gpx; link text "Download {filename}"; Access: Registered.

warden-notes

Textarea

Season and files

Access: Trail Warden; Permission Edit Value: Trail Warden only.

All of them are assigned to the category Trails (and so to Alpine and Valley). Setting Automatic Display to "Do not automatically display" for the number fields means that they appear only where an override places them, which the following sections do.

The definitions can also be listed and managed through the Web Services API, described in Web Services API.

# List the custom fields of the article context (token comes from the environment)
curl -s \
  -H "Accept: application/vnd.api+json" \
  -H "Authorization: Bearer ${TALLPINE_API_TOKEN}" \
  "http://localhost:8080/api/index.php/v1/fields/content/articles"

How a value is saved and read

Saving

The system plugin listens to onContentPrepareForm. For a supported form it calls FieldsHelper::prepareForm(), which loads the fields of the context and of the item’s category and fires onCustomFieldsPrepareDom once per field. The type plugin returns a DOM node that becomes a normal Joomla form field in the form group com_fields. The media plugin, for instance, changes the type of the node to accessiblemedia and disables the field for a user who may not upload.

When the item is saved, the plugin handles onContentAfterSave:

  1. The values under com_fields in the submitted data are matched with the fields of the context.

  2. A field that is not on the form (hidden, or restricted by Editable In) keeps its stored value.

  3. Empty values are removed, and complex values (arrays of arrays, or arrays with non-numeric keys) are encoded as JSON.

  4. FieldModel::setFieldValue() checks the Edit Value permission and writes the rows to #__fields_values. A multi-value field such as Checkboxes stores one row per value.

For a user, the same flow is run from onUserAfterSave, with the exception of the tasks activate, block and unblock, because those would otherwise wipe the stored data. When the item is deleted, onContentAfterDelete removes its values.

Reading

FieldsHelper::getFields($context, $item, $prepareValue) is the central API. It reads the published fields of the context, filters them by the category of the item and, on a multilingual site, by the item’s language, and returns copies of the field objects with these properties:

Property Content

id, name, title, label, type, context, group_id

The definition.

params and fieldparams

Registry objects with the common options and the type-specific options.

rawvalue

The value as stored, or the default value when nothing is stored.

value

The prepared HTML when $prepareValue is true, produced by the type plugin’s tmpl file; otherwise equal to the raw value.

With $prepareValue, getFields() fires three events around the preparation: onCustomFieldsBeforePrepareField, onCustomFieldsPrepareField (the type plugin renders) and onCustomFieldsAfterPrepareField. A plugin can change the field before preparation or the HTML afterwards. Since Joomla 5.3 these events are concrete classes in Joomla\CMS\Event\CustomFields and field plugins are SubscriberInterface subscribers; code that still reads the generic Event::getArgument() array, as shown in older books such as Developing Extensions for Joomla! 5 (2023), should use getField(), getItem() and getContext() on the event objects.

The system plugin also handles onContentPrepare: it fills $item->jcfields, an array of prepared fields keyed by field ID. The Web Services API uses the same helper and exposes every field as an attribute named after the field.

Output: Automatic Display

For each field the system plugin decides when to print it. The three content events are the hooks:

Automatic Display setting Event Typical position in the layout

After Title

onContentAfterTitle

Directly below the heading, via $this->item->event->afterDisplayTitle.

Before Display Content

onContentBeforeDisplay

Between the info block and the text, via $this->item->event->beforeDisplayContent.

After Display Content

onContentAfterDisplay

After the text, via $this->item->event->afterDisplayContent.

Do not automatically display

none

The field is only printed where a layout, an override or a {field ID} tag prints it.

Fields set to the same position are collected by fields/render.php and printed together, in order, as a list: ul.fields-container, one li.field-entry per non-empty field, each with the field’s name and render class. A field with an empty value is skipped, which is why a "not required" field leaves no empty label behind.

The core field/render.php layout wraps one field: an optional span.field-label, the optional prefix, the value in a span.field-value and the optional suffix. The classes configured under Display Options are added to these elements.

Placing a field with a tag

Inside an article text, two tags insert fields. The Content - Fields plugin replaces {field 12} by the field with ID 12 and {fieldgroup 3} by every field of group 3. The tag can be followed by a layout name, for example {field 12 trail-badge}. The editor button Fields inserts the tag from a list. Tags are a convenience for editors; a template author who needs stable markup should rather use overrides, because IDs change between environments.

Overriding the layout

There are four places a themer can change the output, from coarse to fine. The examples assume the child template cassiopeia_tallpine; see Child templates and Template overrides and alternative layouts.

1. Place named fields in the article layout

Set Automatic Display to Do not automatically display, then print the fields where you want them in the view override. The array $this->item->jcfields is keyed by field ID, so build a lookup by name first.

<?php
// templates/cassiopeia_tallpine/html/com_content/article/default.php (excerpt)
defined('_JEXEC') or die;

use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;

$fields = [];

foreach ($this->item->jcfields as $field) {
    $fields[$field->name] = $field;
}

$stat = function (string $name) use ($fields): string {
    if (!isset($fields[$name]) || $fields[$name]->value === '') {
        return '';
    }

    // value is already HTML prepared by the field type plugin
    return FieldsHelper::render(
        $fields[$name]->context,
        'field.render',
        ['field' => $fields[$name]]
    );
};
?>
<dl class="tallpine-trail-stats">
    <dt><?php echo Text::_('TPL_TALLPINE_DISTANCE'); ?></dt>
    <dd><?php echo $stat('distance-km'); ?></dd>
    <dt><?php echo Text::_('TPL_TALLPINE_ELEVATION'); ?></dt>
    <dd><?php echo $stat('elevation-gain'); ?></dd>
</dl>

Two details matter. The value property is already prepared by the type plugin (plain-text types such as Text and List are encoded, an Editor field is HTML), so print it without further escaping; the rawvalue property is the stored text and must be escaped by the override with htmlspecialchars() or a safer helper. And jcfields only contains fields that passed the category, language and access checks, so test with isset() instead of assuming every field exists. The User Guide example for contacts uses the same idea in html/com_contact/contact/default.php, and the same code works for users and articles.

2. Change the wrapper: the field layouts

FieldsHelper::render() first looks for a layout with the requested name in the layout folders of the component of the context, and falls back to the layout of com_fields. In a template the search order for field.render includes:

  • html/layouts/com_content/field/render.php (the component of the context)

  • html/layouts/com_fields/field/render.php

  • html/layouts/field/render.php

flowchart TD A[FieldsHelper::render context, field.render] --> B{Override in template<br/>html/layouts/com_content/field?} B -- yes --> Z[Print the override] B -- no --> C{Override in html/layouts/com_fields/field<br/>or html/layouts/field?} C -- yes --> Z C -- no --> D{Component layouts folder<br/>components/com_content/layouts/field?} D -- yes --> Z D -- no --> E[Core layout<br/>components/com_fields/layouts/field/render.php] E --> Z

The Tallpine template replaces the wrapper for every field, to print the label as a dt style heading and the value in a badge, by adding a file to the child template.

<?php
// templates/cassiopeia_tallpine/html/layouts/com_fields/field/render.php
defined('_JEXEC') or die;

use Joomla\CMS\Language\Text;

if (!isset($displayData['field'])) {
    return;
}

$field  = $displayData['field'];
$value  = $field->value;

if ($value === '' || $value === null) {
    return;
}

$label  = Text::_($field->label);
$prefix = $field->params->get('prefix') ? Text::plural($field->params->get('prefix'), $value) : '';
$suffix = $field->params->get('suffix') ? Text::plural($field->params->get('suffix'), $value) : '';
?>
<?php if ((int) $field->params->get('showlabel') === 1) : ?>
    <span class="tallpine-field-label"><?php echo htmlspecialchars($label, ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
<span class="tallpine-field-value <?php echo htmlspecialchars((string) $field->params->get('value_render_class'), ENT_QUOTES, 'UTF-8'); ?>">
    <?php echo $prefix ? htmlspecialchars($prefix, ENT_QUOTES, 'UTF-8') . ' ' : ''; ?>
    <?php echo $value; ?>
    <?php echo $suffix ? ' ' . htmlspecialchars($suffix, ENT_QUOTES, 'UTF-8') : ''; ?>
</span>

The list wrapper is fields/render.php in the same folders. Overriding it changes the ul.fields-container markup for all the fields of a position, for example to print them as a Bootstrap definition list.

Choosing a layout per field

The Layout option on the Options tab lists the layouts that exist in the component’s layouts/field folder and in html/layouts/{component}/field, html/layouts/com_fields/field and html/layouts/field of every enabled site template. Files named render.php and files starting with an underscore are not offered. When you pick a layout, the list wrapper calls field.{layout} instead of field.render for that field only. The Tallpine difficulty field uses a layout trail-badge:

<?php
// templates/cassiopeia_tallpine/html/layouts/com_fields/field/trail-badge.php
defined('_JEXEC') or die;

$field = $displayData['field'];

if ($field->rawvalue === '' || $field->rawvalue === null) {
    return;
}

// rawvalue holds the list option value: easy, moderate, hard or alpine
$level = preg_replace('/[^a-z]/', '', strtolower((string) $field->rawvalue));
?>
<span class="badge bg-secondary trail-badge trail-badge--<?php echo $level; ?>"><?php echo $field->value; ?></span>

The raw value is reduced to lower-case letters before it becomes part of a class name, so a changed option list cannot inject markup.

The tmpl/{type}.php file of a field plugin can be overridden like a module or a view, in html/plg_fields_{type}/{type}.php. Use this when every field of one type should print differently, for example every Url field. The core Url type prints the generic text "Visit website" for external links. The Tallpine site wants a labelled link with an icon and a visually hidden warning that the link opens a new window:

<?php
// templates/cassiopeia_tallpine/html/plg_fields_url/url.php
defined('_JEXEC') or die;

use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;

$value = $field->value;

if ($value === '') {
    return;
}

$external = !Uri::isInternal($value);
$attrs    = $external ? ' rel="nofollow noopener noreferrer" target="_blank"' : '';
$text     = $fieldParams->get('show_url', 0) ? htmlspecialchars($value) : Text::_('TPL_TALLPINE_OPEN_TRAIL_MAP');
?>
<a class="tallpine-link" href="<?php echo htmlspecialchars($value); ?>"<?php echo $attrs; ?>>
    <?php echo $text; ?>
    <?php if ($external) : ?>
        <span class="visually-hidden"><?php echo Text::_('TPL_TALLPINE_OPENS_NEW_WINDOW'); ?></span>
    <?php endif; ?>
</a>

The plugin file receives the variables $field, $fieldParams (the plugin parameters merged with the field’s own parameters, where the field wins) and $context. The same technique draws a colour swatch for a Color field, which the User Guide describes, or an audio element with a download link for the Audio type. Prefer an override of the plugin file to editing the plugin, which an update replaces.

4. Change the form

The form layout of some types is selectable in the field options (Form Layout on List, SQL, Radio, Integer and Subform), for example a fancy select with search for a long list. The input markup of other types comes from the standard form field layouts under layouts/joomla/form/field, which a template can override in html/layouts/joomla/form/field. See Forms and form fields.

Making a component field-aware

Custom fields come for free in the core components. A component such as com_tallpine has to opt in. The work has four parts.

  1. The service. The component’s extension class implements FieldsFormServiceInterface (or only FieldsServiceInterface) and returns the contexts and the section mapping.

  2. The form. The edit form needs the category field catid and the language field, because the system plugin uses the item’s catid to decide which fields to show. The model must use the form name com_tallpine.trail.

  3. The events. Saving through AdminModel::save() triggers onContentAfterSave with the context of the model, so values are saved without extra code. The site views call the content plugins for onContentPrepare, onContentAfterTitle, onContentBeforeDisplay and onContentAfterDisplay with the context com_tallpine.trail.

  4. The access rules. access.xml gets fieldgroup and field sections so that the ACL assets of fields and groups can exist under com_tallpine, as com_content does in its own access.xml.

<?php
// com_tallpine/administrator/src/Extension/TallpineComponent.php
namespace Tallpine\Component\Tallpine\Administrator\Extension;

use Joomla\CMS\Categories\CategoryServiceInterface;
use Joomla\CMS\Categories\CategoryServiceTrait;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Factory;
use Joomla\CMS\Fields\FieldsFormServiceInterface;
use Joomla\CMS\Fields\FieldsServiceTrait;
use Joomla\CMS\Language\Text;

defined('_JEXEC') or die;

final class TallpineComponent extends MVCComponent implements CategoryServiceInterface, FieldsFormServiceInterface
{
    use CategoryServiceTrait, FieldsServiceTrait {
        CategoryServiceTrait::prepareForm insteadof FieldsServiceTrait;
    }

    public function validateSection($section, $item = null)
    {
        if (Factory::getApplication()->isClient('site') && ($section === 'category' || $section === 'form')) {
            // The list of trails and the front-end edit form use the trail context
            $section = 'trail';
        }

        return $section === 'trail' ? $section : null;
    }

    public function getContexts(): array
    {
        Factory::getApplication()->getLanguage()->load('com_tallpine', JPATH_ADMINISTRATOR);

        return [
            'com_tallpine.trail'      => Text::_('COM_TALLPINE_TRAILS'),
            'com_tallpine.categories' => Text::_('JCATEGORY'),
        ];
    }
}

The Joomla Developer Manual has no dedicated custom-fields page, and the section on events does not list the onCustomFields events. The reference for the integration is the source of com_content, com_contact and com_users, together with the Manual’s page on categories, which com_tallpine also needs. See Component MVC in the administrator, Categories, custom fields and workflow integration and ACL in extensions.

<!-- com_tallpine/administrator/access.xml (excerpt) -->
<access component="com_tallpine">
    <section name="component">
        <action name="core.admin" title="JACTION_ADMIN" />
        <action name="core.manage" title="JACTION_MANAGE" />
        <action name="core.create" title="JACTION_CREATE" />
        <action name="core.edit" title="JACTION_EDIT" />
        <action name="core.edit.state" title="JACTION_EDITSTATE" />
        <action name="core.edit.value" title="JACTION_EDITVALUE" />
    </section>
    <section name="fieldgroup">
        <action name="core.create" title="JACTION_CREATE" />
        <action name="core.delete" title="JACTION_DELETE" />
        <action name="core.edit" title="JACTION_EDIT" />
        <action name="core.edit.state" title="JACTION_EDITSTATE" />
        <action name="core.edit.value" title="JACTION_EDITVALUE" />
    </section>
</access>

A site view of a trail then prints its fields with the usual calls. The call to FieldsHelper::getFields() is only needed when the template prints fields by hand; the plugins do the rest.

<?php
// com_tallpine/src/View/Trail/HtmlView.php (excerpt)
use Joomla\CMS\Event\Content\ContentPrepareEvent;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Registry\Registry;

PluginHelper::importPlugin('content');

$item->text = $item->description;
$this->getDispatcher()->dispatch(
    'onContentPrepare',
    new ContentPrepareEvent(
        'onContentPrepare',
        ['context' => 'com_tallpine.trail', 'subject' => $item, 'params' => new Registry($item->params ?? '{}'), 'page' => 0]
    )
);
// $item->jcfields is now filled by the System - Fields plugin

Reading and writing values through the API

The Content web services plugin registers CRUD routes for the field definitions (v1/fields/content/articles and v1/fields/content/categories) and for the groups (v1/fields/groups/content/articles and v1/fields/groups/content/categories). For the values, an article in the API carries every field as a top-level attribute named after the field, and a write moves those attributes into the com_fields array before saving. The headless Tallpine trail map reads a trail like this:

GET /api/index.php/v1/content/articles/42 HTTP/1.1
Host: localhost:8080
Accept: application/vnd.api+json
Authorization: Bearer ${TALLPINE_API_TOKEN}
{
  "data": {
    "type": "articles",
    "id": "42",
    "attributes": {
      "title": "Lake Ridge circular",
      "distance-km": "12.4",
      "elevation-gain": "850",
      "difficulty": "moderate"
    }
  }
}
PATCH /api/index.php/v1/content/articles/42 HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Authorization: Bearer ${TALLPINE_API_TOKEN}

{ "distance-km": "12.9", "difficulty": "hard" }

The user behind the token must hold Edit Value for the fields, as in the administrator. Joomla 6.2 enables the API token plugin and disables the Basic Auth API plugin on a fresh installation, so use tokens.

Versioning

Joomla keeps a version history of articles and contacts when Enable Versions (save_history) is on in the component options, with a Maximum Versions (history_limit) count. Joomla 6.0 replaced the old approach, in which the Behaviour - Versionable plugin stored a snapshot after each table save, with a model-based concept: a model that implements VersionableModelInterface and uses VersionableModelTrait writes a snapshot in AdminModel::save() through saveHistory(). The snapshot is the item’s row merged with the submitted form data. The Versionable plugin is deprecated in 6.0 for removal in 8.0.

Two points follow for custom fields. First, the values entered for an item are part of the form submitted with the item, so a version snapshot of an article is taken from the same save that stores the values; verify on your own site that restoring a version brings back the values you expect, because values are stored in #__fields_values rather than in the item row. Second, the field and group definitions themselves are managed by FieldModel and GroupModel, which do not implement the versioning interface in 6.2, so the field edit screen has no Versions button. Treat definitions as configuration: export the database or keep a changelog before changing field options. The Web Services API makes it possible to script a backup of the definitions with the routes shown above.

Multilingual sites

A field and a group each have a language. A field with language All appears for every item. On a multilingual site, a field with a specific language only appears for items of that language and for items set to All when the active language matches. Translate labels and option names with language keys (the label of a field is passed through Text::_), and give each translated option text its own constant in the template’s or a plugin’s language file, because option text for List, Radio and Checkboxes fields is also translated with Text::_. The Default Value and the Description of a field are not translated. See Multilingual sites and Language files and internationalisation.

Security

  • Escaping. The core type plugins encode their output (htmlentities for text, list and radio values). A plugin or layout override that prints rawvalue must escape it. Editor fields store HTML, so their output is trusted only as far as the filter allows: leave the Filter on the group’s text filter and never on raw for fields that untrusted users can write.

  • SQL fields. The query is executed with the database credentials of the site every time the form or the output is built. Only a user with permission to create fields can define it, and that permission should be limited to Super Users. Use a fixed query that returns value and text, and never include user input.

  • Edit Value. Grant it deliberately. Users can see a field they may not edit, but the ACL of the item still decides whether they can save the item at all.

  • Access levels. A field left at the Public access level is public wherever it is output, including a Smart Search index that was told to make it searchable.

  • User fields. Fields in the com_users.user context are shown on registration and profile forms. Do not put internal data in a user field that is editable on the site.

  • Uploads. Document, Audio and Video fields use the Media Manager. Keep the allowed extensions and MIME types short, and keep Restrict Uploads and Check MIME on.

  • Required fields. The Required flag is checked on the server by the form validation, so it is not only a client hint.

See Security in extensions for the general checklist.

Common errors and pitfalls

Symptom Cause and fix

The field does not show on the edit form

The field is unpublished, assigned to another category, has another language, or Editable In excludes the current application. A new article in the default category shows only the fields without category, so choose the category first.

The field shows in the form but not on the page

The value is empty (empty fields are not printed), Automatic Display is set to Do not automatically display, or the user lacks the field’s access level. An Integer field always renders; use a Number field if "no value" is a valid state.

The value disappears after a user is activated or blocked

Core skips the field save for those tasks on purpose. If a custom plugin re-saves users, do the same.

A change in a field type setting has no effect

Page and view caching can serve the old output. Clear the cache in System → Clear Cache after changing definitions or adding overrides on a site with caching on.

The type list is missing Audio, Document or Video

The site runs Joomla 6.0 or earlier, or the Media field plugin is disabled.

A Document field refuses a GPX file

The extension is not in the Media Manager’s document extensions or upload restrictions, or its MIME type is not allowed.

Renaming a field broke the layout

Overrides and API clients use the field’s Name. Changing it needs a matching change everywhere, and the stored values stay linked to the field ID.

A subform with a Calendar fails on the second save

Set Show Time to Yes on the Calendar field, as the User Guide describes.

The Layout option shows no layouts

The folder is in the wrong place, the file name starts with an underscore or is render.php, or the template is not enabled.

Version notes and corrections of older material

Version Change

3.7

Custom fields introduced in core.

4.0

The fields service interfaces for components (FieldsServiceInterface) and the Web Services API routes.

5.3

Field plugins use event classes and are SubscriberInterface subscribers.

6.0

The Note and Number field types, and the model-based versioning concept.

6.1

The Audio, Document and Video variants of the Media field, with file-type restriction in the Media Manager modal. Modules also get a version history in this release.

Older books and tutorials contain advice that no longer applies. Joomla! 4 Masterclass (2023) targets Joomla 4, which reached end of life in October 2025: it has no Note or Number type and no Audio, Video or Document field, and it points to docs.joomla.org/J4.x pages and to jQuery-based behaviours that are not part of the current stack. Developing Extensions for Joomla! 5 (2023) covers the integration of components with custom fields in the style of Joomla 5.0, where plugin code reads generic Event arguments; use the concrete event classes. The legacy wiki at docs.joomla.org marks its own custom-field pages as obsolete; the current end-user documentation is the Joomla User Guide at guide.joomla.org, and the Developer Manual has no custom-fields page, so for development rely on the source tree. See Joomla versions, release cycle and support for the support dates and Where older books and tutorials are outdated for the other corrections.