Skip to content

11 — Grid Layout Deduplication Deep Investigation

Date: 2026-02-26 Scope: Cross-datatype comparison of layout element types, area wrappers, and settings element types Method: Programmatic analysis of _blockgrid-configs.json (live v17 BlockGrid configurations), v8 baseline (03-v8-grid-baseline.md), and migration tool source code

Note: Line number references to GridConfigurationMigrationService.cs in this report are approximate — the file has been modified since this report was authored (Layout AllowAtRoot changed from true to false, hardcoded folder ID 1314 replaced with dynamic lookup). The logic described remains correct.


Executive Summary

ZERO layout names are truly identical across datatypes. Every shared layout name (fullwidth, half, thirds, full, leftnav, sidebar) resolves to different specifiedAllowance lists because each datatype has a different set of content element types. The per-datatype architecture is correct and should NOT be changed.

Settings element types are the only viable deduplication target, saving 29 element types with manageable risk, but the benefit is purely cosmetic and the recommendation is to defer.


1. Data Sources and Methodology

1.1 Input Data

  • _blockgrid-configs.json: 13 BlockGrid datatype configurations exported from the live v17 Umbraco instance
  • 03-v8-grid-baseline.md: Comprehensive v8 grid configuration baseline for all 14 datatypes
  • GridConfigurationMigrationService.cs: Migration tool source (4,037 lines)
  • GuidGenerationUtility.cs: Deterministic GUID generation utility (165 lines)
  • GridSettingsDeduplicator.cs: Existing hash-based settings deduplication (165 lines)

1.2 Missing Datatype

The JSON contains 13 datatypes (not 14). Article Controls (v8 ID 1055) is missing. This aligns with the Phase 3 finding in Report 09 that Article Controls' infrastructure types are orphaned despite having content. This is a separate bug to investigate.

1.3 Methodology

  • Parsed all 13 BlockGrid configurations programmatically
  • Extracted every layout block (allowAtRoot=true + has areas), area wrapper block (allowAtRoot=false + has areas), and content block (allowAtRoot=false + no areas)
  • Normalized layout names (lowercase, strip spaces/hyphens/ampersands) for cross-datatype comparison
  • Compared specifiedAllowance GUID lists across all instances of shared layout names
  • Verified content element type sets per datatype

2. Cross-Datatype Layout Comparison

2.1 Layout Names Appearing in Multiple Datatypes

10 normalized layout names appear in more than one datatype:

Normalized Name Instances Column Spans Identical? Allowances Identical?
fullwidth 10 Yes (12) NO
half 8 Yes (6+6) NO
thirds 8 Yes (4+4+4) NO
1columnlayout 8 Yes (12) NO
full 4 Yes (12) NO
2columnlayout 4 No (4+8 vs 8+4) NO
leftnav 4 Yes (4+8) NO
sidebar 3 Yes (12) NO
rightsidebar 2 No (12 vs 8+4) NO
(empty) 4 No NO

Result: 0 out of 10 shared layout names are truly identical across all instances.

Even when column spans match, the specifiedAllowance arrays differ because each area wrapper points to a different set of content element types (which are per-datatype).

2.2 Why Column-Identical Layouts Have Different Allowances

The root cause is that each datatype has a different set of content element types:

Datatype Content Element Types
Standard Page Grid 108
Homepage Grid 61
Animated Grid 50
Standard Page Full Width 41
articleGrid 35
Footer Grid 31
services Grid 16
Contact us Grid 14
Sp with Nav Left Grid 12
Cookie Grid 8
Full width Grid 8
Full Width Grid Bottom 1
test data type 0

No two datatypes have identical content element type sets. Some are subsets (e.g., Cookie Grid's 8 types are a subset of Footer Grid's 31), but none are equal.

When a layout like Half has allowAll=true areas, the migration tool expands that to the full set of content types for that datatype. Since every datatype has a different content type set, every Half layout produces different specifiedAllowance lists.

For layouts with allowAll=false (restricted editor lists), the differences are even more pronounced -- each datatype explicitly configures which editors are allowed per area.

2.3 Detailed Comparison: "Half" Layout Across 8 Datatypes

