Extending the model
The Backstage catalog entity data model is based on the Kubernetes objects format, and borrows a lot of its semantics as well. This page describes those semantics at a higher level and how to extend them to fit your organization.
Backstage comes with a number of catalog concepts out of the box:
- There are a number of builtin versioned kinds, such as
Component
,User
etc. These encapsulate the high level concept of an entity, and define the schema for its entity definition data. - An entity has both a metadata object and a spec object at the root.
- Each kind may or may not have a type. For example, there are several well
known types of component, such as
service
andwebsite
. These clarify the more detailed nature of the entity, and may affect what features are exposed in the interface. - Entities may have a number of annotations on them. These can be added either by humans into the descriptor files, or added by automated processes when the entity is ingested into the catalog.
- Entities may have a number of labels on them.
- Entities may have a number of relations, expressing how they relate to each other in different ways.
We'll list different possibilities for extending this below.
Adding a New apiVersion of an Existing Kind
Example intents:
"I want to evolve this core kind, tweaking the semantics a bit so I will bump the apiVersion a step"
"This core kind is a decent fit but we want to evolve it at will so we'll move it to our own company's apiVersion space and use that instead of
backstage.io
."
The backstage.io
apiVersion space is reserved for use by the Backstage
maintainers. Please do not change or add versions within that space.
If you add an apiVersion space of your own, you are effectively branching out from the underlying kind and making your own. An entity kind is identified by the apiVersion + kind pair, so even though the resulting entity may be similar to the core one, there will be no guarantees that plugins will be able to parse or understand its data. See below about adding a new kind.
Adding a New Kind
Example intents:
"The kinds that come with the package are lacking. I want to model this other thing that is a poor fit for either of the builtins."
"This core kind is a decent fit but we want to evolve it at will so we'll move it to our own company's apiVersion space and use that instead of
backstage.io
."
A kind is an overarching family, or an idea if you will, of entities that also share a schema. Backstage comes with a number of builtin ones that we believe are useful for a large variety of needs that one may want to model in Backstage. The primary ambition is to map things to these kinds, but sometimes you may want or need to extend beyond them.
Introducing a new apiVersion is basically the same as adding a new kind. Bear in
mind that most plugins will be compiled against the builtin
@backstage/catalog-model
package and have expectations that kinds align with
that.
The catalog backend itself, from a storage and API standpoint, does not care
about the kind of entities it stores. Extending with new kinds is mainly a
matter of permitting them to pass validation when building the backend catalog
using the CatalogBuilder
, and then to make plugins be able to understand the
new kind.
For the consuming side, it's a different story. Adding a kind has a very large
impact. The very foundation of Backstage is to attach behavior and views and
functionality to entities that we ascribe some meaning to. There will be many
places where code checks if (kind === 'X')
for some hard coded X
, and casts
it to a concrete type that it imported from a package such as
@backstage/catalog-model
.
If you want to model something that doesn't feel like a fit for either of the builtin kinds, feel free to reach out to the Backstage maintainers to discuss how to best proceed.
If you end up adding that new kind, you must namespace its apiVersion
accordingly with a prefix that makes sense, typically based on your organization
name - e.g. my-company.net/v1
. Also do pick a new kind
identifier that does
not collide with the builtin kinds.
Adding a New Type of an Existing Kind
Example intents:
"This is clearly a component, but it's of a type that doesn't quite fit with the ones I've seen before."
"We don't call our teams "team", can't we put "flock" as the group type?"
Some entity kinds have a type
field in its spec. This is where an organization
are free to express the variety of entities within a kind. This field is
expected to follow some taxonomy that makes sense for yourself. The chosen value
may affect what operations and views are enabled in Backstage for that entity.
Inside Spotify our model has grown significantly over the years, and our
component types now include ML models, apps, data pipelines and many more.
It might be tempting to put software that doesn't fit into any of the existing types into an Other catch-all type. There are a few reasons why we advise against this; firstly, we have found that it is preferred to match the conceptual model that your engineers have when describing your software. Secondly, Backstage helps your engineers manage their software by integrating the infrastructure tooling through plugins. Different plugins are used for managing different types of components.
For example, the Lighthouse plugin only makes sense for Websites. The more specific you can be in how you model your software, the easier it is to provide plugins that are contextual.
Adding a new type takes relatively little effort and carries little risk. Any type value is accepted by the catalog backend, but plugins may have to be updated if you want particular behaviors attached to that new type.
Changing the Validation Rules for The Entity Envelope or Metadata Fields
Example intents:
"We want to import our old catalog but the default set of allowed characters for a metadata.name are too strict."
"I want to change the rules for annotations so that I'm allowed to store any data in annotation values, not just strings."
After pieces of raw entity data have been read from a location, they are passed
through a field format validation step. This ensures that the types and syntax
of the base envelope and metadata make sense - in short, things that aren't
entity-kind-specific. Some or all of these validators can be replaced when
building the backend using the catalog's dedicated catalogModelExtensionPoint
(or directly on the CatalogBuilder
if you are still using the old backend
system).
The risk and impact of this type of extension varies, based on what it is that you want to do. For example, extending the valid character set for kinds, namespaces and names can be fairly harmless, with a few notable exceptions - there is code that expects these to never ever contain a colon or slash, for example, and introducing URL-unsafe characters risks breaking plugins that aren't careful about encoding arguments. Supporting non-strings in annotations may be possible but has not yet been tried out in the real world - there is likely to be some level of plugin breakage that can be hard to predict.
You must also be careful about not making the rules more strict than they used to be after populating the catalog with data. This risks making previously valid entities start having processing errors and fail to update.
Before making this kind of extension, we recommend that you contact the Backstage maintainers or a support partner to discuss your use case.
This is an example of relaxing the format rules of the metadata.name
field:
import { createBackend } from '@backstage/backend-defaults';
import { createBackendModule } from '@backstage/backend-plugin-api';
import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
const myCatalogCustomizations = createBackendModule({
pluginId: 'catalog',
moduleId: 'catalog-customization',
register(reg) {
reg.registerInit({
deps: {
catalogModel: catalogModelExtensionPoint,
},
async init({ catalogModel }) {
catalogModel.setFieldValidators({
// This is only one of many methods that you can pass into
// setFieldValidators; your editor of choice should help you
// find the others. The length checks and regexp inside are
// just examples and can be adjusted as needed, but take care
// to test your changes thoroughly to ensure that you get
// them right.
isValidEntityName(value) {
return (
typeof value === 'string' &&
value.length >= 1 &&
value.length <= 63 &&
/^[A-Za-z0-9@+_.-]+$/.test(value)
);
},
});
},
});
},
});
const backend = createBackend();
// ... add other backend features and the catalog backend itself here ...
backend.add(myCatalogCustomizations);
backend.start();
Changing the Validation Rules for Core Entity Fields
Example intent:
"I don't like that the owner is mandatory. I'd like it to be optional."
After reading and policy-checked entity data from a location, it is sent through
the processor chain looking for processors that implement the
validateEntityKind
step, to see that the data is of a known kind and abides by
its schema. There is a builtin processor that implements this for all known core
kinds and matches the data against their fixed validation schema. This processor
can be replaced when building the backend catalog using the CatalogBuilder
,
with a processor of your own that validates the data differently.
This replacement processor must have a name that matches the builtin processor, BuiltinKindsEntityProcessor
.
This type of extension is high risk, and may have high impact across the
ecosystem depending on the type of change that is made. It is therefore not
recommended in normal cases. There will be a large number of plugins and
processors - and even the core itself - that make assumptions about the shape of
the data and import the typescript data type from the @backstage/catalog-model
package.
Adding New Fields to the Metadata Object
Example intent:
"Our entities have this auxiliary property that I would like to express for several entity kinds and it doesn't really fit as a spec field."
The metadata object is currently left open for extension. Any unknown fields found in the metadata will just be stored verbatim in the catalog. However we want to caution against extending the metadata excessively. Firstly, you run the risk of colliding with future extensions to the model. Secondly, it is common that this type of extension lives more comfortably elsewhere - primarily in the metadata labels or annotations, but sometimes you even may want to make a new component type or similar instead.
There are some situations where metadata can be the right place. If you feel that you have run into such a case and that it would apply to others, do feel free to contact the Backstage maintainers or a support partner to discuss your use case. Maybe we can extend the core model to benefit both you and others.
Adding New Fields to the Spec Object of an Existing Kind
Example intent:
"The builtin Component kind is fine but we want to add an additional field to the spec for describing whether it's in prod or staging."
A kind's schema validation typically doesn't forbid "unknown" fields in an
entity spec
, and the catalog will happily store whatever is in it. So doing
this will usually work from the catalog's point of view.
Adding fields like this is subject to the same risks as mentioned about metadata extensions above. Firstly, you run the risk of colliding with future extensions to the model. Secondly, it is common that this type of extension lives more comfortably elsewhere - primarily in the metadata labels or annotations, but sometimes you even may want to make a new component type or similar instead.
There are some situations where the spec can be the right place. If you feel that you have run into such a case and that it would apply to others, do feel free to contact the Backstage maintainers or a support partner to discuss your use case. Maybe we can extend the core model to benefit both you and others.
Adding a New Annotation
Example intents:
"Our custom made build system has the concept of a named pipeline-set, and we want to associate individual components with their corresponding pipeline-sets so we can show their build status."
"We have an alerting system that automatically monitors service health, and there's this integration key that binds the service to an alerts pool. We want to be able to show the ongoing alerts for our services in Backstage so it'd be nice to attach that integration key to the entity somehow."
Annotations are mainly intended to be consumed by plugins, for feature detection or linking into external systems. Sometimes they are added by humans, but often they are automatically generated at ingestion time by processors. There is a set of well-known annotations, but you are free to add additional ones. This carries no risk or impact to other systems as long as you abide by the following naming rules.
- The
backstage.io
annotation prefix is reserved for use by the Backstage maintainers. Reach out to us if you feel that you would like to make an addition to that prefix. - Annotations that pertain to a well known third party system should ideally be
prefixed with a domain, in a way that makes sense to a reader and connects it
clearly to the system (or the maker of the system). For example, you might use
a
pagerduty.com
prefix for pagerduty related annotations, but maybe notldap.com
for LDAP annotations since it's not directly affiliated with or owned by an LDAP foundation/company/similar. - Annotations that have no prefix at all, are considered local to your Backstage instance and can be used freely as such, but you should not make use of them outside of your organization. For example, if you were to open source a plugin that generates or consumes annotations, then those annotations must be properly prefixed with your company domain or a domain that pertains to the annotation at hand.
Adding a New Label
Example intents:
"Our process reaping system wants to periodically scrape for components that have a certain property."
"It'd be nice if our service owners could just tag their components somehow to let the CD system know to automatically generate SRV records or not for that service."
Labels are mainly intended to be used for filtering of entities, by external
systems that want to find entities that have some certain property. This is
sometimes used for feature detection / selection. An example could be to add a
label deployments.my-company.net/register-srv: "true"
.
At the time of writing this, the use of labels is very limited and we are still settling together with the community on how to best use them. If you feel that your use case fits the labels best, we would appreciate if you let the Backstage maintainers know.
You are free to add labels. This carries no risk or impact to other systems as long as you abide by the following naming rules.
- The
backstage.io
label prefix is reserved for use by the Backstage maintainers. Reach out to us if you feel that you would like to make an addition to that prefix. - Labels that pertain to a well known third party system should ideally be
prefixed with a domain, in a way that makes sense to a reader and connects it
clearly to the system (or the maker of the system). For example, you might use
a
pagerduty.com
prefix for pagerduty related labels, but maybe notldap.com
for LDAP labels since it's not directly affiliated with or owned by an LDAP foundation/company/similar. - Labels that have no prefix at all, are considered local to your Backstage instance and can be used freely as such, but you should not make use of them outside of your organization. For example, if you were to open source a plugin that generates or consumes labels, then those labels must be properly prefixed with your company domain or a domain that pertains to the label at hand.