This is what I did to create a custom option type in Magento 2. For the new option type, I retained using the sku field but renamed it.
app/code/Testvendor/CustomOptions/etc/di.xml
<?xml version="1.0"?><config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions" type="Testvendor\CustomOptions\Ui\DataProvider\Catalog\Product\Form\Modifier\CustomOptions" /></config>
app/code/Testvendor/CustomOptions/etc/product_options.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_options.xsd">
<option name="testvendor" label="Test Vendor" renderer="Testvendor\CustomOptions\Block\Adminhtml\Catalog\Product\Edit\Tab\Options\Type\Testvendor">
<inputType name="testoption" label="Test Option" />
</option>
app/code/Testvendor/CustomOptions/Ui/DataProvider/Catalog/Product/Form/Modifier/CustomOptions.php
<?php
namespace Testvendor\CustomOptions\Ui\DataProvider\Catalog\Product\Form\Modifier;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Model\ProductOptions\ConfigInterface;
use Magento\Catalog\Model\Config\Source\Product\Options\Price as ProductOptionsPrice;
use Magento\Framework\UrlInterface;
use Magento\Framework\Stdlib\ArrayManager;
use Magento\Ui\Component\Modal;
use Magento\Ui\Component\Container;
use Magento\Ui\Component\DynamicRows;
use Magento\Ui\Component\Form\Fieldset;
use Magento\Ui\Component\Form\Field;
use Magento\Ui\Component\Form\Element\Input;
use Magento\Ui\Component\Form\Element\Select;
use Magento\Ui\Component\Form\Element\Checkbox;
use Magento\Ui\Component\Form\Element\ActionDelete;
use Magento\Ui\Component\Form\Element\DataType\Text;
use Magento\Ui\Component\Form\Element\DataType\Number;
use Magento\Framework\Locale\CurrencyInterface;
class CustomOptions extends \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions
{
const FIELD_TESTOPTION_NAME = 'testoption';
protected function getStaticTypeContainerConfig($sortOrder)
{
return [
'arguments' => [
'data' => [
'config' => [
'componentType' => Container::NAME,
'formElement' => Container::NAME,
'component' => 'Magento_Ui/js/form/components/group',
'breakLine' => false,
'showLabel' => false,
'additionalClasses' => 'admin__field-group-columns admin__control-group-equal',
'sortOrder' => $sortOrder,
],
],
],
'children' => [
static::FIELD_PRICE_NAME => $this->getPriceFieldConfig(10),
static::FIELD_PRICE_TYPE_NAME => $this->getPriceTypeFieldConfig(20),
static::FIELD_SKU_NAME => $this->getSkuFieldConfig(30),
static::FIELD_TESTOPTION_NAME => $this->getTestoptionFieldConfig(30),
static::FIELD_MAX_CHARACTERS_NAME => $this->getMaxCharactersFieldConfig(40),
static::FIELD_FILE_EXTENSION_NAME => $this->getFileExtensionFieldConfig(50),
static::FIELD_IMAGE_SIZE_X_NAME => $this->getImageSizeXFieldConfig(60),
static::FIELD_IMAGE_SIZE_Y_NAME => $this->getImageSizeYFieldConfig(70)
]
];
}
/**
* Get config for grid for "select" types
*
* @param int $sortOrder
* @return array
*/
protected function getSelectTypeGridConfig($sortOrder)
{
return [
'arguments' => [
'data' => [
'config' => [
'addButtonLabel' => __('Add Value'),
'componentType' => DynamicRows::NAME,
'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows',
'additionalClasses' => 'admin__field-wide',
'deleteProperty' => static::FIELD_IS_DELETE,
'deleteValue' => '1',
'renderDefaultRecord' => false,
'sortOrder' => $sortOrder,
],
],
],
'children' => [
'record' => [
'arguments' => [
'data' => [
'config' => [
'componentType' => Container::NAME,
'component' => 'Magento_Ui/js/dynamic-rows/record',
'positionProvider' => static::FIELD_SORT_ORDER_NAME,
'isTemplate' => true,
'is_collection' => true,
],
],
],
'children' => [
static::FIELD_TITLE_NAME => $this->getTitleFieldConfig(10),
static::FIELD_PRICE_NAME => $this->getPriceFieldConfig(20),
static::FIELD_PRICE_TYPE_NAME => $this->getPriceTypeFieldConfig(30, ['fit' => true]),
static::FIELD_SKU_NAME => $this->getSkuFieldConfig(40),
static::FIELD_SORT_ORDER_NAME => $this->getPositionFieldConfig(50),
static::FIELD_IS_DELETE => $this->getIsDeleteFieldConfig(60)
]
]
]
];
}
protected function getTypeFieldConfig($sortOrder)
{
return [
'arguments' => [
'data' => [
'config' => [
'label' => __('Option Type'),
'componentType' => Field::NAME,
'formElement' => Select::NAME,
'component' => 'Magento_Catalog/js/custom-options-type',
'elementTmpl' => 'ui/grid/filters/elements/ui-select',
'selectType' => 'optgroup',
'dataScope' => static::FIELD_TYPE_NAME,
'dataType' => Text::NAME,
'sortOrder' => $sortOrder,
'options' => $this->getProductOptionTypes(),
'disableLabel' => true,
'multiple' => false,
'selectedPlaceholders' => [
'defaultPlaceholder' => __('-- Please select --'),
],
'validation' => [
'required-entry' => true
],
'groupsConfig' => [
'text' => [
'values' => ['field', 'area'],
'indexes' => [
static::CONTAINER_TYPE_STATIC_NAME,
static::FIELD_PRICE_NAME,
static::FIELD_PRICE_TYPE_NAME,
static::FIELD_SKU_NAME,
static::FIELD_MAX_CHARACTERS_NAME
]
],
'Testvendor' => [
'values' => ['testoption'],
'indexes' => [
static::CONTAINER_TYPE_STATIC_NAME,
#static::FIELD_SKU_NAME,
static::FIELD_TESTOPTION_NAME,
]
],
'file' => [
'values' => ['file'],
'indexes' => [
static::CONTAINER_TYPE_STATIC_NAME,
static::FIELD_PRICE_NAME,
static::FIELD_PRICE_TYPE_NAME,
static::FIELD_SKU_NAME,
static::FIELD_FILE_EXTENSION_NAME,
static::FIELD_IMAGE_SIZE_X_NAME,
static::FIELD_IMAGE_SIZE_Y_NAME
]
],
'select' => [
'values' => ['drop_down', 'radio', 'checkbox', 'multiple'],
'indexes' => [
static::GRID_TYPE_SELECT_NAME
]
],
'data' => [
'values' => ['date', 'date_time', 'time'],
'indexes' => [
static::CONTAINER_TYPE_STATIC_NAME,
static::FIELD_PRICE_NAME,
static::FIELD_PRICE_TYPE_NAME,
static::FIELD_SKU_NAME
]
]
],
],
],
],
];
}
protected function getTestoptionFieldConfig($sortOrder)
{
return [
'arguments' => [
'data' => [
'config' => [
'label' => __('Test Option'),
'componentType' => Field::NAME,
'formElement' => Input::NAME,
'dataScope' => static::FIELD_SKU_NAME,
'dataType' => Text::NAME,
'sortOrder' => $sortOrder,
],
],
],
];
}
}
After this... I went in the bin directory and ran
./magento setup:upgrade
./magento setup:di:compile
./magento cache:flush
./magento cache:clean
Best Answer
https://community.magento.com/t5/Building-Extensions/Custom-Product-Type-which-extends-Grouped-Product-Type/td-p/16251
The Associated Products tab is added via XML with a handle that is specific to that product type. You will need to add a similar update for your product type in your own admin layout xml file
app/design/adminhtml/default/default/layout/catalog.xml
If you're trying to copy grouped pretty much exactly using the grouped handle as an update like below may work best:
Credit to James Anelay