XRD Form Visibility with x-ui-hidden and x-ui-advanced#
The kubernetes-ingestor plugin supports two vendor-extension annotations on XRD
openAPIV3Schema properties to control how fields appear in the generated Backstage
scaffolder template form.
Both extensions apply to XRD only (XRDTemplateEntityProvider).
x-ui-hidden#
# inside openAPIV3Schema.properties.spec.properties
internalRef:
type: string
x-ui-hidden: true
When x-ui-hidden: true is set on a property, the field is completely excluded from the
generated JSON Schema that drives the scaffolder form. RJSF never sees it — the field is not
rendered, not validated, and not included in formData.
When to use it#
- Fields in migration — the XRD schema still contains the field (needed by Crossplane compositions or for backward compatibility), but you want to stop exposing it to users while the migration is in progress. The field remains valid in the XRD, so Crossplane can still read and patch it; it simply does not appear in the Backstage form.
- Internal / system-managed values — fields set programmatically by a mutating webhook, composition patch, or environment controller that should never be provided by the user.
- Noisy defaults — fields whose value is always fixed and clutters the form.
Scope#
The annotation applies to:
- Top-level
specproperties - Nested object properties (recursive — any depth)
Example#
---
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
name: myapps.infra.example.com
spec:
group: infra.example.com
scope: Cluster
names:
kind: MyApp
plural: myapps
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
# Regular field — shown in the form
replicas:
type: integer
default: 1
description: Number of replicas.
# Field being migrated away from user input.
# Crossplane compositions still read it, but users should not set it.
legacyNetworkMode:
type: string
x-ui-hidden: true
description: >
Deprecated internal network selector. Managed by the composition.
# Nested hidden field
config:
type: object
properties:
publicEndpoint:
type: string
description: Public-facing endpoint URL.
internalToken:
type: string
x-ui-hidden: true
description: Injected by the mutating webhook — do not set manually.
In the example above the Backstage form renders replicas and config.publicEndpoint but
omits legacyNetworkMode and config.internalToken entirely.
x-ui-advanced#
debugMode:
type: boolean
default: false
x-ui-advanced: true
timeout:
type: string
default: 30s
x-ui-advanced: true
Fields marked with x-ui-advanced: true are moved out of the main properties section into
a conditional JSON Schema if/then dependency.
The plugin automatically injects a showAdvancedSettings boolean toggle:
{
"properties": {
"showAdvancedSettings": {
"title": "Show Advanced Settings",
"type": "boolean",
"default": false
}
},
"dependencies": {
"showAdvancedSettings": {
"if": { "properties": { "showAdvancedSettings": { "const": true } } },
"then": {
"properties": {
"debugMode": { "type": "boolean" },
"timeout": { "type": "string", "ui:placeholder": "30s" }
}
}
}
}
}
Advanced fields are invisible by default and only revealed once the user enables the toggle.
Default handling for advanced fields#
RJSF auto-populates formData with every field that has a default, even for fields the user
never touched. For advanced fields this would cause their defaults to appear in every generated
manifest. To prevent silent manifest pollution the plugin applies the following rules when
moving a field to the advanced section:
| Field type | Behaviour |
|---|---|
Non-boolean with default |
default removed; value moved to ui:placeholder (hint text only) |
Boolean with default |
default removed (booleans cannot use ui:placeholder) |
No default |
No change |
The x-ui-advanced marker is stripped from the field definition before it is placed in the
dependency, so it does not appear in generated YAML.
Example#
---
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
name: myapps.infra.example.com
spec:
group: infra.example.com
scope: Cluster
names:
kind: MyApp
plural: myapps
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
# Always-visible fields
replicas:
type: integer
default: 1
size:
type: string
enum: [small, medium, large]
# Advanced fields — hidden behind the toggle
debugMode:
type: boolean
default: false
x-ui-advanced: true
description: Enable verbose logging (advanced).
timeout:
type: string
default: 30s
x-ui-advanced: true
description: Request timeout override (advanced).
networking:
type: object
properties:
mode:
type: string
default: overlay
mtu:
type: integer
default: 1500
x-ui-advanced: true # only mtu is advanced within the object
Combining x-ui-hidden and x-ui-advanced#
Both extensions can be used together in the same XRD:
spec:
properties:
region:
type: string # regular visible field
internalRef:
type: string
x-ui-hidden: true # never shown in the form
tuning:
type: string
default: "auto"
x-ui-advanced: true # shown only when "Show Advanced Settings" is toggled
Notes#
- Both annotations are vendor extensions and are not part of Kubernetes CRD validation.
They are processed at template-generation time by this plugin and have no effect on the
Crossplane runtime. They apply only to XRD (
XRDTemplateEntityProvider). x-ui-hiddenandx-ui-advancedare mutually exclusive on the same field: a field withx-ui-hidden: trueis dropped before the advanced-grouping step, so combining both on one field is redundant (the field will simply be hidden).- For details on controlling field order see
XRD Field Ordering (
x-ui-order).