Skip to content

First attempt at structured data in SeoToolkit - #538

Merged
patrickdemooij9 merged 34 commits into
dev/Umbraco17from
copilot/add-schema-fields-to-seo-package
Sep 30, 2026
Merged

patrickdemooij9 merged 34 commits into
dev/Umbraco17from
copilot/add-schema-fields-to-seo-package

Conversation

@patrickdemooij9

Copy link
Copy Markdown
Owner

No description provided.

patrickdemooij9 and others added 21 commits March 22, 2026 16:51
Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/51b4741c-0cf6-4891-8dd7-82877e03c18b

Co-authored-by: patrickdemooij9 <11466511+patrickdemooij9@users.noreply.github.com>
… property value input

Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/b2c4c012-d7fc-452d-a6f2-9c5277d2c42d

Co-authored-by: patrickdemooij9 <11466511+patrickdemooij9@users.noreply.github.com>
Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/b2c4c012-d7fc-452d-a6f2-9c5277d2c42d

Co-authored-by: patrickdemooij9 <11466511+patrickdemooij9@users.noreply.github.com>
…ce dropdown, doc type ownerKey, remove edit dropdown

Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/2a667a7c-49cf-4667-b2aa-2d8b3cdc5c40

Co-authored-by: patrickdemooij9 <11466511+patrickdemooij9@users.noreply.github.com>
…e review feedback

Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/2a667a7c-49cf-4667-b2aa-2d8b3cdc5c40

Co-authored-by: patrickdemooij9 <11466511+patrickdemooij9@users.noreply.github.com>
Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/97b788e3-258e-4b77-b106-e3f0e189e350

Co-authored-by: patrickdemooij9 <11466511+patrickdemooij9@users.noreply.github.com>
- Add Config dictionary to SchemaProperty for passing editor config to umb-property
- Add allowedSchemas config support to SchemaEditorPropertyEditor for filtering schema types
- Fix ownerKey for nested schema editors by pre-generating GUID client-side
- Fix 'Address Country' display name in PostalAddressSchemaResolver
- Wrap JsonDocument.Parse in try-catch in RawJsonSchemaResolver

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Not ready to approve

There are critical correctness and security issues (modal manifest build break, migration data-loss risk, and unsafe JSON-LD rendering) that should be addressed before approval.

Pull request overview

This PR introduces a first end-to-end implementation of structured data (JSON-LD) in the MetaFields module, including a new backoffice schema editor UI, server-side persistence for schema entries, and runtime conversion/rendering to JSON-LD scripts.

Changes:

  • Added a new Umbraco backoffice property editor (SeoToolkit.SchemaEditor) with modal-driven flows to create/edit/pick schema entries.
  • Implemented server-side schema entry storage (DB table + repository/service + migrations) and a backoffice API for schema types/entries.
  • Added initial schema resolvers (Organization, PostalAddress, Raw JSON) and a value converter to resolve stored entries into Schema.NET IThing[] for rendering.
