# Orchardcore SEO

> Skill for configuring SEO in Orchard Core. Covers SeoMetaPart for meta tags and social sharing, sitemap generation and indexing, canonical URLs, robots.txt, structured data with JSON-LD, and SEO-friendly URL patterns. Use this skill when requests mention Orchard Core SEO, Configure SEO Features, Enabling SEO and Sitemap Features, SeoMetaPart Configuration, Attaching SeoMetaPart to a Content Type via Migration, Attaching SeoMetaPart via Recipe, or closely related Orchard Core implementation, setup, extension, or troubleshooting work. Strong matches include work with OrchardCore.Seo, OrchardCore.Sitemaps, OrchardCore.Autoroute, OrchardCore.ContentManagement.Metadata, OrchardCore.Data.Migration, OrchardCore.Seo.Models, OrchardCore.Autoroute.Models. It also helps with seo examples, Attaching SeoMetaPart to a Content Type via Migration, Attaching SeoMetaPart via Recipe, SeoMetaPart Fields, plus the code patterns, admin flows, recipe steps, and referenced examples captured in this skill.

- Skill: `crestapps/orchardcore-seo` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add crestapps/orchardcore-seo`
- Raw SKILL.md: https://api.skillmd.com/api/skills/crestapps/orchardcore-seo/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- License: Apache-2.0
- Author: CrestApps (https://skillmd.com/u/crestapps)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/crestapps/orchardcore-seo

---


# Orchard Core SEO - Prompt Templates

## Configure SEO Features

You are an Orchard Core expert. Generate code and configuration for SEO optimization in Orchard Core.

### Guidelines

- The `OrchardCore.Seo` module provides `SeoMetaPart` for managing meta tags, Open Graph, and Twitter Card metadata per content item.
- The `OrchardCore.Sitemaps` module handles XML sitemap generation, sitemap indexes, and custom sitemap sources.
- Attach `SeoMetaPart` to any routable content type that needs meta tag control.
- Use `AutoroutePart` alongside `SeoMetaPart` for SEO-friendly URL patterns.
- Configure canonical URLs to prevent duplicate content penalties.
- Use recipe steps to provision sitemaps, sitemap indexes, and SEO settings across environments.
- Always seal classes.

### Enabling SEO and Sitemap Features

```json
{
  "steps": [
    {
      "name": "Feature",
      "enable": [
        "OrchardCore.Seo",
        "OrchardCore.Sitemaps",
        "OrchardCore.Autoroute"
      ],
      "disable": []
    }
  ]
}
```

## SeoMetaPart Configuration

### Attaching SeoMetaPart to a Content Type via Migration

```csharp
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.ContentManagement.Metadata.Settings;
using OrchardCore.Data.Migration;
using OrchardCore.Seo.Models;

public sealed class SeoMigrations : DataMigration
{
    private readonly IContentDefinitionManager _contentDefinitionManager;

    public SeoMigrations(IContentDefinitionManager contentDefinitionManager)
    {
        _contentDefinitionManager = contentDefinitionManager;
    }

