Working with DataObjects

DataObject query plugins
Learn about some of the useful goodies that come pre-packaged with DataObject queries
DataObject operation permissions
A look at how permissions work for DataObject queries and mutations
Versioned content
A guide on how DataObjects with the Versioned extension behave in GraphQL schemas
DataObject inheritance
Learn how inheritance is handled in DataObject types
Property mapping and dot syntax
Learn how to customise field names, use dot syntax, and use aggregate functions
Nested type definitions
Define dependent types inline with a parent type
The DataObject model type
An overview of how the DataObject model can influence the creation of types, queries, and mutations
You are viewing docs for a pre-release version of silverstripe/graphql (4.x). Help us improve it by joining #graphql on the Community Slack, and report any issues at github.com/silverstripe/silverstripe-graphql. Docs for the current stable version (3.x) can be found here

DataObject inheritance

The inheritance pattern used in the ORM is a tricky thing to navigate in a GraphQL API, mostly owing to the fact that there is no concept of inheritance in GraphQL types. The main tools we have at our disposal are interfaces and unions to deal with this type of architecture, but in practise, it quickly becomes cumbersome. For instance, just adding a subclass to a DataObject can force the return type to change from a simple list of types to a union of multiple types, and this would break frontend code.

While more conventional, unions and interfaces introduce more complexity, and given how much we rely on inheritance in Silverstripe CMS, particularly with SiteTree, inheritance in GraphQL is handled in a less conventional but more ergonomic way using a plugin called inheritance.

Introducing pseudo-unions

Let's take a simple example. Imagine we have this design:

> SiteTree (fields: title, content)
  > Page (fields: pageField)
    > NewsPage (fields: newsPageField)
    > Contact Page (fields: contactPageField)

Now, let's expose Page to graphql:

app/_graphql/models.yml

Page:
  fields:
    title: true
    content: true
    pageField: true
  operations: '*'
NewsPage:
  fields:
    newsPageField: true

Here's how we can query the inherited fields:

query readPages {
  nodes {
    title
    content
    pageField
    _extend {
      NewsPage {
        newsPageField
      }
    }
  }
}

The _extend field is semantically aligned with is PHP counterpart -- it's an object whose fields are the names of all the types that are descendants of the parent type. Each of those objects contains all the fields on that type, both inherited and native.

The _extend field is only available on base classes, e.g. Page in the example above.

Implicit exposure

By exposing Page, we implicitly expose all of its ancestors and all of its descendants. Adding Page to our schema implies that we also want its parent SiteTree in the schema (after all, that's where most of its fields come from), but we also need to be mindful that queries for page will return descendants of Page, as well.

But these types are implicitly added to the schema, what are their fields?

The answer is only the fields you've already opted into. Parent classes will apply the fields exposed by their descendants, and descendant classes will only expose their ancestors' exposed fields. If you are opting into all fields on a model (fields: "*"), this only applies to the model itself, not its subclasses.

In our case, we've exposed:

  • title (on SiteTree)
  • content (on SiteTree)
  • pageField (on Page)
  • newsPageField (on NewsPage)

The Page type will contain the following fields:

  • id (required for all DataObject types)
  • title
  • content
  • pageField

And the NewsPage type would contain the following fields:

  • newsPageField
Operations are not implicitly exposed. If you add a read operation to SiteTree, you will not get one for NewsPage and ContactPage, etc. You have to opt in to those.

Pseudo-unions fields are de-duped

To keep things tidy, the pseudo unions in the _extend field remove any fields that are already in the parent.

query readPages {
  nodes {
    title
    content
    _extend {
      NewsPage {
         title <---- Doesn't exist
         newsPageField
      }
    }
  }
}

Further reading

DataObject query plugins
Learn about some of the useful goodies that come pre-packaged with DataObject queries
DataObject operation permissions
A look at how permissions work for DataObject queries and mutations
Versioned content
A guide on how DataObjects with the Versioned extension behave in GraphQL schemas
DataObject inheritance
Learn how inheritance is handled in DataObject types
Property mapping and dot syntax
Learn how to customise field names, use dot syntax, and use aggregate functions
Nested type definitions
Define dependent types inline with a parent type
The DataObject model type
An overview of how the DataObject model can influence the creation of types, queries, and mutations