Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Scalar and Complex Types, Defining Custom Input Types

Scalar and Complex Types, Defining Custom Input Types

~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

After the simple string query from chapter 5, it's now time for complex types - custom object types with multiple fields - and input types, which accept structured arguments instead of loose scalars.

The built-in scalar types

GraphQL ships five built-in scalar types that every schema builds on: Int, Float, String, Boolean, and ID (a string semantically marked as a unique identifier). Magento itself doesn't define additional custom scalars for things like dates - timestamps are transferred as formatted String values, not as a dedicated DateTime scalar. The events project starting in block 4 follows this same convention.

Declaring a custom object type

An example in the warm-up module: a query shopContact that returns a structured object with multiple fields instead of a single string.

app/code/Mironsoft/GraphqlDemo/etc/schema.graphqls
type Query {
    shopContact: ShopContact
        @resolver(class: "Mironsoft\\GraphqlDemo\\Model\\Resolver\\ShopContact")
        @doc(description: "Returns the shop's public contact details")
}

type ShopContact @doc(description: "Public contact details of the shop") {
    company_name: String
    email: String
    phone: String
    support_hours: [String]
}

type ShopContact { ... } is a custom, named GraphQL type - not a PHP interface, just a pure schema declaration. [String] declares a list of strings (chapter 7 covers list semantics in detail).

app/code/Mironsoft/GraphqlDemo/Model/Resolver/ShopContact.php
<?php

declare(strict_types=1);

namespace Mironsoft\GraphqlDemo\Model\Resolver;

use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;

/**
 * Resolves the shopContact query field.
 */
class ShopContact implements ResolverInterface
{
    /**
     * Returns the shop's static contact details as an array matching the
     * ShopContact GraphQL type.
     *
     * @param Field $field Resolved GraphQL field configuration
     * @param mixed $context Resolver context
     * @param ResolveInfo $info GraphQL resolve tree info
     * @param array|null $value Parent resolver's value, unused here
     * @param array|null $args Arguments passed to the shopContact field
     * @return array<string, mixed>
     */
    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        ?array $value = null,
        ?array $args = null
    ): array {
        return [
            'company_name' => 'Mironsoft',
            'email' => 'kontakt@mironsoft.de',
            'phone' => '+49 30 1234567',
            'support_hours' => ['Mon-Fri 9am-5pm'],
        ];
    }
}

The key point: the resolver returns an associative array whose keys match the field names from type ShopContact exactly. For each individual field (company_name, email, ...), the default resolver mentioned in chapter 2 then kicks in automatically - custom resolver classes per field are only needed when a field requires its own logic.

Custom input types for structured arguments

Just like object types exist for return values, there are input types for arguments - declared with input instead of type. They may only consist of scalars, enums, and further input types, never regular object types.

input ShopContactFilterInput {
    department: String
    language: String
}

type Query {
    shopContact(filter: ShopContactFilterInput): ShopContact
        @resolver(class: "Mironsoft\\GraphqlDemo\\Model\\Resolver\\ShopContact")
}

In the resolver, such an input type ends up as a nested array in $args['filter'] - $args['filter']['language'] ?? null then reads the individual value. This pattern - one input type as the single structured argument instead of many loose scalar arguments - is standard practice in Magento's own mutations (chapter 16) and is adopted for filtering and sorting in the events project starting in chapter 14.

Tipp: When choosing between "several loose arguments" and "one input type", a simple rule of thumb applies: once you have three or more related arguments, or as soon as a field should be reusable (e.g. in both a mutation and a query), a dedicated input type pays off.

Chapter 7 covers how to correctly model required fields (!), nullable fields, and lists in the schema - a topic deliberately simplified here for ShopContact.