<p>First, from your field type’s <code>inputHtml()</code> method, make sure you’re passing the field instance to the template as a <code>field</code> variable, and the value as a <code>value</code> variable:</p>
<pre class="lang-php prettyprint-override"><code>protected function fieldHtml($value, ElementInterface $element = null): string
{
    return Craft::$app-&gt;view-&gt;renderTemplate('plugin-handle/path/to/CustomField_input', [
        'field' =&gt; $this,
        'value' =&gt; $value,
    ]);
}
</code></pre>
<p>Then from your template, each of your input names should resemble how you want them to be accessed from the field data – <em>namespaced by the field handle</em>, and you should pull the values the same way you want to on the front end:</p>
<div class="s-table-container">
<table class="s-table">
<thead>
<tr>
<th>Input name</th>
<th>Input value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&quot;#{field.handle}[subGroupA][text]&quot;</code></td>
<td><code>value.subGroupA.text</code></td>
</tr>
<tr>
<td><code>&quot;#{field.handle}[subGroupA][dropdown]&quot;</code></td>
<td><code>value.subGroupA.dropdown</code></td>
</tr>
</tbody>
</table>
</div>
<p>You’ll also want to “namespace” your input IDs by prefixing them with the field handle, in case the same field type is used multiple times in a field layout.</p>
<p>Here’s what it will look like:</p>
<pre><code>{{ forms.textField({
    id: &quot;#{field.handle}-subGroupAText&quot;,
    name: &quot;#{field.handle}[subGroupA][text]&quot;,
    type: 'number',
    value: value.subGroupA.text ?? '',
}) }}
{{ forms.selectField({
    id: &quot;#{field.handle}-subGroupADropdown&quot;,
    name: &quot;#{field.handle}[subGroupA][dropdown]&quot;,
    options: dropdownValues,
    value: field.subGroupA.dropdown ?? 'defaultDropdownValue',
}) }}
</code></pre>
<p>The field will need to store its data as JSON. To get that working…</p>
<ul>
<li><p>Your <code>getContentColumnType()</code> method should return <code>text</code>:</p>
<pre class="lang-php prettyprint-override"><code>public function getContentColumnType()
{
    return \yii\db\Schema::TYPE_TEXT;
}
</code></pre>
</li>
<li><p>Your <code>normalizeValue()</code> method should check if the value is a string, and if so, JSON-decode it:</p>
<pre class="lang-php prettyprint-override"><code>public function normalizeValue($value, ElementInterface $element = null)
{
    if (is_string($value)) {
        $value = \craft\helpers\Json::decode($value);
    }
    return $value;
}
</code></pre>
</li>
</ul>

CC BY-SA 4.0
2021-07-12T12:28:02.687Z
Wow, thanks for the detailed write-up, BK! After having done my best to implement it and still not getting data saved, I have a few questions:
1) is `protected function inputHtml()` (I assume the `fieldHtml` in the code is a typo) the same as the `public function getInputHtml()` that pluginfactory.io started me out with, in `plugin-handle/src/fields/CustomFieldField.php`?
2) `value: value.subGroupA...` or `value: field.subGroupA...`?
3) `getContentColumnType()` was set to `TYPE_STRING`; is that different from `TYPE_TEXT`?
Thanks again!!!!
Sandwich
CC BY-SA 4.0
2021-07-12T14:36:30.297Z
@Sandwich 1) Yeah, in general the protected one is recommended, but either one will work. I’ll PR pluginfactory.
2) `value`
3) yeah `string` will give you a `VARCHAR` column; `TEXT` is better for storing JSON.
Brandon Kelly
CC BY-SA 4.0
2021-07-12T14:54:28.293Z
@Sandwich Just updated the example with namespaced input IDs as well. Forgot you’ll want to do that to ensure you don’t get conflicts if the same field type is used multiple times within the same field layout.
Brandon Kelly
CC BY-SA 4.0
2021-07-12T17:18:13.083Z
Ok, that got things working! I think the main thing left is to figure out how to get `{{ entry.fieldHandle.subGroupA` to custom-output the same as `{{ entry.fieldHandle.subGroupA.text ~ entry.fieldHandle.subGroupA.dropdown }}`. I haven't done any searching yet on the subject (although I'm not quite sure what to search _for_); could you point me in the right direction?
Sandwich
CC BY-SA 4.0
2021-07-12T20:38:00.090Z
@Sandwich That would be a bit more complicated; you would need to start normalizing your field value into nested objects using custom classes in `normalizeValue()`. See Craft’s built-in Color field type + the ColorData model for a relatively simple example.
Brandon Kelly
CC BY-SA 4.0
2021-07-14T08:33:00.910Z
Ok, that's certainly pointed me in the right direction! I have the basic "sub" functions working - `entry.fieldHandle.subGroupA` is spitting out what I want it to. It seems like that comes at the cost of being able to reference field values by their data structure, though (eg. `entry.fieldHandle.subGroupA.dropdown`). Is there any way to have my cake and eat it too? ;)
Sandwich
CC BY-SA 4.0
2021-07-20T23:37:28.853Z
@Sandwich If the subgroups are represented via instantiated class(es), you can add a `__toString()` method to it to determine what should be output when the object itself is output “as a string”, e.g. `entry.fieldHandle.subGroupA`.
Brandon Kelly
CC BY-SA 4.0
2021-07-21T04:42:47.400Z
Ahh, so however I would want multiple "levels" of output variables, I'd need everything down to the next-to-last level to be a class (assuming the straight-up data hierarchy didn't suffice because things needed massaging before output)?
Sandwich
CC BY-SA 4.0
2021-07-12T05:17:40.640Z
2021-07-12T14:53:34.073Z
2021-07-12T14:53:34.073Z
Brandon Kelly
<p>I'm working on what I thought would be a relatively simple plugin; a custom fieldtype that would have 8 &quot;sub-groups&quot;, each of which would consist of a numeric text field and a dropdown field with a few predefined options.</p>
<p>At the Twig/templating end of things, I'd like it to work like this:</p>
<pre><code>{{ entry.customField.subGroup.text }} // (outputs the text field for that sub-group)
{{ entry.customField.subGroup.dropdown }} // (outputs the dropdown field for that sub-group)
{{ entry.customField.subGroup }} // (outputs a combined string of `subGroup.text` and `subGroup.dropdown`)
</code></pre>
<p>Additionally, in the field's settings, I have 8 lightswitch fields to disable any individual sub-group (the <code>{% if %}</code> statement wrapping the code below).</p>
<p>All together, I'm putting it all together in the <code>CustomField_input.twig</code> file like this:</p>
<pre><code>{% set dropdownValues = [
    { label: 'Foo', value: 'foo' },
    { label: 'Bar', value: 'bar' },
    { label: 'Baz', value: 'baz' }
] %}
{% if field.subGroup %}
    {% embed '_includes/forms/field' with {
        label: 'Sub-Group A'|t,
        instructions: &quot;A sub-group of the field&quot;|t,
        id: 'subGroupA'
    } %}
        {% import &quot;_includes/forms&quot; as forms %}
        {% block input %}
            &lt;div class=&quot;flex&quot;&gt;
                {{ forms.textField({
                    id: 'subGroupAText',
                    name: 'subGroupAText',
                    type: 'number',
                    value: field.subGroupAText ?? ''
                }) }}
                {{ forms.selectField({
                    id: 'subGroupADropdown',
                    name: 'subGroupADropdown',
                    options: dropdownValues,
                    value: field.subGroupADropdown ?? 'defaultDropdownValue'
                }) }}
            &lt;/div&gt;
        {% endblock %}
    {% endembed %}
{% endif %}
</code></pre>
<p>First off, the above code renders the fields I want correctly, but no values get saved. Not sure what that's about. But before I resolve that, I suspect I should figure out what I need to alter in the above code to get the nested aspect of the custom field. Is it as simple as changing:</p>
<pre><code>id: 'subGroupAText',
name: 'subGroupAText',
...

value: field.subGroupAText ?? ''
</code></pre>
<p>...to this...?</p>
<pre><code>id: 'subGroupA[text]',
name: 'subGroupA[text]',
...

value: field.subGroupA[text] ?? ''
</code></pre>

<plugin-development><custom-fieldtype>
How to build a custom fieldtype containing multiple nested groups of text and dropdown pairs?
# How to build a custom fieldtype containing multiple nested groups of text and dropdown pairs?

(Asked by: username_0 on 2021-07-11)

I'm working on what I thought would be a relatively simple plugin; a custom fieldtype that would have 8 "sub-groups", each of which would consist of a numeric text field and a dropdown field with a few predefined options.
At the Twig/templating end of things, I'd like it to work like this:
{{ entry.customField.subGroup.text }} // (outputs the text field for that sub-group)
{{ entry.customField.subGroup.dropdown }} // (outputs the dropdown field for that sub-group)
{{ entry.customField.subGroup }} // (outputs a combined string of `subGroup.text` and `subGroup.dropdown`)

Additionally, in the field's settings, I have 8 lightswitch fields to disable any individual sub-group (the {% if %} statement wrapping the code below).
All together, I'm putting it all together in the CustomField_input.twig file like this:
{% set dropdownValues = [
    { label: 'Foo', value: 'foo' },
    { label: 'Bar', value: 'bar' },
    { label: 'Baz', value: 'baz' }
] %}
{% if field.subGroup %}
    {% embed '_includes/forms/field' with {
        label: 'Sub-Group A'|t,
        instructions: "A sub-group of the field"|t,
        id: 'subGroupA'
    } %}
        {% import "_includes/forms" as forms %}
        {% block input %}
            
                {{ forms.textField({
                    id: 'subGroupAText',
                    name: 'subGroupAText',
                    type: 'number',
                    value: field.subGroupAText ?? ''
                }) }}
                {{ forms.selectField({
                    id: 'subGroupADropdown',
                    name: 'subGroupADropdown',
                    options: dropdownValues,
                    value: field.subGroupADropdown ?? 'defaultDropdownValue'
                }) }}
            
        {% endblock %}
    {% endembed %}
{% endif %}

