PortalCustomization
PortalCustomization is a cluster-scoped resource that changes the documents a Promise and
its Resource requests publish. Use it for owners, tags, forms and workflows that the Promise
API alone does not express. It does not change the Promise API or the Backstage plugins' UI.
A PortalCustomization declares containers that run between generate and writer and edit the
generated documents in place:
generate → configure (this profile's containers, in order) → writer
Use Portal Patch recipes for declarative edits. Use your own containers for computed values or external calls. Both can share a pipeline.
Creating a PortalCustomization
PortalCustomization (platform.syntasso.io/v1alpha1) is a cluster-scoped resource:
apiVersion: platform.syntasso.io/v1alpha1
kind: PortalCustomization
metadata:
name: backstage-add-team-owner
spec:
portalType: backstage # required: the portal type this profile applies to
promise:
configure:
containers:
- name: portal-patch
image: ghcr.io/syntasso/portal-patch:v0.8.2
args:
- |
target:
kind: Component
patch:
spec:
owner: platform-team
| Field | Type | Meaning |
|---|---|---|
spec.portalType | string, required | backstage or cortex for the documented integrations. A type, not an instance name, credential or URL. |
spec.promise.configure | Kratix PipelineSpec, optional | Runs between generate and writer for Promise documents. |
spec.resource.configure | Kratix PipelineSpec, optional | Runs between generate and writer for each bound Resource request's documents. |
Omitted lanes leave their documents unchanged.
The configure lane is a Kratix pipeline
Each lane is a full Kratix PipelineSpec, not just a list of containers. Alongside
containers[], which take the usual image, args, command, env, envFrom, volumeMounts,
resources and securityContext, you can set volumes, imagePullSecrets, rbac, jobOptions,
nodeSelector and tolerations.
Containers run in the order listed. They read the parent object from /kratix/input/object.yaml
and edit generated documents in /kratix/output in place. See the
adapter file contract for each portal and lane.
The configure lane shares the sync Job's Service Account. Pipeline RBAC can grant access to Secrets or external services; this is not a security sandbox isolated from writer credentials. Review container images, scripts and permissions before binding them to a Promise.
Do not write /kratix/metadata/destination-selectors.yaml from a configure container unless you
are deliberately redirecting output. The generate stage writes one entry for its output directory,
and duplicate entries for the same directory are rejected by the writer.
Configure containers run for the configure action only. The delete pipeline is writer-only, so a configure container never runs when a Promise or Resource is deleted.
Binding a Promise to a PortalCustomization
Label the Promise with the profile's name:
metadata:
labels:
kratix.io/portal-customization: backstage-add-team-owner
This label opts in using spec.portalType; no additional portal label is needed.
A Promise may be bound to one PortalCustomization only. If more than one
kratix.io/portal-customization label is present, the Promise reports PortalBindingReady: False
with reason AmbiguousCustomization and does not sync.
Combine changes in one profile's containers.
With multiple instances of the type, add the instance name as a suffix:
metadata:
labels:
kratix.io/portal-customization.staging: backstage-add-team-owner
Worked example: setting owner and tags without Portal Patch
Where a recipe will not do, any container that can read and write YAML works:
apiVersion: platform.syntasso.io/v1alpha1
kind: PortalCustomization
metadata:
name: cicd-backstage-customization
spec:
portalType: backstage
resource:
configure:
containers:
- name: set-owner-and-tags
image: ghcr.io/syntasso/kratix-pipeline-utility:v0.0.1
command: ["sh", "-c"]
args:
- |
set -e
file=/kratix/output/catalog-info.yaml
yq -i '.spec.owner = "platform-team"' "$file"
yq -i '.metadata.tags = (.metadata.tags // []) + ["tier-1", "managed-by-kratix"]' "$file"
Bind the Promise to it:
metadata:
labels:
kratix.io/portal-customization: cicd-backstage-customization
Status
A PortalCustomization reports a single Accepted condition. This reflects whether the resource
itself is structurally valid, Validated when it is and PortalTypeMissing when spec.portalType
is absent. It does not reflect whether any particular Promise's binding to it resolved.
Whether a Promise's binding resolved is reported on the Promise, via the PortalBindingReady
condition described in binding status.
status.observedGeneration identifies the resource generation checked by the controller.
Accepted: True does not mean a recipe selector matched or that any document was published.
PortalCustomization resources are cluster-wide. Give them unique, descriptive names.
Examples
- Portal Patch recipe reference for the full declarative grammar.
- Customising the request template for worked examples against a Backstage request template, and what the Promise API already controls on its own.
- Cortex examples for worked examples against Cortex documents.