    public async Task<int> CreateAsync()
    {
        await _contentDefinitionManager.AlterTypeDefinitionAsync("{{ContentTypeName}}", type => type
            .WithPart("TitlePart", part => part
                .WithPosition("0")
            )
            .WithPart("AutoroutePart", part => part
                .WithPosition("1")
                .WithSettings(new AutoroutePartSettings
                {
                    AllowCustomPath = true,
                    Pattern = "{{ Model.ContentItem | display_text | slugify }}"
                })
            )
            .WithPart("SeoMetaPart", part => part
                .WithPosition("5")
            )
        );

        return 1;
    }
}
```

### Attaching SeoMetaPart via Recipe

```json
{
  "steps": [
    {
      "name": "ContentDefinition",
      "ContentTypes": [
        {
          "Name": "{{ContentTypeName}}",
          "DisplayName": "{{DisplayName}}",
          "Settings": {
            "ContentTypeSettings": {
              "Creatable": true,
              "Listable": true,
              "Draftable": true
            }
          },
          "ContentTypePartDefinitionRecords": [
            {
              "PartName": "TitlePart",
              "Name": "TitlePart",
              "Settings": {
                "ContentTypePartSettings": {
                  "Position": "0"
                }
              }
            },
            {
              "PartName": "AutoroutePart",
              "Name": "AutoroutePart",
              "Settings": {
                "ContentTypePartSettings": {
                  "Position": "1"
                },
                "AutoroutePartSettings": {
                  "AllowCustomPath": true,
                  "Pattern": "{{ Model.ContentItem | display_text | slugify }}"
                }
              }
            },
            {
              "PartName": "SeoMetaPart",
              "Name": "SeoMetaPart",
              "Settings": {
                "ContentTypePartSettings": {
                  "Position": "5"
                }
              }
            }
          ]
        }
      ]
    }
  ]
}
```

### SeoMetaPart Fields

| Field | Description |
|-------|-------------|
| `PageTitle` | Overrides the default `<title>` tag for the page. |
| `MetaDescription` | Sets the `<meta name="description">` tag content. |
| `MetaKeywords` | Sets the `<meta name="keywords">` tag content. |
| `Canonical` | Sets a custom canonical URL (`<link rel="canonical">`). |
| `MetaRobots` | Controls indexing directives (e.g., `noindex`, `nofollow`). |
| `CustomMetaTags` | Allows adding arbitrary custom `<meta>` tags. |
| `OpenGraphType` | Sets the `og:type` value (e.g., `website`, `article`). |
| `OpenGraphTitle` | Sets the `og:title` value. |
| `OpenGraphDescription` | Sets the `og:description` value. |
| `OpenGraphImage` | Sets the `og:image` media path. |
| `TwitterCard` | Sets `twitter:card` type (`summary`, `summary_large_image`). |
| `TwitterTitle` | Sets the `twitter:title` value. |
| `TwitterDescription` | Sets the `twitter:description` value. |
| `TwitterImage` | Sets the `twitter:image` media path. |
| `TwitterCreator` | Sets the `twitter:creator` handle (e.g., `@username`). |
| `TwitterSite` | Sets the `twitter:site` handle. |
| `GoogleSchema` | Stores JSON-LD structured data for the page. |

## Canonical URLs

- When `SeoMetaPart.Canonical` is left empty, Orchard Core automatically uses the URL generated by `AutoroutePart`.
- Set a custom canonical URL only when multiple URLs resolve to the same content (e.g., query string variations, pagination).
- Canonical URLs must be absolute (include scheme and host).

## Social Media Meta Tags

### Open Graph Configuration

Open Graph tags control how content appears when shared on Facebook, LinkedIn, and other platforms. The `SeoMetaPart` editor exposes fields for `og:type`, `og:title`, `og:description`, and `og:image`. If Open Graph fields are left empty, the part falls back to the page title and meta description.

### Twitter Card Configuration

Twitter Card tags define how content appears in Twitter/X post previews. Set the `TwitterCard` field to either `summary` (small thumbnail) or `summary_large_image` (large banner). If Twitter-specific fields are left empty, they fall back to Open Graph values.

## Structured Data with JSON-LD

The `GoogleSchema` field on `SeoMetaPart` accepts raw JSON-LD. This structured data is rendered in a `<script type="application/ld+json">` block in the page head.

### Common Structured Data Pattern

Use the `GoogleSchema` field on a content item to provide structured data:

```json
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "{{ArticleTitle}}",
  "description": "{{ArticleDescription}}",
  "author": {
    "@type": "Person",
    "name": "{{AuthorName}}"
  },
  "datePublished": "{{PublishedDate}}",
  "dateModified": "{{ModifiedDate}}",
  "image": "{{ImageUrl}}"
}
```

## Robots.txt

`OrchardCore.Seo` provides a built-in dynamic `robots.txt` editor at
**Settings → Search → Search Engine Optimization → Robots**. It configures the
`RobotsSettings` site-settings object:

| Property | Purpose |
|---|---|
| `AllowAllAgents` | Writes `User-agent: *`. |
| `DisallowAdmin` | Adds a `Disallow` rule for the configured admin URL prefix. |
| `AdditionalRules` | Appends additional robots directives verbatim. |

A physical `robots.txt` file takes precedence over the settings-generated
response. When Sitemaps is also enabled, its sitemap URLs are contributed by
the sitemap robots provider.

## Sitemap Configuration

### Creating a Sitemap via Recipe

```json
{
  "steps": [
    {
      "name": "Sitemaps",
      "Sitemaps": [
        {
          "SitemapId": "{{UniqueSitemapId}}",
          "Name": "{{SitemapName}}",
          "Path": "sitemap.xml",
          "Enabled": true,
          "SitemapSources": [
            {
              "Type": "SitemapSource",
              "ContentTypes": [
                {
                  "ContentTypeName": "{{ContentTypeName}}",
                  "ChangeFrequency": "daily",
                  "Priority": 5
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

### Sitemap Change Frequency Values

| Value | Description |
|-------|-------------|
| `always` | Content changes on every access. |
| `hourly` | Updated every hour. |
| `daily` | Updated once a day. |
| `weekly` | Updated once a week. |
| `monthly` | Updated once a month. |
| `yearly` | Updated once a year. |
| `never` | Archived content that will not change. |

### Creating a Sitemap Index

A sitemap index aggregates multiple sitemaps into a single entry point:

```json
{
  "steps": [
    {
      "name": "SitemapIndexes",
      "SitemapIndexes": [
        {
          "SitemapIndexId": "{{UniqueIndexId}}",
          "Name": "Sitemap Index",
          "Path": "sitemap.xml",
          "Enabled": true,
          "ContainedSitemapIds": [
            "{{SitemapId1}}",
            "{{SitemapId2}}"
          ]
        }
      ]
    }
  ]
}
```

## SEO-Friendly URL Patterns with AutoroutePart

### Common URL Pattern Templates

| Pattern | Example Output |
|---------|---------------|
| `{{ Model.ContentItem \| display_text \| slugify }}` | `my-blog-post` |
| `blog/{{ Model.ContentItem \| display_text \| slugify }}` | `blog/my-blog-post` |
| `{{ Model.ContentItem.CreatedUtc \| date: '%Y/%m' }}/{{ Model.ContentItem \| display_text \| slugify }}` | `2025/01/my-blog-post` |
| `products/{{ Model.ContentItem.Content.ProductPart.Category.Text \| slugify }}/{{ Model.ContentItem \| display_text \| slugify }}` | `products/electronics/wireless-headphones` |

### Configuring AutoroutePart Settings in Migration

```csharp
using OrchardCore.Autoroute.Models;
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.Data.Migration;

public sealed class AutorouteMigrations : DataMigration
{
    private readonly IContentDefinitionManager _contentDefinitionManager;

    public AutorouteMigrations(IContentDefinitionManager contentDefinitionManager)
    {
        _contentDefinitionManager = contentDefinitionManager;
    }

    public async Task<int> CreateAsync()
    {
        await _contentDefinitionManager.AlterTypeDefinitionAsync("BlogPost", type => type
            .WithPart("AutoroutePart", part => part
                .WithSettings(new AutoroutePartSettings
                {
                    AllowCustomPath = true,
                    AllowUpdatePath = true,
                    Pattern = "blog/{{ Model.ContentItem | display_text | slugify }}",
                    ShowHomepageOption = true
                })
            )
        );

        return 1;
    }
}
```

## Page Title Customization

The `PageTitle` field in `SeoMetaPart` overrides the browser tab title. When left empty, Orchard Core uses the `TitlePart` display text combined with the site name. The title is rendered using the page title tag helper.

### Overriding Title Format in a Theme

Use the `<page-title>` tag helper in the layout to control the title format:

```html
<page-title separator=" | " site-name="true" position="AfterTitle" />
```

| Attribute | Description |
|-----------|-------------|
| `separator` | Character(s) between page title and site name. |
| `site-name` | Whether to include the site name. |
| `position` | `AfterTitle` appends site name; `BeforeTitle` prepends it. |

## Custom Meta Tags via SeoMetaPart

`SeoMetaPart` supports adding arbitrary meta tags through the `CustomMetaTags` collection. Each entry specifies a `Name`, `Content`, `HttpEquiv`, and `Charset` value.

### Adding Custom Meta Tags in a Content Item Recipe

```json
{
  "steps": [
    {
      "name": "Content",
      "data": [
        {
          "ContentType": "{{ContentTypeName}}",
          "ContentItemId": "{{ContentItemId}}",
          "DisplayText": "{{PageTitle}}",
          "TitlePart": {
            "Title": "{{PageTitle}}"
          },
          "SeoMetaPart": {
            "PageTitle": "{{CustomBrowserTitle}}",
            "MetaDescription": "{{Description}}",
            "MetaKeywords": "{{Keywords}}",
            "Canonical": "https://{{YourDomain}}/{{CanonicalPath}}",
            "MetaRobots": "index, follow",
            "OpenGraphType": "article",
            "OpenGraphTitle": "{{OgTitle}}",
            "OpenGraphDescription": "{{OgDescription}}",
            "OpenGraphImage": {
              "Paths": ["{{OgImagePath}}"]
            },
            "TwitterCard": "summary_large_image",
            "TwitterTitle": "{{TwitterTitle}}",
            "TwitterDescription": "{{TwitterDescription}}",
            "TwitterImage": {
              "Paths": ["{{TwitterImagePath}}"]
            },
            "TwitterCreator": "@{{TwitterHandle}}",
            "TwitterSite": "@{{SiteTwitterHandle}}",
            "GoogleSchema": "{\"@context\":\"https://schema.org\",\"@type\":\"WebPage\",\"name\":\"{{PageTitle}}\"}",
            "CustomMetaTags": [
              {
                "Name": "author",
                "Content": "{{AuthorName}}"
              },
              {
                "Name": "theme-color",
                "Content": "#{{HexColor}}"
              }
            ]
          }
        }
      ]
    }
  ]
}
```

