[Proposal/Exploration] Expose generating JSON Schema that matches the config schema for decoupled admin UIs

Created on 2 December 2022, over 2 years ago
Updated 20 May 2025, 3 days ago

Problem/Motivation

As I was working on various issues to make the config schema for field_storage_config be exposed via this wonderful module (see #3324769: Schema incorrect for config entity types with multi-part IDs (field_storage_config, field_config, entity_view_mode, entity_form_mode, entity_view_display and entity_form_display) β†’ + #3324824: Schema incorrect for config entity "fields" that are Maps and Sequences β†’ ), specifically for https://www.drupal.org/project/field_ui_modern β†’ , I discovered/realized that there is a lot left to be explored WRT JSON Schema and specifically the validation aspects of it, which are covered by https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validati....

This module already exposes things like required, but rarely more than that. Ideally, we should be able to automatically transform both content entity validation constraints and config schema types + validation constraints (see 🌱 [META] Untie config validation from form validation β€” enables validatable Recipes, decoupled admin UIs … Active ).

Relates issues but with a narrower scope: πŸ› Ensure valid "type" values for JSON data types Active , #3174991: Include all bundles in the enum for entity_reference fields β†’ and πŸ› Properly handle nullable values Active

P.S.: I thought https://modern-json-schema.com/json-schema-is-a-constraint-system was a particularly good high-level introduction.

Steps to reproduce

Proposed resolution

Generate a far richer schema, somewhat like this β€” note the use of:

  1. uniqueItems
  2. exclusiveMinimum
  3. required
  4. enum
  5. $drupalUiHint β†’ Drupal-specific addition!
  6. $drupalPluginIdOfType β†’ Drupal-specific addition!
{
  "$schema": "https://json-schema.org/draft/2019-09/hyper-schema",
  "$id": "/jsonapi/field_storage_config/field_storage_config/resource/schema",
  "title": "Field storage",
  "allOf": [
    {
      "type": "object",
      "properties": {
        "type": {
          "$ref": "#/definitions/type"
        },
        "attributes": {
          "$ref": "#/definitions/attributes"
        }
      }
    },
    {
      "$ref": "https://jsonapi.org/schema#/definitions/resource"
    }
  ],
  "definitions": {
    "type": {
      "const": "field_storage_config--field_storage_config"
    },
    "attributes": {
      "type": "object",
      "description": "Entity attributes",
      "required": [
        "entity_type",
        "field_name",
        "field_storage_config_type"
      ],
      "properties": {
        "uuid": {
          "type": "string",
          "title": "UUID",
          "format": "uuid",
          "maxLength": 128,
          "$drupalUiHint": "no-ui"
        },
        "drupal_internal__id": {
          "type": "string",
          "title": "drupal_internal__id",
          "pattern": "^(node|media|taxonomy_term|user)\\.[_a-z]+[_a-z0-9]*$",
          "$comment": "The ID consists of 2 parts: the entity type and the field name."
        },
        "field_name": {
          "type": "string",
          "title": "Field name",
          "pattern": "^[_a-z]+[_a-z0-9]*$",
          "description": "The name of the field."
        },
        "entity_type": {
          "type": "string",
          "title": "Entity Type",
          "enum": [
            "node",
            "media",
            "taxonomy_term",
            "user"
          ],
          "$drupalPluginIdOfType": "content_entity_type",
          "$drupalUiHint": "dropdown"
        },
        "field_storage_config_type": {
          "type": "string",
          "title": "Field type",
          "$comment": "Tricky: This is only not called 'type' because that is a reserved keyword in JSON:API.",
          "description": "Choose a field type.",
          "enum": [
            "uuid",
            "boolean",
            "decimal",
            "email",
            "entity_reference",
            "float",
            "integer",
            "string",
            "string_long",
            "timestamp",
            "comment",
            "datetime",
            "daterange"
          ],
          "$drupalPluginIdOfType": "field_type",
          "$drupalUiHint": "dropdown"
        },
        "langcode": {
          "type": "string",
          "title": "Language",
          "enum": ["en"],
          "default": "en",
          "$drupalUiHint": "no-ui"
        },
        "status": {
          "type": "boolean",
          "title": "Status",
          "default": true
        },
        "dependencies": {
          "type": "object",
          "title": "Dependencies",
          "$drupalUiHint": "no-ui",
          "additionalProperties": false,
          "properties": {
            "config": {
              "type": "array",
              "uniqueItems": true,
              "title": "Configuration dependencies",
              "items": {
                "type": "string",
                "title": "Configuration dependency"
              }
            },
            "content": {
              "type": "array",
              "uniqueItems": true,
              "title": "Content dependencies",
              "items": {
                "type": "string",
                "title": "Content dependency"
              }
            },
            "module": {
              "type": "array",
              "uniqueItems": true,
              "title": "Module dependencies",
              "items": {
                "type": "string",
                "title": "Module dependency"
              }
            },
            "theme": {
              "type": "array",
              "uniqueItems": true,
              "title": "Theme dependencies",
              "items": {
                "type": "string",
                "title": "Theme dependency"
              }
            }
          },
          "enforced": {
            "type": "object",
            "title": "Enforced dependencies",
            "additionalProperties": false,
            "properties": {
              "config": {
                "type": "array",
                "uniqueItems": true,
                "title": "Configuration dependencies",
                "items": {
                  "type": "string",
                  "title": "Configuration dependency"
                }
              },
              "content": {
                "type": "array",
                "uniqueItems": true,
                "title": "Content dependencies",
                "items": {
                  "type": "string",
                  "title": "Content dependency"
                }
              },
              "module": {
                "type": "array",
                "uniqueItems": true,
                "title": "Module dependencies",
                "items": {
                  "type": "string",
                  "title": "Module dependency"
                }
              },
              "theme": {
                "type": "array",
                "uniqueItems": true,
                "title": "Theme dependencies",
                "items": {
                  "type": "string",
                  "title": "Theme dependency"
                }
              }
            }
          }
        },
        "third_party_settings": {
          "type": "array",
          "items": {
            "properties": {
              "third_party_settings": {
                "title": "third_party_settings"
              }
            }
          }
        },
        "locked": {
          "type": "boolean",
          "title": "Locked",
          "description": "Whether the field storage is locked or not. When locked, field settings cannot be changed, no new fields can be created using this storage, and existing fields using this storage cannot be deleted.",
          "default": false,
          "$drupalUiHint": "no-ui-when-creating"
        },
        "cardinality": {
          "type": "integer",
          "title": "Cardinality",
          "description": "Allowed number of values",
          "exclusiveMinimum": 1,
          "default": 1
        },
        "translatable": {
          "type": "boolean",
          "title": "Translatable",
          "default": true
        },
        "indexes": {
          "type": "array",
          "$drupalUiHint": "no-ui",
          "items": {
            "properties": {
              "indexes": {
                "title": "indexes"
              }
            }
          }
        },
        "persist_with_no_fields": {
          "type": "boolean",
          "title": "Whether the field storage should be persisted when orphaned",
          "default": false,
          "$drupalUiHint": "no-ui"
        },
        "custom_storage": {
          "type": "boolean",
          "title": "custom_storage",
          "default": false,
          "$drupalUiHint": "no-ui"
        }
      },
      "additionalProperties": false
    }
  }
}

Remaining tasks

TBD

User interface changes

TBD

API changes

TBD

Data model changes

TBD

🌱 Plan
Status

Needs work

Version

1.0

Component

Code

Created by

πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

Live updates comments and jobs are added and updated live.
  • Contributed project blocker

    It denotes an issue that prevents porting of a contributed project to the stable version of Drupal due to missing APIs, regressions, and so on.

  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024