diff --git a/ogc/bblocks/examples-schema.yaml b/ogc/bblocks/examples-schema.yaml
index c148a8f..076ba2d 100644
--- a/ogc/bblocks/examples-schema.yaml
+++ b/ogc/bblocks/examples-schema.yaml
@@ -1,4 +1,4 @@
-"$schema": https://json-schema.org/draft/2020-12/schema
+$schema: https://json-schema.org/draft/2020-12/schema
 title: OGC Building Blocks examples schema
 $defs:
 
diff --git a/ogc/bblocks/metadata-schema.yaml b/ogc/bblocks/metadata-schema.yaml
index bc1ae12..690db14 100644
--- a/ogc/bblocks/metadata-schema.yaml
+++ b/ogc/bblocks/metadata-schema.yaml
@@ -234,38 +234,6 @@ properties:
       type: string
       pattern: '^https?://.*'
   semanticUplift:
-    description: Additional configuration for semantic uplift of resources
-    type: object
-    properties:
-      additionalSteps:
-        description: List of additional transformation steps to perform before or after the actual semantic uplift
-        type: array
-        items:
-          type: object
-          properties:
-            type:
-              description: |
-                Type of transformation step. The type will also determine whether the step will be run before or after
-                the uplift
-              enum:
-                - jq
-                - shacl
-                - sparql-construct
-                - sparql-update
-            code:
-              description: Code contents of this transformation step (e.g., jq script or SHACL rules file).
-              type: string
-              pattern: '[^\s]+'
-            ref:
-              description: |
-                Location of a file with the code contents of this transformation ste (instead of
-                providing them inline through the "code" property).
-              type: string
-              pattern: '[^\s]+'
-          required:
-            - type
-          oneOf:
-            - required:
-              - code
-            - required:
-              - ref
+    description: Deprecated - configuration should go in semantic-uplift.yaml
+    not: true
+
diff --git a/ogc/bblocks/models.py b/ogc/bblocks/models.py
index 04c299c..111a553 100644
--- a/ogc/bblocks/models.py
+++ b/ogc/bblocks/models.py
@@ -217,6 +217,22 @@ def output_openapi_contents(self):
             self._lazy_properties['output_openapi_contents'] = load_file(self.output_openapi, self.remote_cache_dir)
         return self._lazy_properties['output_openapi_contents']
 
+    @property
+    def semantic_uplift(self):
+        if 'semantic_uplift' not in self._lazy_properties:
+            fn = self.files_path / 'semantic_uplift.yaml'
+            semantic_uplift = {}
+            if fn.is_file():
+                semantic_uplift = load_yaml(fn)
+                if not semantic_uplift:
+                    semantic_uplift = {}
+                try:
+                    jsonschema.validate(semantic_uplift, get_schema('semantic-uplift'))
+                except Exception as e:
+                    raise BuildingBlockError(f'Error validating semantic uplift metadata for {self.identifier}') from e
+            self._lazy_properties['semantic_uplift'] = semantic_uplift
+        return self._lazy_properties['semantic_uplift']
+
     def get_extra_test_resources(self) -> Generator[dict, None, None]:
         extra_tests_file = self.files_path / 'tests.yaml'
         if extra_tests_file.is_file():
diff --git a/ogc/bblocks/postprocess.py b/ogc/bblocks/postprocess.py
index 1dc1cc6..f58f625 100644
--- a/ogc/bblocks/postprocess.py
+++ b/ogc/bblocks/postprocess.py
@@ -191,12 +191,11 @@ def do_postprocess(bblock: BuildingBlock, light: bool = False) -> bool:
                     transform['ref'] = urljoin(bblock.metadata['sourceFiles'], transform['ref'])
                     bblock.metadata['transforms'].append({k: v for k, v in transform.items() if k != 'code'})
 
-            if bblock.semanticUplift and bblock.semanticUplift.get('additionalSteps'):
-                for step in bblock.semanticUplift['additionalSteps']:
-                    if step.get('ref'):
-                        step['ref'] = PathOrUrl(bblock.files_path).resolve_ref(step['ref']).with_base_url(
-                            base_url, cwd if base_url else output_file_root
-                        )
+            for step in bblock.semantic_uplift.get('additionalSteps', ()):
+                if step.get('ref'):
+                    step['ref'] = PathOrUrl(bblock.files_path).resolve_ref(step['ref']).with_base_url(
+                        base_url, cwd if base_url else output_file_root
+                    )
 
         if not light and (not steps or 'doc' in steps):
             print(f"  > Generating documentation for {bblock.identifier}", file=sys.stderr)
