- Published on
Sitecore GraphQL — Upserting Items
- Authors

- Name
- Mark Gibbons
- @markgibbons25
The new GraphQL API that is shipped as part of the Sitecore Headless Services offering from Sitecore is a game changer. It is a replacement for the decades old Item Rest API. For new development I’d highly recommend it. A good starting point to get your head around it: https://graphql.org/learn/
It comes with GraphQL mutations to create, update, and delete items. And of course querying items directly, as well as querying via the search indexes. In my case I wanted to be able to continuously create / update items which may or may not already exist. This is acheivable by doing a query/search for the existing item and then either call create / update as necessary. But why not cut out the middleman and add an upsert function which can do that hard work for us?
Step 1: Some code.
I decompiled the Sitecore.Services.GraphQL.Content.Mutations.CreateItemMutation and the Sitecore.Services.GraphQL.Content.Mutations.UpdateItemMutation and put it together like this:
using System;
using System.Collections.Generic;
using GraphQL;
using GraphQL.Types;
using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Services.GraphQL.Content;
using Sitecore.Services.GraphQL.Content.GraphTypes;
using Sitecore.Services.GraphQL.Content.Mutations;
using Sitecore.Services.GraphQL.Schemas;
namespace Feature.JssExtensions.GraphQL.Mutations
{
public class UpsertItemMutation : RootFieldType<ItemInterfaceGraphType, Item>, IContentSchemaRootFieldType
{
private readonly FieldTypeToJsonTypeMapper _fieldTypeToJsonTypeMapper;
public Database Database { get; set; }
public UpsertItemMutation(FieldTypeToJsonTypeMapper fieldTypeToJsonTypeMapper)
: base("upsertItem", "Create / update a Sitecore item")
{
Assert.ArgumentNotNull(fieldTypeToJsonTypeMapper, "fieldTypeToJsonTypeMapper");
_fieldTypeToJsonTypeMapper = fieldTypeToJsonTypeMapper;
base.Arguments = new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "name",
Description = "The name of the item to create / update"
}, new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "template",
Description = "The template full path or ID of the template the item is based on"
}, new QueryArgument<NonNullGraphType<StringGraphType>>
{
Name = "parent",
Description = "The parent item path or ID to add the item under"
}, new QueryArgument<StringGraphType>
{
Name = "language",
Description = "The language to create the item in (defaults to context)"
}, new QueryArgument<ListGraphType<NonNullGraphType<FieldValueInputType>>>
{
Name = "fields",
Description = "The fields to set on the item. Optional."
});
}
protected override Item Resolve(ResolveFieldContext context)
{
string name = context.GetArgument<string>("name");
string template = context.GetArgument<string>("template");
string parent = context.GetArgument<string>("parent");
string languageName = context.GetArgument<string>("language");
List<FieldValue> fields = context.GetArgument<List<FieldValue>>("fields");
if (string.IsNullOrWhiteSpace(languageName))
{
languageName = Context.Language.Name;
}
if (!Language.TryParse(languageName, out var language))
{
throw new InvalidOperationException("Language value " + languageName + " was not a valid language.");
}
if (!ItemMutationHelper.TryResolveTemplate(Database, template, language, null, out var templateItem))
{
throw new InvalidOperationException("The template '" + template + "' was not a valid template path or ID, or you did not have access to read it.");
}
var path = $"{parent}/{name}";
if (!IdHelper.TryResolveItem(Database, path, language, null, out var item))
{
try
{
ItemMutationHelper.SetFields(item, fields, _fieldTypeToJsonTypeMapper);
return item;
}
catch (Exception ex)
{
context.Errors.Add(new ExecutionError(ex.Message ?? ""));
throw;
}
}
if (!IdHelper.TryResolveItem(Database, parent, language, null, out var parentItem))
{
throw new InvalidOperationException("The parent item '" + parent + "' was not a valid item path or ID, or you did not have access to read it.");
}
Item newItem = null;
try
{
newItem = parentItem.Add(name, templateItem);
if (newItem.Language != language)
{
newItem = Database.GetItem(newItem.ID, language);
}
ItemMutationHelper.SetFields(newItem, fields, _fieldTypeToJsonTypeMapper);
return newItem;
}
catch (Exception)
{
newItem?.Delete();
throw;
}
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
<sitecore>
<api>
<GraphQL>
<defaults>
<content>
<schemaProviders>
<systemContent type="Sitecore.Services.GraphQL.Content.ContentSchemaProvider, Sitecore.Services.GraphQL.Content">
<mutations hint="raw:AddMutation">
<mutation name="upsertItem" type="Feature.JssExtensions.GraphQL.Mutations.UpsertItemMutation, Feature.JssExtensions" resolve="true"/>
</mutations>
</systemContent>
</schemaProviders>
</content>
</defaults>
</GraphQL>
</api>
</sitecore>
</configuration>
Source: gist
Step 2: Try it out
Sitecore Headless also ships with the GraphQL UI Playground where you can send your queries https://sc.dev.local/sitecore/api/graph/items/master/ui
It generates all the schema docs automatically. So after deploying our new mutation — here it is ready to use!