Datatype Area 0 Allowances Area 1 Allowances Notes
Homepage Grid 1 (area wrapper) 1 (area wrapper) Restricted
Standard Page Grid 1 (area wrapper) 1 (area wrapper) Restricted
Animated Grid 50 50 allowAll expanded
articleGrid 36 36 allowAll expanded
Cookie Grid 9 9 allowAll expanded
Full width Grid 1 (area wrapper) 1 (area wrapper) Restricted
Sp with Nav Left Grid 1 (area wrapper) 1 (area wrapper) Restricted
Standard Page Full Width 1 (area wrapper) 1 (area wrapper) Restricted

Even among the "restricted" ones (allowance count = 1), each references a different area wrapper GUID because area wrappers are per-datatype.

2.4 Detailed Comparison: "FullWidth" Layout Across 10 Datatypes

The fullwidth layout appears in 10 instances across datatypes (2 instances in Contact us Grid, 2 in Footer Grid):

Datatype Instance Area 0 Allowances Notes
Contact us Grid Full Width 1 Single area wrapper
Contact us Grid Full width (row config) 2 Allows 2 layout types
Footer Grid FullWidth 1 Single area wrapper
Footer Grid FullWidth (row config) 6 Allows 6 layout types
Homepage Grid fullWidth 1 Single area wrapper
Standard Page Grid Full Width 62 All 62 content types
Full Width Grid Bottom FullWidth 1 Single area wrapper
Animated Grid FullWidth 1 Single area wrapper
Full width Grid FullWidth 1 Single area wrapper
Sp with Nav Left Grid FullWidth 1 Single area wrapper

Even the "1 allowance" instances all reference different area wrapper GUIDs because the area wrappers themselves contain different content type allowances.


3. Area Wrapper Analysis

3.1 Area Wrapper Architecture

In the BlockGrid model, each layout area has a specifiedAllowance that points to one or more area wrapper element types. Each area wrapper itself is a block with areas, and those inner areas contain the actual specifiedAllowance of content element types.

The migration tool creates area wrappers per layout+area+datatype combination: - Alias: grid_{dataTypeId}_areawrapper_{layoutName}_{areaIndex} - GUID: Generated from settings_{dataTypeId}_areaWrapper_{layoutName}_{areaIndex}

3.2 Area Wrapper Sharing

137 unique area wrapper GUIDs exist in specifiedAllowance entries across all datatypes. Of these, 75 are referenced by multiple datatypes. However, these are NOT area wrapper element types being shared across datatypes -- they are content element types (like RTE, Media, Heading Item) that are correctly shared because GenerateForContentElementType() does NOT include dataTypeId.

The area wrapper element types themselves (the ones that layout areas point to via specifiedAllowance) are always per-datatype.

3.3 Layout Area Breakdown

Area Type Count Description
Restricted areas (1 allowance) 80 Area points to exactly 1 area wrapper
Multi-allowance areas (>1 allowance) 84 Area allows multiple content types directly
Total layout areas 164

4. Settings Element Type Analysis

4.1 Current State

13 unique settings GUIDs exist (one per datatype). Each datatype uses a single settings GUID for ALL its blocks (row, cell, and block settings are all the same element type per DT). No settings GUIDs are shared across datatypes.

4.2 The GridSettingsDeduplicator

The codebase already has GridSettingsDeduplicator.cs which uses hash-based deduplication. However, it only works within a single migration run. Since each datatype generates GUIDs with dataTypeId baked in:

Key = GuidGenerationUtility.GenerateForSettingsElementType(dataTypeId, settingsType),
Alias = $"grid_{dataTypeId}_settings_{settingsType}",

The deduplicator cannot match settings across datatypes even if the property signatures are identical.

4.3 Settings Signature Groups (from Report 05)

Signature Properties Datatypes Count
A (Standard) class, bgImage, bgColor, padding, margin, visible 9 DTs 9
B (Full Animation) A + elementId, 5 animation props 2 DTs 2
C (Partial Animation) A + elementId, 3 animation props 1 DT 1
D (Minimal + bgColor) class, bgImage, bgColor, visible 1 DT 1
E (Minimal) class, bgImage, visible 1 DT 1

Potential savings: 9 DTs in Signature A could share one settings type, saving 8.

4.4 What Cross-DT Settings Dedup Would Require

To enable cross-datatype settings deduplication:

  1. Migration tool change: Modify GridSettingsDeduplicator to use a hash-based key (not dataTypeId) for GUID generation. E.g.:

    Key = GuidGenerationUtility.GenerateFromString($"shared_settings_{signatureHash}"),
    

  2. Content data migration: Every content row's BlockGrid value stores settingsData with contentTypeKey referencing the per-DT settings GUID. All references would need remapping.

  3. uSync config files: Each settings element type has a .config file. The 29 redundant files would need removal and references updated.

  4. Multi-database coordination: All client databases must be updated simultaneously.


5. Migration Tool Code Analysis

5.1 The Per-DataType Pipeline

MigrateGridDataTypeAsync() (line 197) processes one datatype at a time with no cross-datatype awareness:

Step 1: CreateBlockGroupsAsync          → 2 block groups per DT
Step 2: CreateLayoutElementTypesAsync   → N layout types per DT
Step 3: CreateRowConfigElementTypesAsync → M row config types per DT
Step 4: CreateContentElementTypesAsync  → Shared (no dataTypeId in GUID)
Step 5: CreateSettingsElementTypesAsync  → 1 per DT (uses dataTypeId)
Step 6: BuildBlockGridConfigurationAsync → Area wrappers created here (per DT)

5.2 What IS Correctly Shared

Content element types are shared across datatypes because GenerateForContentElementType() uses only the editor alias:

public static Guid GenerateForContentElementType(string editorAlias)
{
    var input = $"content_{editorAlias}";
    return GenerateFromString(input);
}

This means grid_builtin_rte, grid_element_headingitem, etc. are created once and reused by all datatypes. This is confirmed by the data: 75 content element type GUIDs appear in multiple datatypes' configurations.

5.3 What Is Per-DataType (By Design)

Everything else uses dataTypeId in GUID generation: - GenerateForLayoutElementType(dataTypeId, templateName)layout_{dataTypeId}_{name} - GenerateForRowConfigElementType(dataTypeId, layoutName)rowconfig_{dataTypeId}_{name} - GenerateForSettingsElementType(dataTypeId, settingsType)settings_{dataTypeId}_{type} - GenerateForBlockArea(dataTypeId, blockName, areaIndex)area_{dataTypeId}_{block}_{idx}

5.4 Area Wrapper Creation (Line 2771-2816)

Area wrappers are created inside BuildBlockGridConfigurationAsync():

var wrapperAlias = $"grid_{dataTypeId}_areawrapper_{shortLayoutName}_{areaIndex}";
var wrapperElementTypeKey = GuidGenerationUtility.GenerateForSettingsElementType(
    dataTypeId, $"areaWrapper_{shortLayoutName}_{areaIndex}");

Each area wrapper is unique per datatype+layout+area. The area wrapper's own areas contain the actual content type allowances, which are populated based on the layout's allowAll flag and allowedEditors list.


6. Hypothetical Deduplication Scenarios

6.1 Scenario A: Share Layout Element Types by Name

Approach: All datatypes' "Half" layouts share one element type GUID.

Problem: The layout element type itself is empty (no properties), but in the BlockGrid configuration, each layout block entry has its own areas array with specifiedAllowance. If we share the layout GUID, we need to decide whose areas/allowances to use.

Option A1: Union of all allowances (all content types from all DTs) - Result: Every "Half" area would allow 108+ content types (the union) - Impact: Editors see content types they shouldn't. E.g., Contact us Grid's Half would show all 108 Standard Page Grid editors - Verdict: Unacceptable -- breaks editor permissions

Option A2: Keep separate area configurations per DT - Result: Each BlockGrid datatype config still has its own blocks[] entry for "Half" with its own areas, but they all reference the same contentElementTypeKey - Impact: Works for the configuration, BUT the area wrappers are still per-DT (they must be, because they encode different allowances) - Savings: Only the layout element type itself is shared. Since layouts have no properties, this saves only the element type record in the database - Verdict: Minimal benefit -- saves 31 empty element type records but adds complexity

Option A3: Share layout + merge area wrappers into a superset - Result: Same as A1 at the area wrapper level - Verdict: Same problem as A1

6.2 Scenario B: Share Settings Element Types by Signature Hash

Approach: Settings types with identical property signatures share one GUID.

Impact: - Saves 29 element types (42 → 13) - Requires GUID remapping in all content data - Requires uSync config updates - Requires multi-database coordination

Risk: Medium. Settings types are referenced in content data's settingsData[].contentTypeKey.

Benefit: Cleaner backoffice, easier maintenance. But purely cosmetic -- no functional difference.

Verdict: Viable but low priority. The GridSettingsDeduplicator already exists and could be enhanced. Defer until backoffice clutter becomes a real problem.

6.3 Scenario C: Share Area Wrappers When Allowance Lists Match

Approach: Two area wrappers from different DTs with identical specifiedAllowance sets share one GUID.

Analysis: Even among area wrappers with allowAll=true, the expanded allowance lists differ because each DT has a different content element type set. Among restricted areas (allowAll=false), the allowed editor lists differ by design.

Result: No two area wrappers across different datatypes have identical allowance lists.

Verdict: Not feasible. No savings possible.


7. Missing Article Controls (DT 1055) Investigation

The v17 _blockgrid-configs.json contains 13 datatypes but Article Controls (v8 ID 1055) is absent. This aligns with Report 09's finding:

"Article Controls Grid has content in progresscu.ie (12 nodes). Should NOT be orphaned -- investigate why."

This is a separate migration tool bug where the BlockGrid datatype for Article Controls either: 1. Was not created in the v17 database 2. Was created but not configured with the correct element type references

This needs investigation independent of the deduplication question.


8. Concrete Recommendations

8.1 DO NOT Deduplicate Layout Element Types

Factor Assessment
Layouts with identical structures 0 out of 10 shared names
Root cause Different content type sets per DT
Superset approach Breaks editor permissions
Separate areas approach Minimal savings (31 empty records)
Recommendation No action. Architecture is correct.

8.2 DO NOT Deduplicate Area Wrapper Element Types

Factor Assessment
Area wrappers with identical allowances 0 across different DTs
Root cause Different content type sets per DT
Recommendation No action. Architecture is correct.

8.3 DEFER Settings Deduplication

Factor Assessment
Potential savings 29 element types (42 → 13)
Code change needed Modify GuidGenerationUtility + GridSettingsDeduplicator
Content data change GUID remapping in all BlockGrid content values
Risk Medium (multi-database GUID remapping)
Benefit Cosmetic (cleaner backoffice)
Recommendation Defer. Revisit if backoffice clutter is problematic.

8.4 Investigate Missing Article Controls (DT 1055)

This is a separate bug. The BlockGrid configuration for Article Controls is missing from the v17 database despite having content. Investigate in the migration tool.

8.5 Remove Test Data Type (DT 2236) If Unused

The test data type has 0 content element types and is likely unused across all client databases. Validate and remove to save 10 element types.


9. Summary: What's In the 303 Element Types

Category Count Shared? Can Deduplicate?
Layout element types 62 No (per-DT) No -- different allowances
Area wrapper element types 134 No (per-DT) No -- different content types
Row config element types 26 No (per-DT) No -- different layout references
Settings element types 42 No (per-DT) Maybe -- 29 savings, but deferred
Content element types 39 Yes (shared) Already deduplicated correctly
Total 303 Max savings: 29 (settings only)

The migration tool's per-datatype architecture for layouts, area wrappers, and row configs is correct by design. The only practical deduplication target is settings types, which provides cosmetic-only benefit and should be deferred.


Appendix A: Content Element Type Sharing Matrix

75 of 137 content element type GUIDs are shared across multiple datatypes. The top shared types:

Content Element Shared By (DTs)
Heading Item 11
Rich Text Editor 11
Media 10
Quote 9
Embedded Content 8
Audio Tag Element 8
Login Element 8
Login Status Element 8
Register Element 8
Cookies Element 8
Umbraco Form Element 8

This confirms that the content element type sharing (via GenerateForContentElementType() without dataTypeId) is working correctly.

Appendix B: Content Element Type Count Per Datatype

Datatype Content Types Note
Standard Page Grid (1257) 108 Largest -- many custom editors
Homepage Grid (1151) 61
Animated Grid (1534) 50
Standard Page Full Width (1803) 41
articleGrid (1683) 35
Footer Grid (1127) 31
services Grid (1780) 16
Contact us Grid (1093) 14
Sp with Nav Left Grid (1798) 12
Cookie Grid (1709) 8 Subset of Footer Grid
Full width Grid (1736) 8
Full Width Grid Bottom (1459) 1 Only RTE
test data type (2236) 0 No content blocks at all
Article Controls (1055) N/A Missing from v17 configs

No two datatypes have identical content element type sets, which is why every shared layout name resolves to different specifiedAllowance lists.

Migration documentation by Double for Progress Credit Union