When Backstage catalog processors are the right place for validation
Teams often register catalog entities that break naming rules, miss required annotations or point at the wrong owner, and the organization needs one consistent check or enrichment step applied to all of them. Catalog processors fit this job: they validate entities, add annotations and emit relations during ingestion, and this page explains when to use them and when another mechanism is the better choice. In Backstage, the Software Catalog is a built-in core feature that reads entity descriptors, keeps them in a shared model and exposes them to plugins, so processors sit inside that ingestion pipeline rather than in a separate service. Before writing any processor, confirm with your own Backstage version how the catalog backend loads custom processors in your deployment, because the registration pattern is the first thing a version change can affect.
Use a processor when the rule applies to every entity that enters the catalog, whatever its source, and when the outcome is a property of that entity alone. Examples include requiring a metadata.name that follows your convention, rejecting an entity with an unknown spec.lifecycle value, or setting an annotation from a value already present in the descriptor. Processors can also emit relations that connect an entity to a group or system, which is useful when the relation can be derived from data the entity already carries. Ask your team which of these checks must block registration and which should only report, because processors can report errors that keep an entity out of the catalog, and that decision affects every team that registers components.
A processor is the wrong tool when the logic depends on data outside the entity, when it must remove entities, or when it has to track a source that changes over time. A processor cannot delete an entity, so removal logic belongs in an entity provider, which also controls deletion when its source records disappear. If your inventory of services lives in an internal system, the question becomes whether the catalog should pull that inventory on a schedule, and that is the entity provider decision covered in the sibling pages on entity providers and custom providers. Plan for that split early, so the validation rules you build as processors do not quietly become the only place where the catalog's shape is decided.
What a processor can and cannot change in an entity
A catalog processor works on entities after they are read, so its reach is narrow and deliberate. The four capabilities below cover what it can change, and the limit that matters most is that it cannot remove an entity from the catalog.
Validating entity fields
A processor can check that an entity has the fields and formats your organization requires, such as a naming pattern, a valid lifecycle value or a non-empty owner. When a check fails, the processor records an error on the entity so teams see the problem in the catalog instead of finding it later. Confirm exactly how validation errors are reported in your Backstage version, because the error surface changes between releases.
Adding annotations and metadata
Processors can add or change annotations and other metadata on an entity, for example to stamp a cost center or a team's support channel that the source file does not carry. Decide early which keys the processor owns, because a later edit in the descriptor file may overwrite or conflict with a value the processor set. Check the ordering rules for your version before relying on a value that another processor also writes.
Emitting relations
A processor can emit relations between entities, such as linking a component to the system it belongs to or to an API it provides, so the catalog graph reflects facts that are not written in each descriptor. Relations are only as accurate as the rule that creates them, so test the rule against a sample of real descriptors. Confirm which relation types your installed catalog version accepts.
Generating additional entities
A processor can produce new entities from data it reads, for example a Resource entity for each database referenced in a component's descriptor. Generated entities inherit a lifecycle tied to the processor run, so removing the source reference is what retires them, not a manual delete. Confirm how generated entities are named and tracked in your version to avoid duplicate names across runs.
Deleting entities is not possible
A processor cannot delete an entity. It receives entities and returns changes to them, and removal is handled by the ingestion layer, which decides when a record is gone from its source. Orphan handling sits in that layer: an entity whose source no longer produces it can be marked as an orphan and then removed according to the configured strategy. Confirm the deletion and orphan settings in your version before writing a rule that assumes an entity will disappear.
Confirm the interface in your version
The processor interface, the hook names and the ordering behavior are documented in the Backstage catalog reference, and they have changed across releases. Read the interface for the exact version you run and test each processor against a copy of your catalog before enabling it in production. Upgrade notes can change how processors receive entities, so check them whenever you bump Backstage.
Processor types and when each fits
This table compares the four things a catalog processor can do, from rejecting bad descriptors to generating new entities. Read each row as a decision: pick the lightest role that solves the problem, and move to a heavier one only when the signal in the last column appears. The main takeaway is that processors shape what is registered from descriptors, while removal of entities belongs elsewhere.
| Processor role | Typical use case | Main risk | Signal that a different approach is needed |
|---|---|---|---|
| Validation only | Enforcing naming patterns, required fields and allowed lifecycle or tier values before an entity is accepted | Rules become so strict that teams cannot register components, and errors surface as unclear refusals | Teams repeatedly fix the same descriptor errors, or the rule depends on data outside the descriptor |
| Annotation adding | Adding annotations such as a default team link or a catalog-wide tool reference based on fields already present | Silent defaults hide wrong values, and annotations drift from the source system | Annotations must reflect live data from another system, which suggests an entity provider |
| Relation emitting | Creating ownership, dependency or membership links between entities so the catalog graph is connected | Relations that point at entities which do not exist, or that depend on processing order | Relations must follow a source of truth such as an org directory or inventory, which suggests a provider that owns the full set |
| Entity generating | Producing child entities, such as a Resource or API derived from a component descriptor | Generated entities are orphaned or duplicated when the parent changes, and they are hard to trace back to their origin | Generated entities need their own lifecycle, owners or deletion rules, which suggests a custom entity provider |
| Deletion of entities | Not a supported processor role; processors cannot remove entities | Teams expect a processor to clean up, but removed entities persist or return on the next refresh | Stale entities accumulate, which calls for orphan handling and provider-level deletion rules |
How processing order and priority change the result
Processors run in a sequence during each catalog processing pass, so the order in which they act can decide what the catalog finally shows. A processor that reads an annotation or relation depends on whether an earlier processor has already added it. If your validation processor checks for an owner annotation that an enrichment processor sets, validation may run first and reject the entity before enrichment has a chance to add the value. The fix is rarely to make the processors more lenient. Instead, decide which processor produces each field, which processor consumes it, and make sure the producer runs first.
When you reason about priority, list every processor in the chain, including the built-in ones that ship with Backstage, and note what each reads and writes. Custom processors usually sit alongside defaults, and a default processor may already add relations or normalize fields that your rules assume are untouched. Confirm the ordering rules and priority mechanism for your Backstage release in the catalog documentation and the processor source, because this behavior is the kind of detail that can change between versions. Then build a sample entity set that covers edge cases: missing annotations, duplicate names, entities that depend on other entities, and entities that were already enriched in a previous pass.
Test the sample set against each change to the processor chain, and compare the resulting entities with the expected output rather than checking only for errors. Ordering problems often show up as entities that register with a missing relation or a stale annotation rather than as a failure, so a silent result can mislead your team. Record which processor owns each enriched field in your team's documentation. When someone later adds a processor, that record tells them where it belongs in the sequence and which fields it must not overwrite.
Building and registering a custom processor
This sequence takes a validation rule from a written decision to a processor that runs in the Backstage backend, rejects bad descriptors with a visible error, and reaches production through the normal change path. Each step ends with a check so the team knows the rule behaves as intended before it reaches shared environments.
Write the rule and its failure behavior
The platform team writes down the rule in one sentence, names the entity kinds it applies to, and decides what happens on failure: a validation error that blocks the entity from being ingested, or a warning that lets it through with a note. Record which fields the rule reads and which it may change, because processors that add annotations or relations must not overwrite values teams set themselves. The step is done when a reviewer can read the rule and predict the outcome for a passing and a failing sample descriptor.
Write the processor in the catalog module
A developer creates a class that implements the catalog processor interface in a backend catalog module, typically in a separate package so the rule can be versioned and tested on its own. The processor's validation hook inspects each entity it receives and throws or reports an error when the rule is broken. Keep enrichment logic in the same hook only when it is deterministic, so repeated processing produces identical output. The step is done when the package compiles and the type checker reports no errors against the installed Backstage packages.
Register the module in the backend
The developer adds the catalog module to the backend entry point so Backstage loads it at startup, then confirms the module name is unique among installed catalog modules. Use the processor registration pattern documented for the installed Backstage version, and check that the backend starts without a dependency error. The check is the startup log: the backend should report that the module loaded, and a deliberately misspelled import should fail visibly rather than silently skipping the rule.
Run the processor against sample descriptors
The developer starts a development instance backed by SQLite and registers a small set of sample catalog-info.yaml files: one that satisfies the rule, one that breaks it, and one edge case such as a missing optional field. Each sample is a fixture the team keeps in the repository, and each is labeled as illustrative so no one mistakes it for a real service. The check is that the passing sample ingests and the failing sample produces the error the rule defined in step one, with the same wording each time the file is processed.
Confirm errors surface in the catalog
The developer opens the catalog for the development instance and checks the location or entity error view for the failing sample, confirming the message names the file and the field at fault. Next, the developer fixes the sample and confirms the error clears on the next processing cycle, without a manual delete. Because processors cannot delete entities, a rejected descriptor is reported as an error on its location rather than removing an entity already in the catalog, so the team should also test what happens to an existing entity that a new change would invalidate. The check is a clear error message and a cleared state after the fix.
Add automated tests for the rule
The team turns the sample descriptors into unit tests that call the processor directly with fixture entities, covering the passing, failing and edge cases, and asserts that enrichment output is the same across repeated runs. The tests run in the same continuous integration pipeline as the rest of the backend so a change to the rule cannot merge without its tests passing. The check is a green pipeline with the new tests listed in the output, and a deliberately broken rule causing at least one test to fail.
Promote through the normal change path
The change goes through the same review, staging and production release steps as other backend changes, with the rule's failure behavior and affected entity kinds written into the pull request description. Before production, the team confirms with its catalog owners how many existing descriptors the rule will reject, using a staging catalog if one is available, so teams are warned before their entities start showing errors. The check is a staging catalog where the rule produces the expected errors and no unexpected ones, followed by the production release note.
When an entity provider is the better place for the logic
Put logic in an entity provider rather than a processor when it depends on an external source of truth or when it must remove or replace entities. A processor only sees the entities that a location or provider has already handed it. It can validate those entities, add annotations and emit relations, but it cannot decide that an entity should disappear because its record vanished from a source system. That is why processors cannot delete entities. If your rule is "every application in the internal inventory must have a component," the inventory is the authority, and the logic that reconciles the catalog with it belongs in a provider that owns the full set of entities it produces. Before you write a processor that looks up a value in an external system, confirm whether the value is really an enrichment or whether it is a reason to create or retire entities.
Providers differ from processors in how they update the catalog. A provider can send full mutations, which replace everything it owns, or delta mutations, which add, update or remove only the entities that changed. Full mutations are simpler to reason about but cost more as the source grows. Delta mutations suit large sources but require the provider to track what it has already sent. Deletion is the key design question: decide in advance what happens to an entity when its source record disappears, and check how the orphan handling settings in your Backstage configuration interact with that choice. Test this with a source record removed on purpose before you rely on it in production.
Writing a provider is a larger task than adding a processor, and it should be scoped as such. Confirm with your platform team who will own the provider code, how it is scheduled, and how its failures will show up in logs and metrics. The sibling topic on building a custom entity provider covers the implementation details, including mutation types, scheduling and removal. Keep the processor for transformations that are pure functions of the entity itself, and move anything that needs a source of truth or deletion rights into a provider so that the catalog stays consistent with that source.
Security and operational risks of custom processors
A custom processor runs inside the Backstage backend with access to catalog data, so it carries the same risk as any backend plugin. The cards below cover the main exposures: code review, rules that block legitimate registrations, ingestion slowdowns, and external calls. Each one has a matching check you can put in place before a processor reaches production.
Processors run inside the backend
A processor executes in the Backstage backend process and sees the entities it transforms, including annotations, owner references and any values that teams place in descriptors. Treat it like any backend plugin: require code review by the platform team, keep the dependency list short, and confirm which packages it imports. Ask who can merge changes to the processor repository, because that person effectively controls what the catalog accepts.
Rules can block legitimate registrations
A validation rule that is stricter than the descriptor standard will reject entities that teams have already written correctly. The result is a registration that fails with a message nobody on the team understands, often after a release. Before enforcing a new rule, run it in a log-only mode against the existing catalog, review the entities it would reject, and agree on an exception path with the teams that own them.
Slow processors delay ingestion
Processors run as part of catalog processing, so an expensive step or an unhandled loop slows every entity that passes through it. A slow processor shows up as stale catalog data rather than an obvious error, which makes it hard to trace. Measure processing time per entity during a test run with a realistic number of descriptors, and set a clear limit on what a processor is allowed to do per entity.
Log every validation failure
A processor that rejects an entity should record the entity reference, the rule that failed and the source location, so an owner can fix the descriptor without filing a ticket. Without these logs, teams report that the catalog is missing components and the platform team has no evidence of why. Review the rejection log weekly during rollout, and confirm where the backend writes it and how long it is kept.
External calls add dependencies
A processor that calls a service such as an internal API, a CMDB or a Git hosting provider inherits that service's availability and rate limits. If the call fails, the processor must decide whether to skip enrichment or reject the entity, and that decision should be deliberate. Ask which external systems the processor calls, what happens when each is down, and whether responses are cached so that every refresh does not repeat the same request.
Test before every release
Test each processor against a fixture set that includes valid descriptors, malformed files and edge cases such as duplicate names and missing owners. Run the same tests in a staging catalog before a change reaches production, and keep a rollback plan for the processor version. Confirm with your team which tests must pass before a release, and who signs off when a rule change affects existing teams.
Questions teams ask before building catalog processors
Can a Backstage catalog processor delete an entity?
No. A processor can validate an entity, add or change annotations and metadata, and emit relations, but it cannot remove an entity from the catalog. Deletion is handled by entity providers and the orphan strategy, because the catalog must know that a source record was actually removed before it drops an entity. If a processor rejects an entity with a validation error, the entity is marked as failing and is not updated, which is a different outcome from deletion. Teams that want a rule to remove stale records should put that logic in the provider that owns the source data and check the orphan handling settings described in the catalog documentation.
How much effort does a custom catalog processor take, and what drives it?
Effort depends mainly on how many rules you enforce, whether the rules need data from outside the entity, and how many entity kinds they touch. A processor that checks one naming pattern on Components is a small change. A processor that calls an internal inventory API, emits relations to other systems and handles partial failures takes longer to design, test and monitor. Ask any partner for a quote built from a written list of rules, the data sources each rule reads, the entity kinds in scope, and the test cases that prove each rule. Ongoing effort rises when the processor depends on fields that other teams change.
What does the internal team need to provide for a processor project?
Your team needs to provide the naming standard as a written document, examples of valid and invalid descriptors from real repositories, and a named owner who can approve rule changes. The engineers who maintain the Backstage backend should review the code, because processors run inside the catalog pipeline and affect every ingestion cycle. You also need access to a non-production instance and sample catalog data so the rules can be tested before they reach production. Without a clear rule owner, processors tend to accumulate exceptions that nobody can explain later.
Can built-in processors enforce our naming rules?
Built-in validation covers the structure of descriptor files, such as required fields and allowed kinds, and it checks common formats. It does not know your organization's naming convention, such as a required prefix tied to a business unit or a rule that a system name must match a cost center code. For those rules you need a custom processor or a validation step in your own pipeline. Confirm the exact built-in checks against the Backstage catalog documentation for the version you run, since validation behavior changes between releases.
Should we hire outside help for catalog processors or build them ourselves?
Build in-house if your team already maintains Backstage plugins, has TypeScript experience, and can keep the processor working through upgrades. Hire outside help when the team lacks that experience, when the rules touch several external systems, or when you need the work fixed in scope before it starts. DeployOpen implements Backstage as part of self-hosted open-source platform work and writes fixed-scope agreements, so the rule list, deliverables and timeline are agreed in writing before work begins. Ask for the written scope before comparing options.
How do we keep a custom processor working after Backstage upgrades?
Processors depend on Backstage backend interfaces, so they can break when the framework changes. Plan to run your processor tests as part of every upgrade, and check the upgrade notes for the catalog backend before you bump versions. The versioning policy describes monthly and weekly release lines, so a steady test suite matters more than a one-time build. Assign an owner for the processor so that breaking changes are caught in a test environment, not discovered by teams whose entities stop updating.
What happens to entities that fail a processor rule?
The catalog records the error against the entity and the entity is not updated with the new processed data. Teams see the error in the catalog interface, so the message should name the rule and the field that failed. Decide before launch whether failures should block a registration, alert a platform channel or only warn the owning team. Test failure messages with real teams, because a vague message generates support tickets and invites workarounds.
How DeployOpen scopes and delivers catalog processor work
DeployOpen plans, implements, tests, documents and supports self-hosted open-source platforms inside infrastructure you control, including Backstage. For catalog processors, we start from the rules your catalog must enforce, then build and hand over the processor code and its verification in your own environment.
How we scope
- Inventory the validation rules, annotations and relations your catalog needs, and which teams own each rule.
- Decide with you whether each rule belongs in a processor or in an entity provider, based on deletion behavior.
- Record processor order, failure behavior and the Backstage version range in a written scope before work starts.
- Engagements are fixed-scope, with scope, deliverables and timelines agreed in writing before work begins.
How we build
- Write processors that validate, enrich or emit relations without attempting to delete entities.
- Test each processor against sample descriptor files, including invalid names, missing owners and malformed relations.
- Confirm processor priority and ordering in a non-production Backstage instance before any rollout.
- Pin the processor code to the @backstage packages you run so upgrades can be tested as a separate job.
How we hand over
- Deliver the processor source, configuration, test suite and a short runbook covering behavior and known limits.
- Document each rule with its owner, the error message teams see and the steps to fix a rejected descriptor.
- Walk your platform and catalog teams through the code, the rules and how to add a new rule safely.
- Agree in writing which items are handed over and which remain your responsibility after closeout.
How we support
- Offer ongoing support, upgrades and continuous testing as a retainer for processors that must keep working across Backstage releases.
- Review processor compatibility when you move to a new release line, using the Backstage upgrade guidance.
- Re-run the processor test suite after dependency and Node.js changes before any production update.
- Ask us to confirm scope, retainer terms and handover items in a written quote before work starts.

