There are three types of operations that general specification GraphQL models:
- Query – a read‐only fetch.
- Mutation – a write followed by a fetch.
- Subscription – a long‐lived request that fetches data in response to source events.
MIP for now fully offers query and mutation operations. In the case of the query operation, there is no need to explicitly write specification keyword at the start of the document - it is always implied that you are using a query by default.
Objects are primarily composed of fields. An object can "select" information that it has in its scope. Because of that, we can call information that it "selects" as the Selection set. The most basic GraphQL document selection set would consist of the Object name and then the name of the field which value we want to retrieve.
{
Account(offset:1 first:2) {
Id
Name
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nQQAR",
"Name": "Burlington Textiles Corp of America",
"AccountNumber": "CD656092"
},
{
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc.",
"AccountNumber": "CC213425"
}
]
}
}
|
A selection set is primarily composed of fields. A field describes one discrete piece of information available to retrieve within a selection set.
Using fields in the GraphQL is exactly the same as using them in the Salesforce. Every field you can access through Salesforce, MIP can access through GraphQL. If the user has restricted access to some data via the usual means of obtaining it (SOQL or simply viewing records) MIP will restrict access to that data in the exact same fashion when retrieving the result. Some fields describe complex data or relationships to other data. In order to further explore this data, a field itself may contain a selection set, allowing for deeply nested requests. All GraphQL queries must specify their selections down to fields that return scalar values to ensure an unambiguously shaped response.
When trying to access the fields of the Child object,the parent-child relationship name has to be correctly and unambiguously specified.
{
Account(offset:1 first:2) {
Id
Name
AccountNumber
Contacts {
Id
FirstName
LastName
}
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Contacts": [
{
"AccountId": "0013E00001Dq4nQQAR",
"Id": "0033E00001FIO5TQAX",
"FirstName": "Jack",
"LastName": "Rogers"
}
],
"Id": "0013E00001Dq4nQQAR",
"Name": "Burlington Textiles Corp of America",
"AccountNumber": "CD656092"
},
{
"Contacts": [
{
"AccountId": "0013E00001Dq4nRQAR",
"Id": "0033E00001FIO5UQAX",
"FirstName": "Pat",
"LastName": "Stumuller"
}
],
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc.",
"AccountNumber": "CC213425"
}
]
}
}
|
Let us break things down a bit as to why we need to put Contacts instead of Contact when building queries.
Contact has a lookup field for Account. The Child Relationship Name for the lookup field on Contact is "Contacts". Therefore, you would use "Contacts" in Account queries to refer to Contact records whose Account field references a given Account. If necessary, Salesforce allows the name to be changed but accepting those default values is almost always perfectly fine.
If the query is run with the non-existent field name, the error with subType SalesforceQueryError will be shown with the message that the field does not exist. If the parent-child relationship name is not correct, the error subType ObjectNameError, with the message that parent-child relationship name is not found will occur. The first error is documented in the Error Logging, and both of those errors are documented here and here respectively.
¶ First and Offset
Keywords First and Offset have already been mentioned in the queries above and they should be pretty self-explanatory of the jobs they do.
In the following example, we build a query that returns Account records.
{
Account {
Id
Name
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4naQAB",
"Name": "sForce"
},
{
"Id": "0013E00001Dq4nPQAR",
"Name": "Edge Communications",
"AccountNumber": "CD451796"
},
{
"Id": "0013E00001Dq4nQQAR",
"Name": "Burlington Textiles Corp of America",
"AccountNumber": "CD656092"
},
{
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc.",
"AccountNumber": "CC213425"
},
{
"Id": "0013E00001Dq4nSQAR",
"Name": "Dickenson plc",
"AccountNumber": "CC634267"
},
{
"Id": "0013E00001Dq4nTQAR",
"Name": "Grand Hotels & Resorts Ltd",
"AccountNumber": "CD439877"
},
{
"Id": "0013E00001Dq4nUQAR",
"Name": "United Oil & Gas Corp.",
"AccountNumber": "CD355118"
},
{
"Id": "0013E00001Dq4nVQAR",
"Name": "Express Logistics and Transport",
"AccountNumber": "CC947211"
},
{
"Id": "0013E00001Dq4nWQAR",
"Name": "University of Arizona",
"AccountNumber": "CD736025"
},
{
"Id": "0013E00001Dq4nXQAR",
"Name": "United Oil & Gas, UK",
"AccountNumber": "CD355119-A"
},
{
"Id": "0013E00001Dq4nYQAR",
"Name": "United Oil & Gas, Singapore",
"AccountNumber": "CD355120-B"
},
{
"Id": "0013E00001Dq4nZQAR",
"Name": "GenePoint",
"AccountNumber": "CC978213"
}
]
}
}
|
In case we don't want every possible record to be shown, we can use a combination of first and offset to chose any group of them that we require. Same use as we would in SOQL with Salesforce. We can use them individually as well.
Example: return only the first 2 records:
{
Account(first:2) {
Id
Name
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4naQAB",
"Name": "sForce"
},
{
"Id": "0013E00001Dq4nPQAR",
"Name": "Edge Communications",
"AccountNumber": "CD451796"
}
]
}
}
|
And in the case that we want Burlington Textiles Corp of America record, we would manipulate first and offset so that we retrieve exactly that:
{
Account(offset:1 first:1) {
Id
Name
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nQQAR",
"Name": "Burlington Textiles Corp of America",
"AccountNumber": "CD656092"
}
]
}
}
|
It does not matter in which order first
and offset
is put. When parsing the GraphQL query, MIP will always resolves order correctly and construct a Salesforce query with valid order in return.
By default, the key in the MIPs JSON response will always have the same name as the one in the query itself, which is intuitive and self-explanatory. However, you can define a custom name by specifying an alias. Alias specified before the Object
or the field
name will be the one used in the response.
An alias can be specified by putting it in front of the Object or field name, separated with a colon ":"
{
AliasAccount:Account(offset:1 first:1) {
Id
AliasName: Name
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"AliasAccount": [
{
"Id": "0013E00001Dq4nQQAR",
"AliasName": "Burlington Textiles Corp of America",
"AccountNumber": "CD656092"
}
]
}
}
|
Main reason for changing the object and field names can simply be for introducing simplicity in resolving JSON response. We humans prefer to manage names such as Price
, instead of PackagePrefix__PriceWithIncludedTax__c
. And maybe aliens do as well?
We use arguments in GraphQL as a tool to narrow our search down to get precisely what we require. The concept is again simple and intuitive. We construct arguments by using FieldAPIName:Value
pairs.
Arguments are always put in brackets by the Object name that we are filtering our search through. The example below is requesting Id
and Name
of every Contact
record that has "Sean" as the FirstName
.
{
Contact(FirstName:"Sean") {
Id
Name
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Contact": [
{
"Id": "0032o00002ZKqdJAAT",
"Name": "Sean Forbes",
"RecordURL": "https://mmint.eu25.visual.force.com/0032o00002ZKqdJAAT"
}
]
}
}
|
We can think of arguments as WHERE
clause in SOQL. This would directly translate to SELECT Id, Name FROM Contact WHERE FirstName="Sean"
. We can also add more arguments that can be divided by some of the dividers such as comma or newline.
Every argument further listed would be considered as AND
operator by default. So this query:
{
Contact(FirstName:"Sean",
LastName:"Forbes") {
Id
Name
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Contact": [
{
"Id": "0033E00001FIO5SQAX",
"Name": "Sean Forbes"
}
]
}
}
|
would translate to SELECT Id, Name FROM Contact WHERE FirstName="Sean" AND LastName="Forbes"
.
For trying to write more complex combination of ORs
and ANDs
, please reference GraphQL filters.
A GraphQL query can be parameterized with variables, maximizing query reuse, and avoiding costly string rebuilding at runtime.
Purpose of variables is to replace argument and because of that, they are put in the same place the arguments are, in the brackets just after the Object name. Variables placed in that way have a format of FieldAPIName:$variableName
, where variableName
is a value which has to be defined either in the variables section in the GraphQL Playground or sent with the query itself when executing it through the Apex.
In this example, we want to get the same result as the second query in the arguments example, but with using variables:
{
Contact(FirstName:$var1,
LastName :$var2) {
Id
Name
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Contact": [
{
"Id": "0033E00001FIO5SQAX",
"Name": "Sean Forbes"
}
]
}
}
|
{
"var1":"Sean",
"var2":"Forbes"
}
Remaining task would then just be to define those variables as JSON like in the example above, and copy/paste them in the GraphQL Playground, or deserialize into Map
and execute together with query through the Apex.
Variables can also be used with filters:
{
Account(filter: {OR:[{AccountNumber:$var1},
{AccountNumber:$var2}]}
){
Id
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0011X00000k1G78QAE",
"AccountNumber": "CD451796"
},
{
"Id": "0011X00000k1G79QAE",
"AccountNumber": "CD656092"
}
]
}
}
|
{
"var1":"CD451796",
"var2":"CD656092"
}
or with any other combination thereof:
{
Account(AccountNumber_starts_with: $var1,
filter: {OR:[{Name:$var2},
{Name:$var3},
{Name:$var4}]}
first: $var5
offset:$var5){
Id
Name
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0011X00000k1G7GQAU",
"Name": "United Oil & Gas, UK",
"AccountNumber": "CD355119-A"
}
]
}
}
|
{
"var1":"CD",
"var2":"GenePoint",
"var3":"United Oil & Gas, UK",
"var4":"Edge Communications",
"var5":1
}
Notice that we have used the same variable in both first and offset.
Some fields describe complex data or represent relationships to other data. Because of that, we can say that field itself may contain a selection set, allowing for deeply nested requests. In order to further explore this data, the query must specify their selections down to fields that return primitive values to ensure an unambiguously shaped response.
Take for example a query bellow that consists of Object Account
, its child Object Contact
, and some of the Contacts fields.
{
Account(first:1) {
Name
Contacts {
LastName
FirstName
}
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Contacts": [
{
"AccountId": "0013E00001Dq4naQAB",
"LastName": "Nedaerk",
"FirstName": "Siddartha",
"Id": "0033E00001FIO5jQAH"
},
{
"AccountId": "0013E00001Dq4naQAB",
"LastName": "Llorrac",
"FirstName": "Jake",
"Id": "0033E00001FIO5kQAH"
}
],
"Name": "sForce",
"Id": "0013E00001Dq4naQAB"
}
]
}
}
|
Some may find unclear as to why in the query there has to be wriiten Contacts
, and not Contact
.
This field defines a relationship between this object type and a "parent" object type.
The naming convention for this is standard Salesforce functionality.
The Child Relationship Name is used in SOQL queries on the parent object type to refer to this object type. For example, Contact has a lookup field for Account. The Child Relationship Name for the lookup field on Contact is "Contacts" Therefore, you would use "Contacts" in Account queries to refer to Contact records whose Account field references a given Account. Accepting the default value is safe. If necessary, the name can be changed.
Regardless of what specific record is queried, fields Id will always be added to the result, and RecordURL optionally - by enabling it in the SF custom settings.
The main purpose is to make validating the records returned in the GraphQL more easy.
{
AliasAcc: Account(Id: $accid) {
Name
Contacts {
FirstName
LastName
}
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"AliasAcc": [
{
"Contacts": [
{
"AccountId": "0012o00002RTbgPAAT",
"FirstName": "Edna",
"LastName": "Frank",
"Id": "0032o00002ZKqdYAAT",
"RecordURL": "https://mmint.eu25.visual.force.com/0032o00002ZKqdYAAT"
}
],
"Name": "GenePoint",
"Id": "0012o00002RTbgPAAT",
"RecordURL": "https://mmint.eu25.visual.force.com/0012o00002RTbgPAAT"
}
]
}
}
|
These added fields directly impact field ordering.
If the Id and RecordURL are not used in the query, they will be added last.
If Id for example is explicitly asked for in the query, it will be added in that specific place, respecting the field ordering.
{
AliasAcc: Account(Id: $accid) {
Name
Contacts {
FirstName
Id
LastName
}
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"AliasAcc": [
{
"Contacts": [
{
"AccountId": "0012o00002RTbgPAAT",
"FirstName": "Edna",
"Id": "0032o00002ZKqdYAAT",
"LastName": "Frank",
"RecordURL": "https://mmint.eu25.visual.force.com/0032o00002ZKqdYAAT"
}
],
"Name": "GenePoint",
"Id": "0012o00002RTbgPAAT",
"RecordURL": "https://mmint.eu25.visual.force.com/0012o00002RTbgPAAT"
}
]
}
}
|
In the arguments section, we have put an argument after the Object name so that we can narrow our query to specific selection of records. We can think of it as WHERE
clause in Salesforce.
But to further expand that, it is common to use some combination of logical operators AND, OR to further narrow our query and that is exactly where filter in the GraphQL comes from.
We use filter when we want to translate Salesforce AND
, OR
logical operators into GraphQL compatible query.
Below is the example of the filter usage:
{
Account(filter:{OR:[{Name:"GenePoint"},
{AND:[{Industry:"Education"},
{AccountNumber:"CD736025"}]}]}
){
Id
Name
Industry
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nWQAR",
"Name": "University of Arizona",
"Industry": "Education",
"AccountNumber": "CD736025"
},
{
"Id": "0013E00001Dq4nZQAR",
"Name": "GenePoint",
"Industry": "Biotechnology",
"AccountNumber": "CC978213"
}
]
}
}
|
We will now point to couple of guidlines to successfully build the filter in the query.
- Argument keyword filter is written in round
()
brackets after the object name
- Colon
:
after logical operators AND:
, OR:
is used to tell the MIP that everything after that is a value for that operator
- Square brackets
OR:[]
, AND:[]
are marking begining and end of list that means we will have more than one OR
, AND
operator present inside
- Values of operators (ex. Name:"GenePoing") are always wrapped by curly
{}
brackets
- Operators must consist of at least two of those
Name:Value
pairs
Let's break the filter above to more manageable parts and build it from scratch:
filter : {}
filter : {OR: []}
filter : {OR: [ {},{} ]}
filter : {OR: [ {},{ AND:[]} ] }
filter : {OR: [ {},{ AND:[{},{}]} ] }
filter : {OR:[{Name:"GenePoint"},{AND:[{Industry:"Education"},{AccountNumber:"CD736025"}] }]}
and to further explain building process:
- Filter content is always wrapped inside curly
{}
brackets
OR: []
indicates that we have one outer OR condition
{},{}
inside OR condition must consist of Name:Value pairs such as Name:"GenePoint"
- Inside second pair of curly brackets, we have nested another
AND
operator
AND
operator again consists of in this case two Name:Value pairs similair to OR
operator
- Finished filter argument
What is really powerfull about this is that there is virtually no limits as to how many values we can put into operators.
So for example, lets try again to build query with one OR
operator and five name:value
pairs inside it:
filter: {OR:[{},{},{},{},{}]}
filter: {OR:[ {Name:""},
{Name:""},
{Name:""},
{Name:""},
{Name:""}
]
}
filter: {OR:[ {Name:"GenePoint"},
{Name:"United Oil & Gas, UK"},
{Name:"Edge Communications"},
{Name:"Pyramid Construction Inc."},
{Name:"Dickenson plc"}
]
}
and the final query would look like:
{
Account(filter: {OR:[ {Name:"GenePoint"},
{Name:"United Oil & Gas, UK"},
{Name:"Edge Communications"},
{Name:"Pyramid Construction Inc."},
{Name:"Dickenson plc"}]}
){
Id
Name
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nPQAR",
"Name": "Edge Communications"
},
{
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc."
},
{
"Id": "0013E00001Dq4nSQAR",
"Name": "Dickenson plc"
},
{
"Id": "0013E00001Dq4nXQAR",
"Name": "United Oil & Gas, UK"
},
{
"Id": "0013E00001Dq4nZQAR",
"Name": "GenePoint"
}
]
}
}
|
filters can also be added to the nested objects easily:
{
Account(filter: {OR:[{Name:"GenePoint"},
{Name:"United Oil & Gas, UK"},
{Name:"Edge Communications"},
{Name:"Pyramid Construction Inc."},
{Name:"Dickenson plc"}]}){
Id
Name
Contacts(filter: {OR:[{FirstName:"Pat"},
{FirstName:"Andy"}]}){
Id
FirstName
}
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Contacts": null,
"Id": "0013E00001Dq4nPQAR",
"Name": "Edge Communications"
},
{
"Contacts": [
{
"AccountId": "0013E00001Dq4nRQAR",
"Id": "0033E00001FIO5UQAX",
"FirstName": "Pat"
}
],
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc."
},
{
"Contacts": [
{
"AccountId": "0013E00001Dq4nSQAR",
"Id": "0033E00001FIO5VQAX",
"FirstName": "Andy"
}
],
"Id": "0013E00001Dq4nSQAR",
"Name": "Dickenson plc"
},
{
"Contacts": null,
"Id": "0013E00001Dq4nXQAR",
"Name": "United Oil & Gas, UK"
},
{
"Contacts": null,
"Id": "0013E00001Dq4nZQAR",
"Name": "GenePoint"
}
]
}
}
|
Filters can be used together with regular arguments which would translate to filter AND
argument.
In some cases it could be better to wrap everything as filter if possible (just for estetics of it), but this is also valid query:
{
Account(AccountNumber: "CD451796",
filter: {OR:[{Name:"GenePoint"},
{Name:"United Oil & Gas, UK"},
{Name:"Edge Communications"}]}){
Id
Name
AccountNumber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nPQAR",
"Name": "Edge Communications",
"AccountNumber": "CD451796"
}
]
}
}
|
Sorting is not something that is specificaly described in the GraphQL specification. However, as Salesforce SOQL suports it, we decided to implement it via the tools that GraphQL gives us.
Sort (SOQL ORDER BY) will be used in similair way the filters are used.
Syntax is as follows: ObjectName(sort: {field:"fieldName" order:"fieldOrder"})
{
Account(sort: {field:"Name" order:"ASC"}) {
Id
Name
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nQQAR",
"Name": "Burlington Textiles Corp of America"
},
{
"Id": "0013E00001Dq4nSQAR",
"Name": "Dickenson plc"
},
{
"Id": "0013E00001Dq4nPQAR",
"Name": "Edge Communications"
},
{
"Id": "0013E00001Dq4nVQAR",
"Name": "Express Logistics and Transport"
},
{
"Id": "0013E00001Dq4nZQAR",
"Name": "GenePoint"
},
{
"Id": "0013E00001Dq4nTQAR",
"Name": "Grand Hotels & Resorts Ltd"
},
{
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc."
},
{
"Id": "0013E00001Dq4naQAB",
"Name": "sForce"
},
{
"Id": "0013E00001Dq4nUQAR",
"Name": "United Oil & Gas Corp."
},
{
"Id": "0013E00001Dq4nYQAR",
"Name": "United Oil & Gas, Singapore"
},
{
"Id": "0013E00001Dq4nXQAR",
"Name": "United Oil & Gas, UK"
},
{
"Id": "0013E00001Dq4nWQAR",
"Name": "University of Arizona"
}
]
}
}
|
Sorting supports multiple fields in one order:
{
Account(sort: {field:"Name, AccountNumber" order:"ASC"}) {
Id
Name
AccountNUmber
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nQQAR",
"Name": "Burlington Textiles Corp of America",
"AccountNumber": "CD656092"
},
{
"Id": "0013E00001Dq4nSQAR",
"Name": "Dickenson plc",
"AccountNumber": "CC634267"
},
{
"Id": "0013E00001Dq4nPQAR",
"Name": "Edge Communications",
"AccountNumber": "CD451796"
},
{
"Id": "0013E00001Dq4nVQAR",
"Name": "Express Logistics and Transport",
"AccountNumber": "CC947211"
},
{
"Id": "0013E00001Dq4nZQAR",
"Name": "GenePoint",
"AccountNumber": "CC978213"
},
{
"Id": "0013E00001Dq4nTQAR",
"Name": "Grand Hotels & Resorts Ltd",
"AccountNumber": "CD439877"
},
{
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc.",
"AccountNumber": "CC213425"
},
{
"Id": "0013E00001Dq4naQAB",
"Name": "sForce"
},
{
"Id": "0013E00001Dq4nUQAR",
"Name": "United Oil & Gas Corp.",
"AccountNumber": "CD355118"
},
{
"Id": "0013E00001Dq4nYQAR",
"Name": "United Oil & Gas, Singapore",
"AccountNumber": "CD355120-B"
},
{
"Id": "0013E00001Dq4nXQAR",
"Name": "United Oil & Gas, UK",
"AccountNumber": "CD355119-A"
},
{
"Id": "0013E00001Dq4nWQAR",
"Name": "University of Arizona",
"AccountNumber": "CD736025"
}
]
}
}
|
There can also be multiple field sort with different orders for each field. We build them by simply putting them into list (same as we would with filters).
{
Account(sort:[{field:"Name, Website" order:"ASC"},
{field:"Active__c" order:"DESC"}]) {
Id
Name
Website
Active__c
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nQQAR",
"Name": "Burlington Textiles Corp of America",
"Website": "www.burlington.com"
},
{
"Id": "0013E00001Dq4nSQAR",
"Name": "Dickenson plc",
"Website": "dickenson-consulting.com",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4nPQAR",
"Name": "Edge Communications",
"Website": "http://edgecomm.com",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4nVQAR",
"Name": "Express Logistics and Transport",
"Website": "www.expressl&t.net",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4nZQAR",
"Name": "GenePoint",
"Website": "www.genepoint.com",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4nTQAR",
"Name": "Grand Hotels & Resorts Ltd",
"Website": "www.grandhotels.com",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc.",
"Website": "www.pyramid.com",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4naQAB",
"Name": "sForce",
"Website": "www.sforce.com"
},
{
"Id": "0013E00001Dq4nUQAR",
"Name": "United Oil & Gas Corp.",
"Website": "http://www.uos.com",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4nYQAR",
"Name": "United Oil & Gas, Singapore",
"Website": "http://www.uos.com",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4nXQAR",
"Name": "United Oil & Gas, UK",
"Website": "http://www.uos.com",
"Active__c": "Yes"
},
{
"Id": "0013E00001Dq4nWQAR",
"Name": "University of Arizona",
"Website": "www.universityofarizona.com",
"Active__c": "Yes"
}
]
}
}
|
Sort can naturally be used in any combination with other elements described above.
{
Account(AccountNumber_starts_with:"C"
sort: {field:"name" order:"ASC"}
filter: {AND:[{Name_contains:"United"}
{Name_contains:"."}]}){
Id
Name
AccountNumber
Contacts(FirstName_starts_with:"Ar",
sort: [{field:"firstName" order:"ASC"}
{field:"LastName" order:"DESC"}]
filter: {AND:[{FirstName_not:"Arina"}
{FirstName_ends_with:"ur"}]}) {
FirstName
LastName
}
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Contacts": [
{
"AccountId": "0013E00001Dq4nUQAR",
"FirstName": "Arthur",
"LastName": "Song",
"Id": "0033E00001FIO5dQAH"
}
],
"Id": "0013E00001Dq4nUQAR",
"Name": "United Oil & Gas Corp."
}
]
}
}
|
GraphQL specification does not specify the usage of operators such as _gt (greater than), _lt (less than), _gte (greater than or equal to), and similar.
Regardless, using them can narrow the search to be even more precise which means getting less data that is not required while still preserving querying of the fields naturally in the native GraphQL way.
Operators in MIP are used directly by concatenating them as the suffix in the field name.
Operators available for use are located in the following table. Every column starts with the Schema.DisplayType of the field and then available operators for it bellow:
Integer, Double, DateTime/Date/Time, Percent, Currency |
String, Email, Phone, Textarea, URL |
Boolean |
Picklist |
Multi-Select Picklist |
Id |
_equals |
_equals |
_equals |
_equals |
_equals |
_equals |
_not |
_not |
_not |
_not |
_not |
_not |
_lt |
_not_starts_with |
_available |
_not_starts_with |
_includes |
|
_gt |
_starts_with |
_available_not |
_starts_with |
_excludes |
|
_lte |
_not_ends_with |
|
_not_ends_with |
|
|
_gte |
_ends_with |
|
_ends_with |
|
|
|
_not_contains |
|
_not_contains |
|
|
|
_contains |
|
_contains |
|
|
|
|
|
_in |
|
|
|
|
|
_not_in |
|
|
Naming will be kept as standard and unambiguous as possible so there will be no explanation of what each of them does as they are self-explanatory.
{
Account(filter: {AND:[{CreatedDate_lt:"YESTERDAY"},
{AND:[{SLAExpirationDate__c_gt:"2020-02-22"},
{Name_contains:"."}]}]}
){
Id
Name
CreatedDate
SLAExpirationDate__c
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Account": [
{
"Id": "0013E00001Dq4nRQAR",
"Name": "Pyramid Construction Inc.",
"CreatedDate": "2020-09-24T08:11:31.000Z",
"SLAExpirationDate__c": "2021-04-20"
},
{
"Id": "0013E00001Dq4nUQAR",
"Name": "United Oil & Gas Corp.",
"CreatedDate": "2020-09-24T08:11:31.000Z",
"SLAExpirationDate__c": "2021-04-20"
}
]
}
}
|
Here is the example of using operators with Picklists and Multi-Select Picklists:
{
Contact(filter: {AND:[{LeadSource_equals:"Public Relations"},
{LeadSource_in:"Public Relations, Something else"},
{LeadSource_in:"(Public Relations, Something else)"},
{LeadSource_not_in:"Public, else"},
{LeadSource_starts_with:"Public"},
{LeadSource_ends_with:"Relations"},
{LeadSource_contains:"ic"},
{TestMultiPicklist__c_includes:"one"},
{TestMultiPicklist__c_includes:"one;two"},
{TestMultiPicklist__c_excludes:"three"},
{TestMultiPicklist__c_includes:"one;two,three"},
{TestMultiPicklist__c_includes:"(one;two,three)"}]}) {
LeadSource
TestMultiPicklist__c
}
}
| |
{
"extensions": null,
"errors": null,
"data": {
"Contact": [
{
"LeadSource": "Public Relations",
"TestMultiPicklist__c": "one;two",
"Id": "0033E00001FIO5dQAH"
}
]
}
}
|
A GraphQL supports introspection over its schema. This schema is queried using GraphQL itself, creating a powerful platform for tool‐building.
MIPs GraphQL implementation includes getting the default schema for SF object model. With this, we can get all of the information of every field on SF that can potentialy be queried.
This funcionality can be tried by typing __schema
as the query.
You would get something like this:
__schema
| |
{
"name": "Contract",
"kind": "OBJECT",
"fields": [
{
"type": {
"name": "ID",
"kind": "SCALAR"
},
"name": "Id"
},
{
"type": {
"name": "REFERENCE",
"kind": "OBJECT"
},
"name": "AccountId"
},
{
"type": {
"name": "REFERENCE",
"kind": "OBJECT"
},
"name": "Pricebook2Id"
},
{
"type": {
"name": "PICKLIST",
"kind": "ENUM"
},
"name": "OwnerExpirationNotice"
},
{
"type": {
"name": "DATE",
"kind": "SCALAR"
},
"name": "StartDate"
},
{
"type": {
"name": "DATE",
"kind": "SCALAR"
},
"name": "EndDate"
},
{
"type": {
"name": "TEXTAREA",
"kind": "SCALAR"
},
"name": "BillingStreet"
},
{
"type": {
"name": "STRING",
"kind": "SCALAR"
},
"name": "BillingCity"
},
{
"type": {
"name": "STRING",
"kind": "SCALAR"
},
"name": "BillingState"
},
{
"type": {
"name": "STRING",
"kind": "SCALAR"
},
"name": "BillingPostalCode"
},
{
"type": {
"name": "STRING",
"kind": "SCALAR"
},
"name": "BillingCountry"
},
{
"type": {
"name": "DOUBLE",
"kind": "SCALAR"
},
"name": "BillingLatitude"
},
{
"type": {
"name": "DOUBLE",
"kind": "SCALAR"
},
"name": "BillingLongitude"
},
{
"type": {
"name": "PICKLIST",
"kind": "ENUM"
},
"name": "BillingGeocodeAccuracy"
},
{
"type": {
"name": "ADDRESS",
"kind": "OBJECT"
},
"name": "BillingAddress"
},
{
"type": {
"name": "TEXTAREA",
"kind": "SCALAR"
},
"name": "ShippingStreet"
},
{
"type": {
"name": "STRING",
"kind": "SCALAR"
},
"name": "ShippingCity"
},
{
"type": {
"name": "STRING",
"kind": "SCALAR"
},
"name": "ShippingState"
},
{
"type": {
"name": "STRING",
"kind": "SCALAR"
},
"name": "ShippingPostalCode"
},
{
"type": {
"name": "STRING",
"kind": "SCALAR"
},
"name": "ShippingCountry"
},
{
"type": {
"name": "DOUBLE",
"kind": "SCALAR"
},
"name": "ShippingLatitude"
},
{
"type": {
"name": "DOUBLE",
"kind": "SCALAR"
},
"name": "ShippingLongitude"
},
{
"type": {
"name": "PICKLIST",
"kind": "ENUM"
},
"name": "ShippingGeocodeAccuracy"
},
{
"type": {
"name": "ADDRESS",
"kind": "OBJECT"
},
"name": "ShippingAddress"
},
{
"type": {
"name": "INTEGER",
"kind": "SCALAR"
},
"name": "ContractTerm"
},
{
"type": {
"name": "REFERENCE",
"kind": "OBJECT"
},
"name": "OwnerId"
},
{
"type": {
"name": "PICKLIST",
"kind": "ENUM"
},
"name": "Status"
}
|
In this example, the result is shortened as the real one includes tens of thousands of lines. We can see the true power of GraphQL here, as time it took to retrieve and display them is measured in seconds and result is something that would be extremely hard to accomplish by doing it traditionally over REST.
Further improvement of this should be possibility of making the Introspection over the query, and not only schema.
That means that it will be possible to write keyword __type
before the standard GraphQL query which would then return formatted response (as the one in the previous example) but only with those fields in the query, which would also be nested as is the query itself.