SharePoint Content Types: Architecture and Best Practices

SharePoint Content Types: Architecture and Best Practices

Introduction

Content types are the foundation of SharePoint's information architecture, enabling consistent metadata management across sites, libraries, and lists. This guide covers content type design, custom columns, inheritance patterns, deployment strategies, and best practices for scalable enterprise solutions.

Understanding Content Types

What Are Content Types?

Content types define the metadata schema, workflows, and behaviors for items in SharePoint. They ensure consistency and enable:

  • Consistent metadata across multiple libraries
  • Inherited properties from parent content types
  • Document templates for specific document types
  • Custom workflows triggered by content type
  • Retention policies based on content classification

Built-in vs. Custom Content Types

Built-in Content Types:

  • Document
  • Folder
  • Item
  • Event
  • Task
  • Announcement

Custom Content Types extend built-in types with specific metadata for your organization.

Creating Custom Content Types

Through SharePoint UI

  1. Navigate to Site Settings → Site Content Types
  2. Create Content Type
    • Name: Contract Document
    • Parent: Document
    • Group: Legal Documents
  3. Add Site Columns
    • Contract Number (Single line of text)
    • Contract Value (Currency)
    • Expiration Date (Date)
    • Contract Status (Choice: Draft, Active, Expired)

Using PnP PowerShell

# Connect to SharePoint site
Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/legal" -Interactive

# Create site columns
Add-PnPField -Type Text -InternalName "ContractNumber" -DisplayName "Contract Number" -Group "Legal Columns"

Add-PnPField -Type Currency -InternalName "ContractValue" -DisplayName "Contract Value" -Group "Legal Columns"

Add-PnPField -Type DateTime -InternalName "ExpirationDate" -DisplayName "Expiration Date" -Group "Legal Columns"

Add-PnPField -Type Choice -InternalName "ContractStatus" -DisplayName "Contract Status" -Group "Legal Columns" -Choices "Draft","Active","Expired"

# Create content type
Add-PnPContentType -Name "Contract Document" -Group "Legal Documents" -ParentContentType "Document"

# Add columns to content type
Add-PnPFieldToContentType -Field "ContractNumber" -ContentType "Contract Document"
Add-PnPFieldToContentType -Field "ContractValue" -ContentType "Contract Document"
Add-PnPFieldToContentType -Field "ExpirationDate" -ContentType "Contract Document"
Add-PnPFieldToContentType -Field "ContractStatus" -ContentType "Contract Document"

# Make column required
Set-PnPField -Identity "ContractNumber" -Values @{Required=$true}

Using PnP Provisioning Template

<?xml version="1.0"?>
<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2021/03/ProvisioningSchema">
  <pnp:Preferences Generator="PnP.Framework, Version=1.0.0.0" />
  <pnp:Templates ID="CONTAINER-TEMPLATE">
    <pnp:ProvisioningTemplate ID="ContentTypeTemplate" Version="1">
      
      <!-- Site Columns -->
      <pnp:SiteFields>
        <Field ID="{A1234567-89AB-CDEF-0123-456789ABCDEF}" 
               Name="ContractNumber" 
               DisplayName="Contract Number" 
               Type="Text" 
               Required="TRUE" 
               Group="Legal Columns" />
        
        <Field ID="{B1234567-89AB-CDEF-0123-456789ABCDEF}" 
               Name="ContractValue" 
               DisplayName="Contract Value" 
               Type="Currency" 
               Group="Legal Columns" />
        
        <Field ID="{C1234567-89AB-CDEF-0123-456789ABCDEF}" 
               Name="ExpirationDate" 
               DisplayName="Expiration Date" 
               Type="DateTime" 
               Format="DateOnly" 
               Group="Legal Columns" />
        
        <Field ID="{D1234567-89AB-CDEF-0123-456789ABCDEF}" 
               Name="ContractStatus" 
               DisplayName="Contract Status" 
               Type="Choice" 
               Group="Legal Columns">
          <CHOICES>
            <CHOICE>Draft</CHOICE>
            <CHOICE>Active</CHOICE>
            <CHOICE>Expired</CHOICE>
          </CHOICES>
        </Field>
      </pnp:SiteFields>
      
      <!-- Content Type -->
      <pnp:ContentTypes>
        <pnp:ContentType ID="0x0101009A1234567890ABCDEF" 
                         Name="Contract Document" 
                         Group="Legal Documents" 
                         Description="Legal contract document with metadata">
          <pnp:FieldRefs>
            <pnp:FieldRef ID="{A1234567-89AB-CDEF-0123-456789ABCDEF}" Name="ContractNumber" Required="true" />
            <pnp:FieldRef ID="{B1234567-89AB-CDEF-0123-456789ABCDEF}" Name="ContractValue" />
            <pnp:FieldRef ID="{C1234567-89AB-CDEF-0123-456789ABCDEF}" Name="ExpirationDate" />
            <pnp:FieldRef ID="{D1234567-89AB-CDEF-0123-456789ABCDEF}" Name="ContractStatus" />
          </pnp:FieldRefs>
        </pnp:ContentType>
      </pnp:ContentTypes>
      
    </pnp:ProvisioningTemplate>
  </pnp:Templates>
</pnp:Provisioning>

Apply template:

Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/legal" -Interactive
Invoke-PnPSiteTemplate -Path "ContentTypeTemplate.xml"

Content Type Inheritance

Inheritance Hierarchy

System Content Types
  └── Item (0x01)
      ├── Document (0x0101)
      │   ├── Contract Document (0x0101009A...)
      │   │   └── NDA Contract (0x0101009A...001)
      │   └── Policy Document (0x0101009B...)
      └── Event (0x0102)
          └── Company Event (0x0102009C...)

Creating Child Content Types

# Create parent content type
Add-PnPContentType -Name "Legal Document" -Group "Legal" -ParentContentType "Document"
Add-PnPFieldToContentType -Field "Department" -ContentType "Legal Document"
Add-PnPFieldToContentType -Field "ReviewDate" -ContentType "Legal Document"

# Create child content type (inherits Department and ReviewDate)
Add-PnPContentType -Name "Contract" -Group "Legal" -ParentContentType "Legal Document"
Add-PnPFieldToContentType -Field "ContractNumber" -ContentType "Contract"

# Create another child
Add-PnPContentType -Name "Policy" -Group "Legal" -ParentContentType "Legal Document"
Add-PnPFieldToContentType -Field "PolicyNumber" -ContentType "Policy"

Benefits:

  • Reusable columns inherited by all children
  • Consistent metadata across related content types
  • Easier maintenance - update parent to affect all children

Advanced Column Types

Managed Metadata

# Create term set
$termStore = Get-PnPTermStore
$termGroup = Get-PnPTermGroup -Identity "Departments"
$termSet = New-PnPTermSet -Name "Locations" -TermGroup $termGroup

# Add terms
New-PnPTerm -Name "New York" -TermSet $termSet
New-PnPTerm -Name "London" -TermSet $termSet
New-PnPTerm -Name "Tokyo" -TermSet $termSet

# Create managed metadata column
$termSetId = $termSet.Id
Add-PnPTaxonomyField -DisplayName "Office Location" `
                     -InternalName "OfficeLocation" `
                     -TermSetPath "Departments|Locations" `
                     -Group "Company Columns"

# Add to content type
Add-PnPFieldToContentType -Field "OfficeLocation" -ContentType "Employee Document"

Lookup Columns

# Create lookup to another list
Add-PnPField -Type Lookup -InternalName "ProjectLookup" `
             -DisplayName "Related Project" `
             -List "Documents" `
             -LookupList "Projects" `
             -LookupField "Title"

# Multi-select lookup
Add-PnPField -Type LookupMulti -InternalName "TeamMembers" `
             -DisplayName "Team Members" `
             -List "Projects" `
             -LookupList "Employees" `
             -LookupField "Title"

Calculated Columns

# Days until expiration
Add-PnPField -Type Calculated -InternalName "DaysUntilExpiration" `
             -DisplayName "Days Until Expiration" `
             -Formula "=[ExpirationDate]-TODAY()" `
             -ResultType Number

# Full name concatenation
Add-PnPField -Type Calculated -InternalName "FullName" `
             -DisplayName "Full Name" `
             -Formula "=[FirstName]&\" \"&[LastName]" `
             -ResultType Text

# Status indicator
Add-PnPField -Type Calculated -InternalName "ExpirationStatus" `
             -DisplayName "Status" `
             -Formula "=IF([ExpirationDate]<TODAY(),\"Expired\",IF([ExpirationDate]<TODAY()+30,\"Expiring Soon\",\"Active\"))" `
             -ResultType Text

Deploying Content Types Across Sites

Hub-Associated Sites

Content types from hub site automatically available to associated sites:

# Register site as hub
Register-PnPHubSite -Site "https://contoso.sharepoint.com/sites/hub"

# Associate site with hub
Add-PnPHubSiteAssociation -Site "https://contoso.sharepoint.com/sites/teamsite" `
                          -HubSite "https://contoso.sharepoint.com/sites/hub"

Content Type Publishing

Enable Content Type Publishing Hub:

# Enable content type publishing (SharePoint Admin)
Set-SPOTenantContentTypeHub -Enabled $true

# Publish content type
$ctx = Get-PnPContext
$ct = Get-PnPContentType -Identity "Contract Document"
$ct.Published = $true
$ct.Update($true)
$ctx.ExecuteQuery()

Script-Based Deployment

# Deploy to multiple sites
$sites = @(
    "https://contoso.sharepoint.com/sites/legal",
    "https://contoso.sharepoint.com/sites/finance",
    "https://contoso.sharepoint.com/sites/hr"
)

$templatePath = ".\ContentTypeTemplate.xml"

foreach ($site in $sites) {
    Write-Host "Deploying to $site..." -ForegroundColor Yellow
    
    Connect-PnPOnline -Url $site -Interactive
    Invoke-PnPSiteTemplate -Path $templatePath
    
    Write-Host "Deployed successfully" -ForegroundColor Green
}

Adding Content Types to Libraries

PowerShell Method

Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/legal" -Interactive

# Enable content types on library
Set-PnPList -Identity "Contracts" -EnableContentTypes $true

# Add content type to library
Add-PnPContentTypeToList -List "Contracts" -ContentType "Contract Document"

# Set as default content type
Set-PnPList -Identity "Contracts" -DefaultContentType "Contract Document"

# Remove default Document content type (optional)
Remove-PnPContentTypeFromList -List "Contracts" -ContentType "Document"

Multiple Content Types in One Library

# Add multiple content types
Add-PnPContentTypeToList -List "Legal Documents" -ContentType "Contract"
Add-PnPContentTypeToList -List "Legal Documents" -ContentType "Policy"
Add-PnPContentTypeToList -List "Legal Documents" -ContentType "Memorandum"

# Users can select content type when uploading

Content Type Column Ordering

# Set column order in content type
$ctx = Get-PnPContext
$ct = Get-PnPContentType -Identity "Contract Document"
$fieldLinks = $ct.FieldLinks

# Define order
$order = @("Title", "ContractNumber", "ContractValue", "ExpirationDate", "ContractStatus")

for ($i = 0; $i -lt $order.Count; $i++) {
    $fieldLink = $fieldLinks | Where-Object { $_.Name -eq $order[$i] }
    if ($fieldLink) {
        $fieldLink.DisplayOrder = $i
        $fieldLink.Update()
    }
}

$ctx.ExecuteQuery()

Content Type Validation

Column Validation

# Expiration date must be in future
Set-PnPField -Identity "ExpirationDate" `
             -Values @{
                 ValidationFormula = "=[ExpirationDate]>TODAY()"
                 ValidationMessage = "Expiration date must be in the future"
             }

# Contract value must be positive
Set-PnPField -Identity "ContractValue" `
             -Values @{
                 ValidationFormula = "=[ContractValue]>0"
                 ValidationMessage = "Contract value must be greater than zero"
             }

List Validation

# Cross-column validation
Set-PnPList -Identity "Contracts" `
            -ValidationFormula "=AND([StartDate]<[EndDate],[ContractValue]>1000)" `
            -ValidationMessage "Start date must be before end date, and value must exceed $1000"

Content Type Workflows

Power Automate Integration

  1. Create flow triggered by content type
  2. Condition: Content Type = "Contract Document"
  3. Actions:
    • Send approval request
    • Update status based on approval
    • Notify stakeholders
    • Create record in external system

Example Flow

{
  "trigger": {
    "type": "When a file is created or modified",
    "site": "https://contoso.sharepoint.com/sites/legal",
    "library": "Contracts"
  },
  "condition": {
    "if": "ContentType equals 'Contract Document'",
    "then": [
      {
        "action": "Start and wait for approval",
        "approvers": "Legal Team",
        "details": "Contract: @{triggerBody()?['Title']}"
      },
      {
        "action": "Update item",
        "field": "ContractStatus",
        "value": "@{if(equals(outputs('Approval')?['outcome'], 'Approve'), 'Active', 'Draft')}"
      }
    ]
  }
}

Best Practices

Design Principles

  1. Plan content type hierarchy - Start with parent types for common metadata
  2. Use site columns - Create reusable columns at site collection level
  3. Meaningful names - Clear, descriptive names for content types and columns
  4. Limit required fields - Only make essential fields required
  5. Test before deployment - Test in dev/test environment first

Governance

# Document content type structure
$contentTypes = Get-PnPContentType

$report = @()
foreach ($ct in $contentTypes) {
    $fields = Get-PnPField -ContentType $ct
    
    $report += [PSCustomObject]@{
        ContentType = $ct.Name
        Group = $ct.Group
        Parent = $ct.Parent
        FieldCount = $fields.Count
        Fields = ($fields.InternalName -join ", ")
    }
}

$report | Export-Csv "ContentTypeAudit.csv" -NoTypeInformation

Migration Considerations

  • Preserve content type IDs during migration
  • Map metadata to new content types
  • Test workflows after migration
  • Document dependencies between content types and apps

Troubleshooting

Content Type Not Appearing in Library

# Check if content types enabled
$list = Get-PnPList -Identity "Documents"
$list.ContentTypesEnabled  # Should be True

# Enable content types
Set-PnPList -Identity "Documents" -EnableContentTypes $true

Inherited Column Can't Be Modified

Columns inherited from parent content type are read-only. Create new column or modify at parent level.

Content Type Changes Not Reflecting

# Update list content types from site content type
Update-PnPContentTypeFromSiteContentType -ContentType "Contract Document"

Key Takeaways

  • Content types ensure consistent metadata across SharePoint
  • Inheritance enables reusable column sets
  • PnP PowerShell and templates enable automated deployment
  • Managed metadata provides controlled vocabularies
  • Content types integrate with Power Automate for workflows
  • Plan hierarchy before implementation

Next Steps

  • Implement content type hub for enterprise-wide publishing
  • Create PnP provisioning templates for repeatable deployments
  • Design retention policies based on content types
  • Integrate sensitivity labels with content types

Additional Resources


Structure your content. Scale your solution.