First off, the above code renders the fields I want correctly, but no values get saved. Not sure what that's about. But before I resolve that, I suspect I should figure out what I need to alter in the above code to get the nested aspect of the custom field. Is it as simple as changing:
id: 'subGroupAText',
name: 'subGroupAText',
...

value: field.subGroupAText ?? ''

...to this...?
id: 'subGroupA[text]',
name: 'subGroupA[text]',
...

value: field.subGroupA[text] ?? ''

**Tags:** plugin-development, custom-fieldtype

---

## 1 answers

### Answer by username_1 on 2021-07-12. Score: 2 | Accepted answer

First, from your field type’s inputHtml() method, make sure you’re passing the field instance to the template as a field variable, and the value as a value variable:
protected function fieldHtml($value, ElementInterface $element = null): string
{
    return Craft::$app->view->renderTemplate('plugin-handle/path/to/CustomField_input', [
        'field' => $this,
        'value' => $value,
    ]);
}

Then from your template, each of your input names should resemble how you want them to be accessed from the field data – namespaced by the field handle, and you should pull the values the same way you want to on the front end:




Input name
Input value




"#{field.handle}[subGroupA][text]"
value.subGroupA.text


"#{field.handle}[subGroupA][dropdown]"
value.subGroupA.dropdown




You’ll also want to “namespace” your input IDs by prefixing them with the field handle, in case the same field type is used multiple times in a field layout.
Here’s what it will look like:
{{ forms.textField({
    id: "#{field.handle}-subGroupAText",
    name: "#{field.handle}[subGroupA][text]",
    type: 'number',
    value: value.subGroupA.text ?? '',
}) }}
{{ forms.selectField({
    id: "#{field.handle}-subGroupADropdown",
    name: "#{field.handle}[subGroupA][dropdown]",
    options: dropdownValues,
    value: field.subGroupA.dropdown ?? 'defaultDropdownValue',
}) }}

The field will need to store its data as JSON. To get that working…

Your getContentColumnType() method should return text:
public function getContentColumnType()
{
    return \yii\db\Schema::TYPE_TEXT;
}


Your normalizeValue() method should check if the value is a string, and if so, JSON-decode it:
public function normalizeValue($value, ElementInterface $element = null)
{
    if (is_string($value)) {
        $value = \craft\helpers\Json::decode($value);
    }
    return $value;
}

> **Comments:**
> **username_0**: Wow, thanks for the detailed write-up, BK! After having done my best to implement it and still not getting data saved, I have a few questions: 1) is `protected function inputHtml()` (I assume the `fieldHtml` in the code is a typo) the same as the `public function getInputHtml()` that pluginfactory.io started me out with, in `plugin-handle/src/fields/CustomFieldField.php`? 2) `value: value.subGroupA...` or `value: field.subGroupA...`? 3) `getContentColumnType()` was set to `TYPE_STRING`; is that different from `TYPE_TEXT`? Thanks again!!!!  
> **username_1**: @Sandwich 1) Yeah, in general the protected one is recommended, but either one will work. I’ll PR pluginfactory. 2) `value` 3) yeah `string` will give you a `VARCHAR` column; `TEXT` is better for storing JSON.  
> **username_1**: @Sandwich Just updated the example with namespaced input IDs as well. Forgot you’ll want to do that to ensure you don’t get conflicts if the same field type is used multiple times within the same field layout.  
> **username_0**: Ok, that got things working! I think the main thing left is to figure out how to get `{{ entry.fieldHandle.subGroupA` to custom-output the same as `{{ entry.fieldHandle.subGroupA.text ~ entry.fieldHandle.subGroupA.dropdown }}`. I haven't done any searching yet on the subject (although I'm not quite sure what to search _for_); could you point me in the right direction?  
> **username_1**: @Sandwich That would be a bit more complicated; you would need to start normalizing your field value into nested objects using custom classes in `normalizeValue()`. See Craft’s built-in Color field type + the ColorData model for a relatively simple example.  
> **username_0**: Ok, that's certainly pointed me in the right direction! I have the basic "sub" functions working - `entry.fieldHandle.subGroupA` is spitting out what I want it to. It seems like that comes at the cost of being able to reference field values by their data structure, though (eg. `entry.fieldHandle.subGroupA.dropdown`). Is there any way to have my cake and eat it too? ;)  
> **username_1**: @Sandwich If the subgroups are represented via instantiated class(es), you can add a `__toString()` method to it to determine what should be output when the object itself is output “as a string”, e.g. `entry.fieldHandle.subGroupA`.  
> **username_0**: Ahh, so however I would want multiple "levels" of output variables, I'd need everything down to the next-to-last level to be a class (assuming the straight-up data hierarchy didn't suffice because things needed massaging before output)?  

---

