Extending Core Types: Using extend type Instead of Duplicating Types
Extending Core Types: Using extend type Instead of Duplicating Types
~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
So far, every custom query has declared a brand-new type. Just as common - in practice, even more common - is the reverse case: an existing core type such as Product, Category, or CustomerOutput should get an extra field of your own. That's what extend type is for.
Why not just create a new type?
A brand-new, custom type MironsoftProductExtra with its own field in the query would technically work, but it would force every client to fire a second, completely separate query for data that logically belongs together - exactly the underfetching problem from chapter 1 that GraphQL is supposed to solve. extend type Product { ... } instead attaches the custom field directly to the existing type, so it can be queried in the same request as every other product field.
The extend type syntax
extend type Product {
mironsoft_badge_text: String
@resolver(class: "Mironsoft\\GraphqlDemo\\Model\\Resolver\\Product\\BadgeText")
@doc(description: "Custom marketing badge text shown on the product tile")
}Important: Product is itself already an extension of ProductInterface from Magento_CatalogGraphQl - Magento's own product type comes into being through the exact same pattern used here. Without the module dependency on Magento_CatalogGraphQl set in chapter 4, extend type Product would fail silently, because the base type wouldn't exist yet at load time.
Resolvers for extended fields
<?php
declare(strict_types=1);
namespace Mironsoft\GraphqlDemo\Model\Resolver\Product;
use Magento\Catalog\Model\Product;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
/**
* Resolves the custom mironsoft_badge_text field on the Product type.
*/
class BadgeText implements ResolverInterface
{
/**
* Derives a marketing badge from the resolved product model.
*
* @param Field $field Resolved GraphQL field configuration
* @param mixed $context Resolver context
* @param ResolveInfo $info GraphQL resolve tree info
* @param array|null $value Parent Product resolver's value
* @param array|null $args Arguments passed to this field
* @return string|null
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
?array $value = null,
?array $args = null
): ?string {
/** @var Product|null $product */
$product = $value['model'] ?? null;
if ($product === null || !$product->getData('is_new_arrival')) {
return null;
}
return 'New arrival';
}
}The key difference from chapters 5 and 6: $value is now not empty. It contains whatever the parent resolver (Magento\CatalogGraphQl\Model\Resolver\Products) supplied for the product currently being processed - including the key model, under which many core resolvers make the full product model available to downstream field resolvers. Chapter 10 covers $value in detail.
Achtung: The key under which a parent object sits in the value array (model for products, different keys for other core types) isn't a fixed convention of the GraphQL specification - it's a decision made by the respective core module. Before writing your first custom field resolver on a core type, it's worth looking at the resolver that fills the parent field to find the correct array key.
Querying the extended field
query {
products(filter: { sku: { eq: "24-MB01" } }) {
items {
name
mironsoft_badge_text
}
}
}To the client, mironsoft_badge_text is indistinguishable from a native core field - that's exactly the point of extend type: custom extensions blend seamlessly into existing types.
Tipp: extend type works for any type present in the merged schema - including custom-defined ones. The events project in block 4 later uses this itself, to extend its own Event type with further fields without touching the original type declaration.
Chapter 9 dives deeper into this pattern on three concrete, particularly commonly extended core types: product, category, and customer.