diff --git a/ogc/bblocks/semantic-uplift-schema.yaml b/ogc/bblocks/semantic-uplift-schema.yaml
new file mode 100644
index 0000000..2ef505e
--- /dev/null
+++ b/ogc/bblocks/semantic-uplift-schema.yaml
@@ -0,0 +1,36 @@
+$schema: https://json-schema.org/draft/2020-12/schema
+title: Additional configuration for semantic uplift of resources
+type: object
+properties:
+  additionalSteps:
+    description: List of additional transformation steps to perform before or after the actual semantic uplift
+    type: array
+    items:
+      type: object
+      properties:
+        type:
+          description: |
+            Type of transformation step. The type will also determine whether the step will be run before or after
+            the uplift
+          enum:
+            - jq
+            - shacl
+            - sparql-construct
+            - sparql-update
+        code:
+          description: Code contents of this transformation step (e.g., jq script or SHACL rules file).
+          type: string
+          pattern: '[^\s]+'
+        ref:
+          description: |
+            Location of a file with the code contents of this transformation ste (instead of
+            providing them inline through the "code" property).
+          type: string
+          pattern: '[^\s]+'
+      required:
+        - type
+      oneOf:
+        - required:
+          - code
+        - required:
+          - ref
\ No newline at end of file
diff --git a/ogc/bblocks/templates/json-full/index.json b/ogc/bblocks/templates/json-full/index.json
index da28f99..70301e1 100644
--- a/ogc/bblocks/templates/json-full/index.json
+++ b/ogc/bblocks/templates/json-full/index.json
@@ -13,6 +13,8 @@ if bblock.example_prefixes:
     output['examplePrefixes'] = bblock.example_prefixes
 if bblock.annotated_schema:
     output['annotatedSchema'] = bblock.annotated_schema_contents
+if bblock.semantic_uplift:
+    output['semanticUplift'] = bblock.semantic_uplift
 if git_repo:
     output['gitRepository'] = git_repo
     output['gitPath'] = git_path
diff --git a/ogc/bblocks/validation/uplift.py b/ogc/bblocks/validation/uplift.py
index 564bc21..f5a5488 100644
--- a/ogc/bblocks/validation/uplift.py
+++ b/ogc/bblocks/validation/uplift.py
@@ -16,25 +16,24 @@ def __init__(self, bblock: BuildingBlock):
         self.bblock_files = PathOrUrl(bblock.files_path)
 
     def _run_steps(self, stage: str, report: ValidationReportItem, input_data: Any, *args):
-        if self.bblock.semanticUplift and self.bblock.semanticUplift.get('additionalSteps'):
-            for idx, step in enumerate(self.bblock.semanticUplift['additionalSteps']):
-                func_name = f"_{stage}_{step['type'].replace('-', '_')}"
-                if hasattr(self, func_name):
-                    code = step.get('code')
-                    report_source = 'inline code'
-                    if not code:
-                        if step.get('ref'):
-                            code = load_file(self.bblock_files.resolve_ref(step['ref']), self.bblock.remote_cache_dir)
-                            report_source = step['ref']
-                        else:
-                            raise ValueError(
-                                f'No code or ref found for semanticUplift step {idx} in {self.bblock.identifier}')
-                    step['stage'] = stage
-                    report.add_entry(ValidationReportEntry(
-                        section=ValidationReportSection.SEMANTIC_UPLIFT,
-                        message=f"Running {stage}-uplift {step['type']} transform step from {report_source}",
-                    ))
-                    input_data = getattr(self, func_name)(code, input_data, *args)
+        for idx, step in enumerate(self.bblock.semantic_uplift.get('additionalSteps', ())):
+            func_name = f"_{stage}_{step['type'].replace('-', '_')}"
+            if hasattr(self, func_name):
+                code = step.get('code')
+                report_source = 'inline code'
+                if not code:
+                    if step.get('ref'):
+                        code = load_file(self.bblock_files.resolve_ref(step['ref']), self.bblock.remote_cache_dir)
+                        report_source = step['ref']
+                    else:
+                        raise ValueError(
+                            f'No code or ref found for semanticUplift step {idx} in {self.bblock.identifier}')
+                step['stage'] = stage
+                report.add_entry(ValidationReportEntry(
+                    section=ValidationReportSection.SEMANTIC_UPLIFT,
+                    message=f"Running {stage}-uplift {step['type']} transform step from {report_source}",
+                ))
+                input_data = getattr(self, func_name)(code, input_data, *args)
         return input_data
 
     def pre_uplift(self, report: ValidationReportItem, json_doc: dict | list):