diff --git a/packages/frontend-plugin-api/src/schema/createPortableSchema.ts b/packages/frontend-plugin-api/src/schema/createPortableSchema.ts
new file mode 100644
index 0000000000..877cbd2ef1
--- /dev/null
+++ b/packages/frontend-plugin-api/src/schema/createPortableSchema.ts
@@ -0,0 +1,454 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// -----------------------------------------------------------------------
+// Mock / exploration file — not wired into anything.
+//
+// Shows what a central, reusable Standard-Schema-based utility could
+// look like, covering:
+//
+// 1. Public types for APIs that accept per-field schema records
+// 2. Type-level inference (output & input) without any zod types
+// 3. Runtime validation with good error messages
+// 4. JSON Schema generation from any supported source
+// 5. Backward compat with the current (zImpl) => ZodType factory form
+// 6. Merging schemas from different sources (blueprint + override)
+// -----------------------------------------------------------------------
+
+import { JsonObject } from '@backstage/types';
+import { z as zodV3, type ZodType } from 'zod/v3';
+import zodToJsonSchema from 'zod-to-json-schema';
+import { PortableSchema } from './types';
+
+// ---------------------------------------------------------------------------
+// Standard Schema V1 — inlined from https://standardschema.dev
+//
+// This is the cross-library interface implemented by zod v3.25+, zod v4,
+// valibot, arktype, and others. It's designed to be inlined: "Libraries
+// wishing to implement the spec can copy/paste the code block below into
+// their codebase" — no npm dependency required.
+//
+// See: https://github.com/standard-schema/standard-schema
+// ---------------------------------------------------------------------------
+
+/** The Standard Schema interface. */
+interface StandardSchemaV1 {
+ readonly '~standard': StandardSchemaV1.Props ;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-namespace
+namespace StandardSchemaV1 {
+ export interface Props {
+ readonly version: 1;
+ readonly vendor: string;
+ readonly validate: (
+ value: unknown,
+ options?: Options | undefined,
+ ) => Result | Promise>;
+ readonly types?: Types | undefined;
+ }
+
+ export type Result = SuccessResult | FailureResult;
+
+ export interface SuccessResult {
+ readonly value: Output;
+ readonly issues?: undefined;
+ }
+
+ export interface Options {
+ readonly libraryOptions?: Record | undefined;
+ }
+
+ export interface FailureResult {
+ readonly issues: ReadonlyArray;
+ }
+
+ export interface Issue {
+ readonly message: string;
+ readonly path?: ReadonlyArray | undefined;
+ }
+
+ export interface PathSegment {
+ readonly key: PropertyKey;
+ }
+
+ export interface Types {
+ readonly input: Input;
+ readonly output: Output;
+ }
+
+ export type InferInput = NonNullable<
+ Schema['~standard']['types']
+ >['input'];
+
+ export type InferOutput = NonNullable<
+ Schema['~standard']['types']
+ >['output'];
+}
+
+// ---------------------------------------------------------------------------
+// Public types
+// ---------------------------------------------------------------------------
+
+/**
+ * A single config field schema. Accepts any Standard Schema implementation,
+ * or the legacy factory form for backward compat and zod-based composition.
+ */
+type ConfigFieldSchema = StandardSchemaV1 | ((zImpl: typeof zodV3) => ZodType);
+
+/** A record of per-field config schemas. */
+type ConfigSchemaRecord = { [key: string]: ConfigFieldSchema };
+
+/** Resolves a field entry to its StandardSchemaV1 form for type inference. */
+type ResolveField = T extends (
+ ...args: any[]
+) => infer R
+ ? R extends StandardSchemaV1
+ ? R
+ : never
+ : T extends StandardSchemaV1
+ ? T
+ : never;
+
+/**
+ * Infers the parsed output type of a config schema record.
+ * Replaces: `{ [key in keyof T]: z.infer> }`
+ */
+type InferConfigOutput = {
+ [K in keyof T]: StandardSchemaV1.InferOutput>;
+};
+
+/**
+ * Infers the raw input type (before defaults/transforms) of a config
+ * schema record.
+ * Replaces: `z.input>`
+ */
+type InferConfigInput = {
+ [K in keyof T]: StandardSchemaV1.InferInput>;
+};
+
+// ---------------------------------------------------------------------------
+// The PortableSchema — now with per-field tracking for mergeability
+// ---------------------------------------------------------------------------
+
+/**
+ * Internally, each PortableSchema tracks which keys it owns and how to
+ * validate each one individually. This is the unit of composition: when a
+ * blueprint defines config fields and an override adds more, each produces
+ * its own PortableSchema and they're merged at the end.
+ */
+interface FieldValidator {
+ validate(value: unknown): { value: unknown } | { errors: string[] };
+ jsonSchema: JsonObject;
+ required: boolean;
+}
+
+/**
+ * Internal representation that carries per-field validators alongside the
+ * public PortableSchema surface. The brand field is used to detect whether
+ * a PortableSchema came from this utility (and thus supports merging).
+ */
+interface MergeablePortableSchema
+ extends PortableSchema {
+ /** @internal */
+ readonly _fields: Record;
+}
+
+// ---------------------------------------------------------------------------
+// createPortableSchema — builds from a field record
+// ---------------------------------------------------------------------------
+
+function createPortableSchema(
+ fields: T,
+): MergeablePortableSchema, InferConfigInput> {
+ const fieldValidators: Record = {};
+
+ for (const [key, field] of Object.entries(fields)) {
+ const resolved = typeof field === 'function' ? field(zodV3) : field;
+ fieldValidators[key] = buildFieldValidator(key, resolved);
+ }
+
+ return buildPortableSchema(fieldValidators);
+}
+
+// ---------------------------------------------------------------------------
+// mergePortableSchemas — combines schemas from different sources
+//
+// This is the key operation for blueprint + override composition. Each
+// source may use a completely different schema library. Because we track
+// per-field validators, merging is just combining the field maps —
+// no need to mix schema types within a single validator.
+// ---------------------------------------------------------------------------
+
+function mergePortableSchemas(
+ a: MergeablePortableSchema | undefined,
+ b: MergeablePortableSchema | undefined,
+): MergeablePortableSchema | undefined {
+ if (!a && !b) {
+ return undefined;
+ }
+ if (!a) {
+ return b as MergeablePortableSchema ;
+ }
+ if (!b) {
+ return a as MergeablePortableSchema ;
+ }
+
+ return buildPortableSchema ({
+ ...a._fields,
+ ...b._fields,
+ });
+}
+
+// ---------------------------------------------------------------------------
+// buildPortableSchema — internal: produces a PortableSchema from a
+// field validator map. This is the shared implementation for both
+// createPortableSchema and mergePortableSchemas.
+// ---------------------------------------------------------------------------
+
+function buildPortableSchema(
+ fieldValidators: Record,
+): MergeablePortableSchema {
+ const jsonSchema = buildObjectJsonSchema(fieldValidators);
+
+ return {
+ parse(input) {
+ const inputObj = (input ?? {}) as Record;
+ const result: Record = {};
+ const errors: string[] = [];
+
+ for (const [key, validator] of Object.entries(fieldValidators)) {
+ const validated = validator.validate(inputObj[key]);
+ if ('errors' in validated) {
+ errors.push(...validated.errors);
+ } else {
+ result[key] = validated.value;
+ }
+ }
+
+ if (errors.length > 0) {
+ throw new Error(errors.join('; '));
+ }
+
+ return result as TOutput;
+ },
+
+ schema: jsonSchema,
+
+ _fields: fieldValidators,
+ };
+}
+
+// ---------------------------------------------------------------------------
+// buildFieldValidator — wraps a single schema (any type) into a
+// normalized FieldValidator with validation, JSON Schema, and required.
+// ---------------------------------------------------------------------------
+
+function buildFieldValidator(key: string, schema: unknown): FieldValidator {
+ if (isZodV3Type(schema)) {
+ return buildZodFieldValidator(key, schema);
+ }
+ if (isStandardSchema(schema)) {
+ return buildStandardFieldValidator(key, schema);
+ }
+ throw new Error(
+ `Config schema for field '${key}' is not a valid Standard Schema or zod schema`,
+ );
+}
+
+function buildZodFieldValidator(key: string, schema: ZodType): FieldValidator {
+ // Wrap the single field in a one-key z.object so we get proper zod
+ // object-level behavior (default application, optional handling, etc.)
+ const wrapper = zodV3.object({ [key]: schema });
+ const wholeJsonSchema = zodToJsonSchema(wrapper) as Record;
+
+ return {
+ validate(value) {
+ const result = wrapper.safeParse({ [key]: value });
+ if (result.success) {
+ return { value: result.data[key] };
+ }
+ return { errors: result.error.issues.map(formatZodIssue) };
+ },
+ jsonSchema: (wholeJsonSchema.properties?.[key] ?? {}) as JsonObject,
+ required: (wholeJsonSchema.required ?? []).includes(key),
+ };
+}
+
+function buildStandardFieldValidator(
+ key: string,
+ schema: StandardSchemaV1,
+): FieldValidator {
+ let fieldJsonSchema: JsonObject;
+ if (hasJsonSchemaConverter(schema)) {
+ const raw = schema['~standard'].jsonSchema.input({ target: 'draft-07' });
+ const { $schema: _, ...rest } = raw;
+ fieldJsonSchema = rest as JsonObject;
+ } else {
+ throw new Error(
+ `Config schema for field '${key}' does not support JSON Schema conversion`,
+ );
+ }
+
+ const required = isFieldRequired(schema);
+
+ return {
+ validate(value) {
+ const result = schema['~standard'].validate(value);
+ if (result instanceof Promise) {
+ throw new Error(
+ `Config schema for '${key}' returned a Promise — async schemas are not supported`,
+ );
+ }
+ if (result.issues) {
+ return {
+ errors: Array.from(result.issues).map(issue =>
+ formatStandardIssue(key, issue),
+ ),
+ };
+ }
+ return { value: result.value };
+ },
+ jsonSchema: fieldJsonSchema,
+ required,
+ };
+}
+
+// ---------------------------------------------------------------------------
+// buildObjectJsonSchema — assembles per-field JSON Schemas into a
+// single object-level JSON Schema
+// ---------------------------------------------------------------------------
+
+function buildObjectJsonSchema(
+ fieldValidators: Record,
+): JsonObject {
+ const properties: Record = {};
+ const required: string[] = [];
+
+ for (const [key, validator] of Object.entries(fieldValidators)) {
+ properties[key] = validator.jsonSchema;
+ if (validator.required) {
+ required.push(key);
+ }
+ }
+
+ const schema: Record = {
+ type: 'object',
+ properties,
+ additionalProperties: false,
+ };
+
+ if (required.length > 0) {
+ schema.required = required;
+ }
+
+ return schema as JsonObject;
+}
+
+// ---------------------------------------------------------------------------
+// Detection helpers
+// ---------------------------------------------------------------------------
+
+function isZodV3Type(value: unknown): value is ZodType {
+ return (
+ typeof value === 'object' &&
+ value !== null &&
+ typeof (value as any)._parse === 'function' &&
+ '_def' in value
+ );
+}
+
+function isStandardSchema(value: unknown): value is StandardSchemaV1 {
+ return (
+ typeof value === 'object' &&
+ value !== null &&
+ '~standard' in value &&
+ typeof (value as any)['~standard']?.validate === 'function'
+ );
+}
+
+function hasJsonSchemaConverter(
+ schema: StandardSchemaV1,
+): schema is StandardSchemaV1 & {
+ '~standard': { jsonSchema: { input: Function } };
+} {
+ const std = schema['~standard'] as any;
+ return typeof std?.jsonSchema?.input === 'function';
+}
+
+function isFieldRequired(schema: StandardSchemaV1): boolean {
+ const result = schema['~standard'].validate(undefined);
+ if (result instanceof Promise) {
+ return true;
+ }
+ return (result.issues?.length ?? 0) > 0;
+}
+
+// ---------------------------------------------------------------------------
+// Error formatting
+// ---------------------------------------------------------------------------
+
+function formatZodIssue(issue: {
+ code: string;
+ message: string;
+ path: Array;
+ unionErrors?: Array<{ issues: Array }>;
+}): string {
+ if (issue.code === 'invalid_union' && issue.unionErrors?.[0]?.issues?.[0]) {
+ return formatZodIssue(issue.unionErrors[0].issues[0]);
+ }
+ let message = issue.message;
+ if (message === 'Required') {
+ message = 'Missing required value';
+ }
+ if (issue.path.length) {
+ message += ` at '${issue.path.join('.')}'`;
+ }
+ return message;
+}
+
+function formatStandardIssue(
+ fieldKey: string,
+ issue: StandardSchemaV1.Issue,
+): string {
+ let message = issue.message;
+ if (message === 'Required') {
+ message = 'Missing required value';
+ }
+ const path = issue.path?.length
+ ? `${fieldKey}.${issue.path
+ .map((p: PropertyKey | StandardSchemaV1.PathSegment) =>
+ typeof p === 'object' ? p.key : p,
+ )
+ .join('.')}`
+ : fieldKey;
+ return `${message} at '${path}'`;
+}
+
+// ---------------------------------------------------------------------------
+// Exports
+// ---------------------------------------------------------------------------
+
+export {
+ createPortableSchema,
+ mergePortableSchemas,
+ type MergeablePortableSchema,
+ type ConfigFieldSchema,
+ type ConfigSchemaRecord,
+ type InferConfigOutput,
+ type InferConfigInput,
+ type StandardSchemaV1,
+};