Skip to main content

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
tip

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
FieldTypeMeaning
spec.portalTypestring, requiredbackstage or cortex for the documented integrations. A type, not an instance name, credential or URL.
spec.promise.configureKratix PipelineSpec, optionalRuns between generate and writer for Promise documents.
spec.resource.configureKratix PipelineSpec, optionalRuns 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.

Configure containers are trusted platform code

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.

warning

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.

important

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.

tip

PortalCustomization resources are cluster-wide. Give them unique, descriptive names.

Examples​