File summaries
File Description
src/SeoToolkit.Umbraco.MetaFields/assets/src/propertyEditors/SchemaEditorPropertyEditor.element.ts New schema editor property editor UI (list + add/edit flows).
src/SeoToolkit.Umbraco.MetaFields/assets/src/popups/SchemaSourceModal.element.ts Modal to choose creating a new schema vs selecting an existing entry.
src/SeoToolkit.Umbraco.MetaFields/assets/src/popups/SchemaPropertyModal.element.ts Modal to edit schema properties, including reference-from-context support.
src/SeoToolkit.Umbraco.MetaFields/assets/src/popups/SchemaPickerModal.element.ts Modal to pick a schema type.
src/SeoToolkit.Umbraco.MetaFields/assets/src/manifests/PropertyEditorManifests.ts Registers the new SeoToolkit.SchemaEditor property editor UI.
src/SeoToolkit.Umbraco.MetaFields/assets/src/manifests/ModalManifests.ts Registers schema modals (and should keep AI suggestions modal registration intact).
src/SeoToolkit.Umbraco.MetaFields/assets/src/dataAccess/SchemaEntrySource.ts Client data access for schema entry CRUD endpoints.
src/SeoToolkit.Umbraco.MetaFields/assets/src/dataAccess/MetaFieldsSchemaSource.ts Client data access for available schema types.
src/SeoToolkit.Umbraco.MetaFields/assets/src/api/types.gen.ts Generated TS API types for schema endpoints/models.
src/SeoToolkit.Umbraco.MetaFields/assets/src/api/sdk.gen.ts Generated TS SDK methods for schema endpoints.
src/SeoToolkit.Umbraco.MetaFields.Core/Services/SchemaEntryService/SchemaEntryService.cs Adds cached service layer for schema entries.
src/SeoToolkit.Umbraco.MetaFields.Core/Services/SchemaEntryService/ISchemaEntryService.cs Service interface for schema entry operations.
src/SeoToolkit.Umbraco.MetaFields.Core/SeoToolkit.Umbraco.MetaFields.Core.csproj Adds Schema.NET dependency.
src/SeoToolkit.Umbraco.MetaFields.Core/Repositories/SchemaEntryRepository/SchemaEntryDatabaseRepository.cs NPoco repository for schema entry persistence.
src/SeoToolkit.Umbraco.MetaFields.Core/Repositories/SchemaEntryRepository/ISchemaEntryRepository.cs Repository interface for schema entries.
src/SeoToolkit.Umbraco.MetaFields.Core/Models/SeoField/SeoSchemaField.cs Switches schema field to IThing[] and renders JSON-LD script tags.
src/SeoToolkit.Umbraco.MetaFields.Core/Models/SchemaEntry/ViewModels/SchemaEntryViewModel.cs Backoffice view model for schema entries.
src/SeoToolkit.Umbraco.MetaFields.Core/Models/SchemaEntry/PostModels/SchemaEntryPostModel.cs Backoffice post model for creating/updating schema entries.
src/SeoToolkit.Umbraco.MetaFields.Core/Models/SchemaEntry/Database/SchemaEntryEntity.cs DB entity mapping for schema entry table.
src/SeoToolkit.Umbraco.MetaFields.Core/Models/SchemaEntry/Business/SchemaEntryDto.cs Business DTO for schema entries.
src/SeoToolkit.Umbraco.MetaFields.Core/Models/SchemaEditor/SchemaTypeViewModel.cs Backoffice view model for schema types + their properties.
src/SeoToolkit.Umbraco.MetaFields.Core/Models/SchemaEditor/SchemaEditorModel.cs Models for schema editor value + property values.
src/SeoToolkit.Umbraco.MetaFields.Core/Migrations/SchemaEntryTableMigration.cs Creates schema entry table and migrates legacy schema values.
src/SeoToolkit.Umbraco.MetaFields.Core/Migrations/SchemaEntryDisplayNameMigration.cs Adds DisplayName column if missing.
src/SeoToolkit.Umbraco.MetaFields.Core/Migrations/MetaFieldsMigrationPlan.cs Adds schema-entry migrations to the MetaFields migration plan.
src/SeoToolkit.Umbraco.MetaFields.Core/Controllers/SchemaController.cs Backoffice API for schema types and schema entry CRUD/reuse.
src/SeoToolkit.Umbraco.MetaFields.Core/Controllers/MetaFieldsSettingsController.cs Injects schema editor config (ownerType/nodeGuid) for doc type settings.
src/SeoToolkit.Umbraco.MetaFields.Core/Controllers/MetaFieldsController.cs Injects schema editor config (ownerType/nodeGuid/documentTypeKey) for content editing.
src/SeoToolkit.Umbraco.MetaFields.Core/Composers/MetaFieldsComposer.cs Registers schema services/repos, converters, and schema resolvers.
src/SeoToolkit.Umbraco.MetaFields.Core/Common/SeoFieldEditEditors/SeoSchemaEditEditor.cs New edit editor mapping to SeoToolkit.SchemaEditor.
src/SeoToolkit.Umbraco.MetaFields.Core/Common/SchemaResolvers/SchemaProperty.cs Defines schema property metadata (editor, converter, config).
src/SeoToolkit.Umbraco.MetaFields.Core/Common/SchemaResolvers/RawJsonThing.cs Wrapper Thing to emit raw JSON for JSON-LD.
src/SeoToolkit.Umbraco.MetaFields.Core/Common/SchemaResolvers/RawJsonSchemaResolver.cs Resolver that validates and returns raw JSON schema.
src/SeoToolkit.Umbraco.MetaFields.Core/Common/SchemaResolvers/PostalAddressSchemaResolver.cs Resolver for PostalAddress schema.
src/SeoToolkit.Umbraco.MetaFields.Core/Common/SchemaResolvers/OrganizationSchemaResolver.cs Resolver for Organization schema (including nested address).
src/SeoToolkit.Umbraco.MetaFields.Core/Common/SchemaResolvers/ISchemaResolver.cs Interface for schema resolvers.
src/SeoToolkit.Umbraco.MetaFields.Core/Common/Converters/SeoValueConverters/SchemaSeoValueConverter.cs Converts stored schema IDs into resolved IThing[] at runtime.
src/SeoToolkit.Umbraco.MetaFields.Core/Common/Converters/EditorConverters/SchemaEditorValueConverter.cs Converts editor value ↔ database GUID list for schema selections.
src/SeoToolkit.Umbraco.MetaFields.Core/Collections/SchemaResolverCollectionBuilder.cs Collection builder for registering schema resolvers.
src/SeoToolkit.Umbraco.MetaFields.Core/Collections/SchemaResolverCollection.cs Resolver collection implementation used by runtime/converters.

Copilot's findings

  • Files reviewed: 40/40 changed files
  • Comments generated: 9

Note

Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.

Comment on lines 10 to +14

const AIGenerateSuggestionsModalManifest: ManifestModal = {
const SchemaPickerModalManifest : ManifestModal = {
type: 'modal',
alias: ST_AI_SUGGESTIONS_MODAL,
name: 'SeoToolkit AI Suggestions',
js: () => import('../popups/MetaFieldsAISuggestionsModal.element'),
};
alias: 'seoToolkit.modal.schemaPicker',
name: 'SeoToolkit SchemaPicker',
Comment on lines +78 to +100
private static bool IsAlreadyMigrated(string userValue)
{
try
{
var token = JToken.Parse(userValue);
if (token is JArray arr && arr.Count > 0)
{
// If the first element is a GUID string, it's already migrated
var first = arr[0];
if (first.Type == JTokenType.String && Guid.TryParse(first.Value<string>(), out _))
return true;

// If the first element is an empty array or the array is empty, it's migrated
if (arr.Count == 0)
return true;
}
return false;
}
catch
{
return false;
}
}
Comment on lines +31 to +33
var schemaTags = schemas
.Where(schema => schema is not null)
.Select(schema => $"<script type=\"application/ld+json\">{schema}</script>");
Comment on lines +26 to +35
try
{
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.ValueKind != JsonValueKind.Object && doc.RootElement.ValueKind != JsonValueKind.Array)
return null;
}
catch (JsonException)
{
return null;
}
Comment on lines +39 to +47
// Additively combine with document type schemas
IEnumerable<SchemaEntryDto> allEntries = contentEntries;
if (currentContent?.ContentType?.Key is Guid docTypeKey)
{
var docTypeEntries = _schemaEntryService.GetAll("documentType", docTypeKey);
allEntries = docTypeEntries.Concat(contentEntries);
}

var schemas = new List<IThing>();
Comment on lines +213 to +215
this._value = {
schemas: [...this._value.schemas, sourceResult.entryId],
};
result.data.id,
result.data,
);
this._value = { schemas: [...this._value.schemas, result.data.id] };
Comment on lines +121 to +125
const value = propertyValue ?? {
value: undefined,
isReference: false,
referenceKey: undefined,
};
Comment on lines +44 to +48
public IEnumerable<SchemaEntryDto> GetByIds(IEnumerable<Guid> ids)
{
var idList = ids.ToList();
if (idList.Count == 0)
return Enumerable.Empty<SchemaEntryDto>();
@patrickdemooij9
patrickdemooij9 changed the base branch from dev/main to dev/Umbraco17 September 21, 2026 20:43
@patrickdemooij9
patrickdemooij9 merged commit 538fafb into dev/Umbraco17 Sep 30, 2026
1 check failed
@patrickdemooij9
patrickdemooij9 deleted the copilot/add-schema-fields-to-seo-package branch September 30, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants