Magento GraphQL – How to Extend schema.graphqls and inject custom fields

Magento community is now implementing graphql support with all its modules day by day. You will find a separate graphql module to Magento’s default module. e.g. there is a module module-catalog-graph-ql to handle graphql details for magento-catalog module.

The “graphql-modules” has all their graphql request definition and their resolver class stored in schema.graphqls file in etc folder. This file contains all the information of the graphql requests, their data fields and the class which will serve data for each field. Lets take example for the graphql handling product detail requests.

Go to vendor/magento/module-catalog-graph-ql/etc/schema.graphqls.
Here you will see configuration which handles Product queries at ProductInterface interface.

interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "The ProductInterface contains attributes that are common to all types of products. Note that descriptions may not be available for custom and EAV attributes.") {
    id: Int @doc(description: "The ID number assigned to the product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId")
    name: String @doc(description: "The product name. Customers use this name to identify the product.")
    sku: String @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer")
    description: ComplexTextValue @doc(description: "Detailed information about the product. The value can include simple HTML tags.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductComplexTextAttribute")
    short_description: ComplexTextValue @doc(description: "A short description of the product. Its use depends on the theme.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductComplexTextAttribute")
    special_price: Float @doc(description: "The discounted price of the product")
    special_from_date: String @doc(description: "The beginning date that a product has a special price")
    special_to_date: String @doc(description: "The end date that a product has a special price")
    attribute_set_id: Int @doc(description: "The attribute set assigned to the product")
    meta_title: String @doc(description: "A string that is displayed in the title bar and tab of the browser and in search results lists")
    meta_keyword: String @doc(description: "A comma-separated list of keywords that are visible only to search engines")
    meta_description: String @doc(description: "A brief overview of the product for search results listings, maximum 255 characters")
    image: ProductImage @doc(description: "The relative path to the main image on the product page") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage")
    small_image: ProductImage @doc(description: "The relative path to the small image, which is used on catalog pages") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage")
    thumbnail: ProductImage @doc(description: "The relative path to the product's thumbnail image") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductImage")
    new_from_date: String @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo")
    new_to_date: String @doc(description: "The end date for new product listings") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo")
    tier_price: Float @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached")
    options_container: String @doc(description: "If the product has multiple options, determines where they appear on the product page")
    created_at: String @doc(description: "Timestamp indicating when the product was created")
    updated_at: String @doc(description: "Timestamp indicating when the product was updated")
    country_of_manufacture: String @doc(description: "The product's country of origin")
    type_id: String @doc(description: "One of simple, virtual, bundle, downloadable, grouped, or configurable")
    websites: [Website] @doc(description: "An array of websites in which the product is available") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Websites")
    product_links: [ProductLinksInterface] @doc(description: "An array of ProductLinks objects") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductLinks")
    media_gallery_entries: [MediaGalleryEntry] @doc(description: "An array of MediaGalleryEntry objects") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGalleryEntries")
    tier_prices: [ProductTierPrices] @doc(description: "An array of ProductTierPrices objects") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\TierPrices")
    price: ProductPrices @doc(description: "A ProductPrices object, indicating the price of an item") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Price")
    gift_message_available: String @doc(description: "Indicates whether a gift message is available")
    manufacturer: Int @doc(description: "A number representing the product's manufacturer")
    categories: [CategoryInterface] @doc(description: "The categories assigned to a product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories")
    canonical_url: String @doc(description: "Canonical URL") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CanonicalUrl")
}

You can see here all the fields product details graphql can ask for and can get data in pwa-studio. But what if you want to get more data than what is provided here. Suppose, I want to add a new field to this interface which will give me some data like brand name of my product, what are the steps you can follow to achieve it.? How you can extend schema.graphqls file in your own module? In this post, I will explain how to achieve this.

Lets take an example of adding brand name to my product details graphql which is served by ProductInterface object. We will be using manufacturer attribute to be our brand attribute and will get its value for each product.

In above interface, you can see there is a manufacturer attribute already available. You may also notice that the type for this attribute is added as Int But the issue with it is that it will give you the id of your manufacturer which is of no use to our users.

We require the name for the manufacturer and to accomplish this, we can create a new module which will extend Magento’s default ProductInterface and will inject our brand field to get brand name.

Lets create a new module with name JS_CatalogGraphQl
You will need to add sequence in our module’s module.xml file like below.

<module name="JS_CatalogGraphQl" setup_version="1.0.0">
	<sequence>
		<module name="Magento_GraphQl"/>
		<module name="Magento_CatalogGraphQl"/>
	</sequence>
</module>

Now create a schema.graphqls file in our etc directory.

interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "The ProductInterface contains attributes that are common to all types of products. Note that descriptions may not be available for custom and EAV attributes.") {
    brand: String @doc(description: "Brand Name based on manufacturer attribute") @resolver(class: "JS\\CatalogGraphQl\\Model\\Resolver\\Product\\Brand")
}

Here, we are using Magento’s default ProductInterface and adding one more field brand and its resolver class details.

Now create our resolver class at JS/CatalogGraphQl/Model/Resolver/Product/Brand.php

<?php
declare(strict_types=1);

namespace JS\CatalogGraphQl\Model\Resolver\Product;

use Magento\Catalog\Model\Product;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;

/**
 * Resolve data for product Brand name
 */
class Brand implements ResolverInterface
{
    /**
     * @inheritdoc
     */
    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
    ) {
        if (!isset($value['model'])) {
            throw new LocalizedException(__('"model" value should be specified'));
        }

        /* @var $product Product */
        $product = $value['model'];
        $optionText = '';
        $attr = $product->getResource()->getAttribute('manufacturer');
        if ($attr->usesSource()) {
            $optionText = $attr->getSource()->getOptionText($product->getManufacturer());
        }
        
        return ($optionText) ? : '';
    }
}

This will use product’s manufacturer attribute value and return its text value. You can use this field now in your graphql query file like below.

query productDetail($urlKey: String = "carmina-earrings") {
    productDetail: products(filter: { url_key: { eq: $urlKey } }) {
        items {
            sku
            name
            manufacturer
            brand
        }
    }
}

Here, do not remove default manufacturer field as it will be used to get manufacturer id for current product. One more thing to note here is as we have injected brand field to product interface, it can be fetched on product listing page and search results page because Magento uses this interface only on all places.

Now, you will be able to get brand name with your product details.

Leave a comment