Skip to main content
AI/MLCrestApps

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.

Stars
13
Source
CrestApps/CrestApps.AgentSkills
Updated
2026-05-29
Slug
CrestApps--CrestApps.AgentSkills--orchardcore-seo
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/CrestApps/CrestApps.AgentSkills/HEAD/plugins/orchardcore/skills/orchardcore-seo/SKILL.md -o .claude/skills/orchardcore-seo.md

Drops the SKILL.md into .claude/skills/orchardcore-seo.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

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

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

SeoMetaPart Configuration

Attaching SeoMetaPart to a Content Type via Migration

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

{
  "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:

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

Robots.txt

Orchard Core does not provide a built-in robots.txt editor. Serve a robots.txt file using one of these approaches:

Static File Approach

Place a robots.txt file in the wwwroot folder of the web project:

User-agent: *
Allow: /
Sitemap: https://{{YourDomain}}/sitemap.xml

Middleware Approach

public sealed class RobotsTxtMiddleware
{
    private readonly RequestDelegate _next;

    public RobotsTxtMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Path.StartsWithSegments("/robots.txt"))
        {
            context.Response.ContentType = "text/plain";
            var siteUrl = $"{context.Request.Scheme}://{context.Request.Host}";
            await context.Response.WriteAsync(
                $"""
                User-agent: *
                Allow: /
                Sitemap: {siteUrl}/sitemap.xml
                """);
            return;
        }

        await _next(context);
    }
}

Register the middleware in Startup:

public sealed class Startup : StartupBase
{
    public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
    {
        app.UseMiddleware<RobotsTxtMiddleware>();
    }
}

Sitemap Configuration

Creating a Sitemap via Recipe

{
  "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:

{
  "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

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:

<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

{
  "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}}"
              }
            ]
          }
        }
      ]
    }
  ]
}