Developers
Custom Fields
Register custom field transformers to teach CoPilot how to read and write third-party field types.
Field transformers tell the agent how to describe, read, and write a field type. CoPilot covers all native Craft fields. For third-party fields, register your own transformer via events.
Example
A transformer for Formie form selection fields. Combined with a custom tool like searchFormieForms, the agent can find a form by name and set it on an entry.
modules/mymodule/transformers/FormieFieldTransformer.php
<?php
namespace modules\mymodule\transformers;
use craft\base\Element;
use craft\base\FieldInterface;
use samuelreichor\coPilot\helpers\PluginHelper;
use samuelreichor\coPilot\transformers\fields\FieldTransformerInterface;
class FormieFieldTransformer implements FieldTransformerInterface
{
public function getSupportedFieldClasses(): array
{
return [];
}
public function matchesField(FieldInterface $field): ?bool
{
if (!PluginHelper::isPluginInstalledAndEnabled('formie')) {
return null;
}
return get_class($field) === 'verbb\formie\fields\Forms' ? true : null;
}
public function describeField(
FieldInterface $field,
array $fieldInfo,
): array {
$fieldInfo['valueFormat'] = 'form handle (string)';
$fieldInfo['hint'] = 'Use searchFormieForms to find the correct handle.';
return $fieldInfo;
}
public function serializeValue(
FieldInterface $field,
mixed $value,
int $depth,
): mixed {
if ($value === null) {
return null;
}
// Formie returns a Form element query
$form = is_object($value) && method_exists($value, 'one')
? $value->one()
: $value;
if ($form === null) {
return null;
}
return [
'id' => $form->id,
'title' => $form->title,
'handle' => $form->handle,
];
}
public function normalizeValue(
FieldInterface $field,
mixed $value,
?Element $element = null,
): mixed {
// Accept a handle string and resolve it to a form ID
if (is_string($value)) {
$form = \verbb\formie\elements\Form::find()
->handle($value)
->one();
return $form ? [$form->id] : null;
}
return null;
}
}
FieldTransformerInterface
Matching
Transformers are checked in order. For each field, the registry calls matchesField() first. If it returns null, class matching via getSupportedFieldClasses() is used as fallback.
Event-registered transformers are checked before built-in ones, so you can override default behavior for any field type.