Template Customization Guide
This guide explains how to customize the Handlebars templates used to generate Backstage templates from Crossplane XRDs.
Overview
The ingestor plugin uses Handlebars templates to transform Crossplane XRDs into Backstage Template and API entities. By default, templates are bundled with the npm package, but you can customize them to fit your organization's needs.
Template Structure
Templates are organized into specialized directories:
templates/
├── README.md # Template development guide
├── backstage/ # Main template entity generation
│ ├── default.hbs # Standard template
│ └── debug.hbs # Debug output template
├── parameters/ # Parameter definitions
│ ├── default.hbs # Standard parameters
│ └── gitops.hbs # GitOps-specific parameters
├── steps/ # Scaffolder step definitions
│ ├── default.hbs # Direct kube:apply steps
│ └── gitops.hbs # PR-based GitOps workflow
├── output/ # Output panel templates
│ ├── default.hbs # Basic output
│ ├── gitops.hbs # GitOps with PR link
│ ├── download-manifest.hbs
│ ├── gitops-summary.hbs
│ └── pr-link.hbs
└── api/ # API entity templates
└── default.hbs # API documentation
Quick Start
1. Initialize Custom Templates
Using npm directly:
npx @open-service-portal/backstage-plugin-ingestor init
Or if using in a Backstage app with yarn scripts:
yarn ingestor:init
This creates ./ingestor-templates/ with all default templates.
Note for Backstage Applications:
- The templates are copied to your app directory, not symlinked
- This allows you to customize them independently from the npm package
- You can track custom templates in git for version control and team collaboration
- Use
yarn ingestor:init --forceto reset templates to npm package defaults
2. Configure Template Directory
Add to app-config/ingestor.yaml:
ingestor:
crossplane:
xrds:
# Path to custom templates (relative to app root)
templateDir: './ingestor-templates'
# GitOps configuration (used by templates)
gitops:
owner: 'your-org'
repo: 'catalog-orders'
targetBranch: 'main'
3. Customize Templates
Edit templates in ./ingestor-templates/:
# Customize the main Backstage template structure
vim ingestor-templates/backstage/default.hbs
# Customize GitOps workflow steps
vim ingestor-templates/steps/gitops.hbs
# Customize parameter definitions
vim ingestor-templates/parameters/default.hbs
4. Test Your Changes
# Test with CLI (uses configured templates)
npx @open-service-portal/backstage-plugin-ingestor path/to/xrd.yaml
# Or override for testing
npx @open-service-portal/backstage-plugin-ingestor \
--template-path ./experimental-templates \
path/to/xrd.yaml
# Test with Backstage (uses configured templates)
yarn start
Configuration Priority
Templates are loaded with this priority:
- CLI flag (CLI only):
--template-path ./custom - Config setting:
ingestor.crossplane.xrds.templateDir - Built-in default: Templates from npm package
This allows:
- Production: Use configured templates from
app-config.yaml - Development: Test experimental templates with CLI flag
- Fallback: Automatic use of built-in templates if none configured
Template Workflows
Workflow 1: Using Built-in Templates (Default)
Best for: Getting started, using standard templates
# app-config/ingestor.yaml
ingestor:
crossplane:
xrds:
enabled: true
# No templateDir specified - uses built-in templates
gitops:
owner: 'your-org'
repo: 'catalog-orders'
Pros:
- ✅ Zero setup - works immediately
- ✅ Automatic updates when upgrading npm package
- ✅ No files to maintain
Cons:
- ❌ Cannot customize templates
- ❌ Templates update with npm package (might break customizations)
Workflow 2: Tracked Custom Templates (Recommended)
Best for: Teams needing customization, version control
# 1. Initialize templates
yarn ingestor:init
# 2. Track in git
git add ingestor-templates/
git commit -m "feat: initialize custom ingestor templates"
# 3. Configure
# app-config/ingestor.yaml
ingestor:
crossplane:
xrds:
templateDir: './ingestor-templates'
# 4. Customize and commit
vim ingestor-templates/backstage/default.hbs
git add ingestor-templates/
git commit -m "feat: add organization branding to templates"
Pros:
- ✅ Full customization control
- ✅ Version controlled with application
- ✅ Team collaboration via PR reviews
- ✅ Independent from npm package updates
- ✅ Can reset to defaults anytime
Cons:
- ❌ Manual updates when npm package templates improve
- ❌ Need to maintain template files
This is the recommended workflow for app-portal and similar consuming applications.
Workflow 3: Gitignored Custom Templates
Best for: Personal customization, local development
# 1. Initialize templates
yarn ingestor:init
# 2. Add to .gitignore
echo "ingestor-templates/" >> .gitignore
# 3. Configure
# app-config.local.yaml (gitignored)
ingestor:
crossplane:
xrds:
templateDir: './ingestor-templates'
Pros:
- ✅ Personal customization without affecting team
- ✅ No git noise from template experiments
Cons:
- ❌ Not shared with team
- ❌ Lost when cloning fresh
- ❌ Each developer maintains their own
Workflow 4: CLI Testing Only
Best for: Experimenting with templates before committing
# Test with experimental templates
npx ingestor transform \
--template-path ./experimental-templates \
path/to/xrd.yaml
# Production still uses configured templates
yarn start # Uses templateDir from config
Pros:
- ✅ Safe experimentation
- ✅ No risk to production templates
- ✅ Easy to test multiple template variants
Cons:
- ❌ Only works with CLI, not Backstage runtime
Resetting to Built-in Templates
If you're using tracked custom templates (Workflow 2) and want to reset:
# Backup current templates
cp -r ingestor-templates ingestor-templates.backup
# Reinitialize from npm package
yarn ingestor:init --force
# Review changes
git diff ingestor-templates/
# Option 1: Keep npm package defaults
git add ingestor-templates/
git commit -m "chore: reset templates to npm package defaults"
# Option 2: Restore your customizations
mv ingestor-templates.backup ingestor-templates
Template Contexts
Templates receive a rich context object:
{
xrd: {
metadata: { name, annotations, labels },
spec: {
group,
versions: [...],
names: { kind, plural, singular },
scope: 'Namespaced' | 'Cluster'
}
},
properties: [
{ name, type, title, description, required, default, enum }
],
config: {
gitops: {
owner: 'your-org',
repo: 'catalog-orders',
targetBranch: 'main'
}
},
helpers: {
slugify,
extractTitle,
extractDescription,
// ... more helpers
}
}
Available Helpers
The templates include powerful Handlebars helpers:
String Helpers
{{slugify str}}- Convert to URL-safe slug{{pascalCase str}}- Convert to PascalCase{{camelCase str}}- Convert to camelCase
Backstage Helpers
{{backstageVar "parameters.name"}}- Generates${{ parameters.name }}{{backstageConfigFallback "parameters.owner" config.gitops.owner}}- With fallback
XRD Helpers
{{extractTitle xrd}}- Extract human-friendly title{{extractDescription xrd}}- Extract description from XRD
Conditional Helpers
{{#if (eq scope "Namespaced")}}...{{/if}}{{#if (includes array value)}}...{{/if}}
Template Selection
Templates are selected via XRD annotations:
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
name: dnsrecords.example.com
annotations:
# Select templates to use
openportal.dev/template-steps: "gitops" # Use gitops.hbs for steps
openportal.dev/template-parameters: "gitops" # Use gitops.hbs for parameters
openportal.dev/template-output: "gitops" # Use gitops.hbs for output
If annotations are not specified, default.hbs is used.
Common Customizations
Adding Organization Branding
Customizing Parameter Validation
Adding Custom Workflow Steps
Testing Template Changes
1. Regression Testing
Use the test infrastructure:
cd ingestor
./run-tests.sh
2. Manual Testing
# Transform a real XRD
npx @open-service-portal/backstage-plugin-ingestor \
path/to/real-xrd.yaml > generated-template.yaml
# Review the output
cat generated-template.yaml
# Test in Backstage
yarn start
# Navigate to http://localhost:3000/create
3. Validation
# Validate without outputting
npx @open-service-portal/backstage-plugin-ingestor \
--validate \
path/to/xrd.yaml
Version Control Best Practices
-
Commit Custom Templates
git add ingestor-templates/
git commit -m "feat: customize XRD templates for our org" -
Review Template Changes
- Create PRs for template modifications
- Test with real XRDs before merging
- Document customization rationale
-
Track Template Versions
- Add version comments in templates
- Link to relevant issues/tickets
- Document breaking changes
Upgrading Templates
When upgrading the ingestor plugin:
-
Review Changelog
- Check for template improvements
- Note new helpers or context fields
-
Reinitialize (Optional)
# Save your customizations
cp -r ingestor-templates ingestor-templates.backup
# Get latest default templates
npx @open-service-portal/backstage-plugin-ingestor init --force
# Merge your customizations
# Compare and merge manually -
Test Thoroughly
- Run regression tests
- Test with representative XRDs
- Verify in Backstage UI
Troubleshooting
Templates Not Loading
Symptom: Built-in templates used instead of custom ones
Solution:
# Verify templateDir is set
cat app-config/ingestor.yaml | grep templateDir
# Check path is correct (relative to app root)
ls -la ingestor-templates/
# Restart Backstage
yarn start
Template Syntax Errors
Symptom: Transform fails with Handlebars errors
Solution:
# Enable verbose mode
npx @open-service-portal/backstage-plugin-ingestor \
--verbose \
path/to/xrd.yaml
# Check for:
# - Unclosed {\{#if}} blocks
# - Invalid helper usage
# - Incorrect context paths
Missing Context Variables
Symptom: Template generates empty values
Solution:
# Use debug template to see full context
npx @open-service-portal/backstage-plugin-ingestor \
--template debug \
path/to/xrd.yaml
# Review available context fields
# Update template to use correct paths
Examples
See the templates/README.md for:
- Template development guide
- Example customizations
- Helper function reference
- Best practices
Support
For questions or issues:
- Check XRD Transform Examples
- Review Template README
- File issues at https://github.com/open-service-portal/ingestor/issues