Data Template inheritance. Most of the time it's great and a powerful tool to help you define your content schema effectively. But there are a few places where it can trip you up - and one of the interesting ones is duplicated field names. I found myself chatting about what actually happens and how this might affect PowerShell scripts and headless code recently, and it seemed worth writing down...
Sitecore is clever enough to stop you defining the same field name twice in a single data template. If you try to edit a template and add the same field name multiple times you get an error when you try to save, and the second and subsequent instances of the field won't be created:
It seems like the save stops at the point it first spots the duplication, so any other fields before the 2nd instance of the duplicated name should be saved in the content tree, but those after the duplicate won't be unless you correct the issue and retry your save.
But it is absolutely still possible to create a template with duplicate fields. You just have to do it with inheritance. You can define the same field name on every template in your inheritance structure if you want, and you'll not see any warning or error from this. So for example, this data template (DuplicatesTemplate
in the tree) defines a field called "Duplicate" itself and then it includes two base templates which both define this field name as well. And each of them has a different data type to show that doesn't matter:
And if you create an item from that template you see the same field three times when you edit it. (For clarity I've given them different titles in Content Editor here to make it clearer what's what)
You can see the duplicates can all have different data types and settings, but they all have the same field name under the surface.
Sitecore's training says "don't do this" so in theory this isn't a situation we should ever find ourselves in. Nobody should design themselves data templates which do this if they paid attention. But after a decade of working with this tech I can say with confidence that it is still a thing you can come across in projects every so often...
A place where I've seen this happen is with 3rd party modules - where you're including their base templates into your site content. For example, say you have a site built with an SEO module that adds some metadata for controlling search engines. And then at some point a requirement comes through to move to a different module to get some extra features. A common approach to deploy this change might be to add the data templates for the new module, get all the data set up correctly, swap over to the new code and then delete the old data templates. Something like this flow:
flowchart LR IS[Site starts with
Module v1 installed] MS[Module v2 templates
installed as well
and data setup underway] ES[Swap code to
use Module v2 instead
of Module v1] FS[Delete Module v1
data templates] IS --> MS MS --> ES ES --> FS
But what happens if the old and new modules happen to both define a field called "RobotsFlags" that gets inherited by your pages. Maybe the old module had a string field where you could type "noindex, nofollow" and the new one has a droplink where you pick the state you want.
Well in the 2nd and 3rd states in the flowchart above you might have a conflict. Hence the code running in those states might have issues because of this duplication...
Well you can see from the earlier screenshot that you can set data on duplicated fields without issues. And as long as you access the data using the field IDs then all should be well. Under the surface all the fields defined do get stored correctly - hence the reason Content Editor will show them ok. For example this little bit of PowerShell can show all three of the values from the
DuplicatesTemplate
above:
$itm = Get-Item "/sitecore/content/Home/DuplicatesItem" $itm.Fields["{845D6151-1C64-4CD3-8C3F-69E1DD0E5A59}"].Value # Base _One $itm.Fields["{A9E3AD92-6442-463E-AFCE-F2B43CB804FB}"].Value # Base _Two $itm.Fields["{7AE25CB9-37A9-4E92-A6FF-27FFCC47C84F}"].Value # Item
and the result of that is:
But if you try to access these by other methods you may run into some issues.
Using the name of the field
$itm.Fields["Duplicate"].Value
will return one of the values. But which one? Well for practical purposes you probably want to assume it's a random one, but its probably something to do with the database IDs and the order the underlying queries return the fields in - and you get the first one it returns. So in this scenario your code can't really be confident about which instance of the field it will see, as it depends on all the IDs. And hence you can't be confident what data type the field you get will be.
(While these examples are using PowerShell there broad concepts apply to other code looking at fields too)
Another place this can be confusing is if your code makes use of the
PSFields
object in PowerShell. If you iterate the fields that returns you'll not see the whole set. For example:
Oddly this returns two fields, but not all three from our Data Template. What you seem to get here is one of the base template fields (Likely the lowest ID again) and the field from the item itself.
So if you access this by name
$itm.PSFields."Duplicate"
then you get a random one of the values it does contain. But if you access it by ID (say
$itm.PSFields."{845D6151-1C64-4CD3-8C3F-69E1DD0E5A59}"
) then you can get the specific one you want.
Its worth noting that if you make GraphQL queries against this data via the preview APIs you'll see another variation on all this.
A query like this which lists the fields and values attached to a specific item:
query { item(path:"/sitecore/content/Home/ExamplePage", language:"en") { fields { name, value } } }
gets a result as follows:
{ "data": { "item": { "fields": [ { "name": "Duplicate", "value": "ABC" }, { "name": "Duplicate", "value": "DuplicatesItem" }, { "name": "Duplicate", "value": "123" }, ... snip ... ] } } }
So you can see that returns all three variants of the duplicated field, as we'd have expected from the APIs working at field-level. You can also get the field IDs back in this sort of query, so you can achieve the same sort of behaviour as above for accessing data by IDs if you really need to.
But if that item happens to be a page, and you make a
layout
query against it:
query { layout(site: "website", routePath: "/ExamplePage", language: "en") { item { rendered } } }
you get something different:
{ "data": { "layout": { "item": { "rendered": { "sitecore": { "context": { "pageEditing": false, "site": { "name": "website" }, "pageState": "normal", "editMode": "chromes", "language": "en", "itemPath": "/ExamplePage" }, "route": { "name": "ExamplePage", "displayName": "ExamplePage", "fields": { "Duplicate": { "value": 123 } }, ... snip ... } } } } } } }
This returns only one field out of the three. And you see something similar if the item with duplicates is a data source instead of the root item:
{ "data": { "layout": { "item": { "rendered": { "sitecore": { "context": { "pageEditing": false, "site": { "name": "website" }, "pageState": "normal", "editMode": "chromes", "language": "en", "itemPath": "/" }, "route": { "name": "Home", "displayName": "Home", ... snip ... "layoutId": "96e5f4ba-a2cf-4a4c-a4e7-64da88226362", "templateId": "76036f5e-cbce-46d1-af0a-4143f9b557aa", "templateName": "Sample Item", "placeholders": { "headless-header": [], "headless-main": [ { "uid": "bf6baa3b-bf45-4407-ab12-597a13703467", "componentName": "RichText", "dataSource": "{2290F73E-43C9-446C-9DD5-343FA14DDE29}", "params": { "FieldNames": "Default" }, "fields": { "Duplicate": { "value": 123 } } } ], "headless-footer": [] } } } } } } } }
The same single field again...
Well the key thing is that the training is right. You really want to avoid a situation where field names are duplicated if you can. It's confusing for editors to see multiple things with the same name even if you don't hit technical problems. And it can be tricky for developers too.
But it's entirely possible for this scenario to happen sometimes. You can't always control what field names get defined in base templates from 3rd party packages. So it is possible for them to clash.
So if you're something which might get used in that sort of scenario, you should think about being a bit defensive with your code and config. What does defensive mean here? Well you might want to consider: