First attempt at structured data in SeoToolkit - #538
Merged
patrickdemooij9 merged 34 commits intoSep 30, 2026
Merged
Conversation
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>
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>
…n, and UI Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/d9bbc074-03ce-475d-bffa-6fb46a2ac480 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/87519cb0-70d5-4ac1-a816-8ee35e9fac41 Co-authored-by: patrickdemooij9 <11466511+patrickdemooij9@users.noreply.github.com>
Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/eab47e70-5014-40a2-a50a-9b9f85c37ea3 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>
Agent-Logs-Url: https://github.com/patrickdemooij9/SeoToolkit.Umbraco/sessions/e4c902cd-0f56-4c10-8e09-1069869d4025 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
There was a problem hiding this comment.
⚠️ 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.NETIThing[]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
deleted the
copilot/add-schema-fields-to-seo-package
branch
September 30, 2026 18:43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.