Version 5 supported

Extending the schema

Adding a custom operation
Add a new operation for model types
Adding middleware
Add middleware to to extend query execution
Adding a custom model
Add a new class-backed type beyond DataObject
The global schema
How to push modifications to every schema in the project

Adding a custom operation

By default, we get basic operations for our models, like read, create, update, and delete, but we can add to this list by creating an implementation of OperationProvider and registering it.

Let's build a new operation that duplicates DataObjects.

namespace App\GraphQL;

use SilverStripe\GraphQL\Schema\Field\ModelMutation;
use SilverStripe\GraphQL\Schema\Interfaces\ModelOperation;
use SilverStripe\GraphQL\Schema\Interfaces\OperationCreator;
use SilverStripe\GraphQL\Schema\Interfaces\SchemaModelInterface;
use SilverStripe\GraphQL\Schema\SchemaConfig;

class DuplicateCreator implements OperationCreator
{
    public function createOperation(
        SchemaModelInterface $model,
        string $typeName,
        array $config = []
    ): ?ModelOperation {
        $mutationName = 'duplicate' . ucfirst(SchemaConfig::pluralise($typeName));

        return ModelMutation::create($model, $mutationName)
            ->setType($typeName)
            ->addArg('id', 'ID!')
            ->setDefaultResolver([static::class, 'resolve'])
            ->setResolverContext([
                'dataClass' => $model->getSourceClass(),
            ]);
    }
}

We add resolver context to the mutation because we need to know what class to duplicate, but we need to make sure we still have a static function.

The signature for resolvers with context is:

namespace App\GraphQL\Resolvers;

use Closure;

class MyResolver
{
    public static function resolve(array $context): Closure
    {
        // ...
    }
}

We use the context to pass to a function that we'll create dynamically. Let's add that now.

namespace App\GraphQL;

use Closure;
// ...
use SilverStripe\ORM\DataObject;

class DuplicateCreator implements OperationCreator
{
    // ...

    public static function resolve(array $context = []): Closure
    {
        $dataClass = $context['dataClass'] ?? null;
        return function ($obj, array $args) use ($dataClass) {
            if (!$dataClass) {
                return null;
            }
            return DataObject::get_by_id($dataClass, $args['id'])
                ->duplicate();
        };
    }
}

Now, just add the operation to the DataObjectModel configuration to make it available to all DataObject types.

# app/_graphql/config.yml
modelConfig:
  DataObject:
    operations:
      duplicate:
        class: 'App\GraphQL\DuplicateCreator'

And use it:

# app/_graphql/models.yml
App\Model\MyDataObject:
  fields: '*'
  operations:
    read: true
    duplicate: true

Further reading

Adding a custom operation
Add a new operation for model types
Adding middleware
Add middleware to to extend query execution
Adding a custom model
Add a new class-backed type beyond DataObject
The global schema
How to push modifications to every schema in the project