Develop a Blueprint
What means develop a blueprint
Developing a Blueprint in Blindata means designing a reusable, production-ready template that standardizes how data products are created across your organization.
At a high level, a Blueprint is not just code - it’s a codified set of best practices. It captures how infrastructure, pipelines, governance, and configuration should be defined, validated, and deployed, and makes that knowledge available as a self-service asset for other teams.
Blueprints rely on the Apache Velocity template engine to render dynamic content. Files in the repository can include template expressions and use the .vm suffix. During instantiation, these templates are processed by replacing variables with the values provided through the manifest parameters, producing fully configured files in the generated data product repository.
When you develop a Blueprint, you are:
- Abstracting complexity into a reusable template
- Defining inputs (parameters) that make the template flexible
- Embedding governance and controls directly into the setup
- Automating provisioning of data product components
- Enabling consistency across all generated data products
From a practical perspective, developing a Blueprint consists of three main activities:
- Structuring the repository: Organizing your Git repository to include all the assets needed to provision a data product (infrastructure, pipelines, CI/CD, etc.).
- Defining the Blueprint Manifest: Writing the
manifest.yaml, which acts as the contract between the template and its users-declaring parameters, validation rules, UI behavior, composition, and instantiation strategy. - Designing for reuse: Ensuring the template is generic, configurable, and versioned so it can be instantiated multiple times across different contexts.
Blueprint repository layout
A blueprint is a regular Git repository. Blindata only needs a few known files to register, publish, and instantiate it - specifically the Blueprint Manifest, the README, and (optionally) the Data Product Descriptor template written in Apache Velocity. Everything else in the repository is fully under your control: you are free to organize folders, tooling, and assets as your team sees fit.
A possible example of a well-structured blueprint repository is shown below. Treat it as a reference layout, not a requirement: Blindata does not enforce, validate, or manage any of the non-blueprint folders (infrastructure, pipelines, notebooks, CI/CD, etc.) - they live in your Git repository and are maintained by you like any other codebase.
repository/
├── blueprint/
│ ├── manifest.yaml # Blueprint Manifest
│ └── README.md # Rendered in the "Documentation" tab
├── descriptor.json.vm # Apache Velocity descriptor template
├── infrastructure/ # Example folder — Terraform, or any IaC you ship with the template
├── pipeline/ # Example folder — transformations (dlt, dbt, ...), jobs, notebooks
├── .github/workflows/ # Example folder — CI/CD
└── ... (any other content your template needs)
Only the paths to the manifest, the README, and the descriptor template are known to Blindata, and they are configurable when you register the blueprint . All other files and folders shown above are just illustrative examples of content you might ship with a blueprint - their structure, naming, and lifecycle are entirely managed inside your Git repository.
The Blueprint Manifest (blueprint/manifest.yaml by convention) is the single source of truth that drives how Blindata renders, validates, and instantiates the blueprint. Everything else in the repository is template content, rendered on instantiation with the parameters collected from the user.
Blueprint Manifest
The manifest is a YAML file conforming to the odm-blueprint-manifest specification. YAML is recommended over JSON because it supports comments, multi-line strings, and is the industry standard for infrastructure configuration.
A copy of the manifest - complete with the resolved parameter values and versioning metadata - is stored in every instantiated data product repository, so lineage back to the source blueprint is always preserved.
Below are showed the main sections that odm-blueprint-manifest specification supports.
Core schema
The following top-level keys are supported.
| Field | Type | Required | Description |
|---|---|---|---|
spec |
String | Yes | Specification name. Must be odm-blueprint-manifest. |
specVersion |
String | Yes | Version of the manifest schema itself (e.g. 1.0.0). |
name |
String | Yes | Machine-readable identifier of the blueprint. |
displayName |
String | No | Human-readable name. |
version |
String | Yes | Semantic version of the blueprint release (e.g. 1.2.0). |
description |
String | No | Short summary of what the blueprint provisions. |
parameters |
Array<Parameter> | No | Inputs collected from the user at instantiation time. |
protectedResources |
Array<ProtectedResource> | No | Files or globs marked immutable after generation. |
composition |
Array<Module> | No | Child blueprints to instantiate alongside the parent. |
instantiation |
Object | Yes | Target strategy and layout for generated output. |
Parameters
Each parameter declares a key that the template may reference, its type, validation constraints, and presentation hints for the collection form.
| Field | Type | Required | Description |
|---|---|---|---|
key |
String | Yes | Variable name to inject into templates. |
type |
Enum | No | One of string, integer, boolean, array, object. Defaults to string. |
required |
Boolean | No | Marks the parameter as mandatory. Defaults to false. |
default |
Any | No | Fallback value when none is provided. Must match type. |
validation |
Object | No | Strict constraints evaluated before instantiation. |
ui |
Object | No | Presentation metadata used by Blindata to render the form. |
Validation
The validation object, constraints are evaluated against the value supplied by the user before the template is rendered. This object stays in parameters sections.
| Field | Applies to | Description |
|---|---|---|
allowedValues |
Scalar types | Value must equal one of the listed entries. |
format |
Strings | Well-known JSON-Schema-style formats (hostname, uri, email, …). |
pattern |
Strings | Regular expression the value must match. |
min |
Numbers, strings, arrays | Minimum numeric value or minimum length / item count. |
max |
Numbers, strings, arrays | Maximum numeric value or maximum length / item count. |
UI
The ui object drives how Blindata lays out the parameter form during instantiation and in the Parameters tab of the blueprint detail page. All fields are optional strings.
| Field | Purpose |
|---|---|
group |
Splits the form into sections using a /-separated path (e.g. Networking / Firewall). Ungrouped parameters are rendered after every grouped section. |
label |
Short title displayed next to the input. When absent, Blindata falls back to key. |
description |
Helper text shown as caption or inline help. |
formType |
Suggests the control style. Combine it with type and validation to drive the rendered widget. |
Supported formType values per parameter type:
type |
Supported formType |
Notes |
|---|---|---|
string |
text (default), textarea, dropdown |
Use dropdown with validation.allowedValues for fixed choices; textarea for multi-line input. |
integer |
number (default), text |
text still coerces to integer before submit. |
boolean |
checkbox, switch |
Omit formType to use the default two-state style. |
array |
json (default), stringList |
stringList renders a row/line-based editor for string arrays. |
object |
json (default) |
Renders a structured / JSON editor. |
Protected resources
protectedResources declares paths (files, directories, or globs) that must stay immutable in the instantiated data product repository. This protects core infrastructure definitions or scaffolding from accidental edits.
| Field | Type | Required | Description |
|---|---|---|---|
path |
String | Yes | Path relative to the repository root, or a glob (e.g. infrastructure/**). |
integrity |
Object | No | Cryptographic digest recorded at instantiation time for tamper detection. |
integrity.algorithm |
String | Yes (if integrity is set) |
Hash algorithm (e.g. sha256). |
integrity.value |
String | Yes (if integrity is set) |
Lowercase hex-encoded digest. |
integrity is omitted from the source manifest and populated automatically on the copy stored in the instantiated data product repository.
Composition
Composition lets a parent blueprint reference one or more child blueprints (“modules”). The manifest explicitly maps which values flow from the parent to each child — there is no implicit global scope, exactly like Terraform modules.
| Field | Type | Required | Description |
|---|---|---|---|
module |
String | Yes | Logical alias for the child module; referenced by instantiation.compositionLayout and instantiation.targets. |
blueprintName |
String | Yes | Identifier of the child blueprint. |
blueprintVersion |
String | Yes | Target release version of the child blueprint. |
parameterMapping |
Object | No | Maps child parameter keys to literals or to parent parameter keys. |
Instantiation strategies
The instantiation block declares where the generated output is written.
| Field | Type | Required | Description |
|---|---|---|---|
strategy |
Enum | Yes | monorepo (single target repo) or polyrepo (multiple target repos). |
compositionLayout |
Array | No | Maps each composed module to a directory in the target repo (monorepo + composition). |
targets |
Array | Conditional | Required when strategy: polyrepo. One entry per deployment slice. |
When polyrepo is used, the parent repository name is not a manifest parameter: it is supplied at instantiation time by the user. The platform derives every target repository by concatenating that runtime name with the repositoryNamePostfix declared in each targets entry.
targets entries support two mutually exclusive shapes depending on whether composition is in use.
| Field | Type | Required | Description |
|---|---|---|---|
repositoryNamePostfix |
String | Yes | Suffix appended to the runtime parent name to derive the target repository. |
createPolicy |
Enum | No | create_if_missing or must_exist. Whether Blindata may create the repository. |
module |
String | Conditional | Polyrepo + composition: must match a composition[].module. |
sourcePath |
String | Conditional | Polyrepo without composition: path inside the blueprint checkout. |
targetPath |
String | Conditional | Polyrepo without composition: destination path inside the derived repo. |
Repeat the same repositoryNamePostfix on multiple entries if several slices should land in the same derived repository.
Manifest examples
All examples below are source blueprint manifests using spec: odm-blueprint-manifest and specVersion: 1.0.0. Parameter lists are abbreviated; production manifests should declare every input the templates reference.
Monorepo, no composition
A single target repository, parent output only. No composition and no extra instantiation keys beyond strategy.
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: analytics-lakehouse
displayName: Analytics Lakehouse Blueprint
version: 1.0.0
description: Provisions storage and compute defaults for an analytics data product.
parameters:
- key: environment
type: string
required: true
validation:
allowedValues: [dev, staging, prod]
ui:
group: General Configuration
label: Environment
description: Deployment stage for this data product.
formType: dropdown
- key: retentionDays
type: integer
default: 90
validation:
min: 1
max: 3650
ui:
group: Storage
label: Data retention (days)
formType: number
protectedResources:
- path: infrastructure/core/**
- path: README.md
instantiation:
strategy: monorepo
Monorepo + composition
Child modules are merged into one target repository using compositionLayout. The parent repository name is chosen at runtime; only the directory layout is fixed in the manifest.
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: full-stack-dp
version: 2.1.0
description: Parent blueprint composing storage and serving modules into one repo.
parameters:
- key: projectSlug
type: string
required: true
validation:
pattern: '^[a-z][a-z0-9-]{1,62}$'
ui:
group: General Configuration
label: Project slug
formType: text
- key: enablePiiMasking
type: boolean
default: true
ui:
group: Security
label: Enable PII masking
composition:
- module: storage
blueprintName: odm-blueprint-s3-lake
blueprintVersion: 3.0.1
parameterMapping:
bucketPrefix: projectSlug
encryptAtRest: enablePiiMasking
- module: serving
blueprintName: odm-blueprint-api-skeleton
blueprintVersion: 1.4.0
parameterMapping:
serviceName: projectSlug
instantiation:
strategy: monorepo
compositionLayout:
- module: storage
targetPath: data-plane/storage
- module: serving
targetPath: app/serving
Polyrepo, no composition
Output is split across multiple repositories, each derived from runtime parent name + repositoryNamePostfix. Every targets row uses sourcePath / targetPath (no module).
If the user instantiates with parent repository name acme-customer-360, the platform derives acme-customer-360-infra and acme-customer-360-apps from the postfixes below.
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: split-stack-template
version: 0.5.0
parameters:
- key: awsRegion
type: string
required: true
validation:
allowedValues: [eu-west-1, eu-central-1, us-east-1]
ui:
label: AWS region
formType: dropdown
instantiation:
strategy: polyrepo
targets:
- repositoryNamePostfix: "-infra"
createPolicy: create_if_missing
sourcePath: terraform/
targetPath: ./
- repositoryNamePostfix: "-apps"
createPolicy: create_if_missing
sourcePath: application/
targetPath: ./
- repositoryNamePostfix: "-infra"
sourcePath: policies/
targetPath: policies/
The first and third entries share the -infra postfix, showing how multiple slices can land in the same derived repository.
Polyrepo + composition
Each composed module is deployed to its own repository, identified by parent name (runtime) + postfix. Each targets row sets module (no sourcePath / targetPath).
spec: odm-blueprint-manifest
specVersion: 1.0.0
name: mesh-polyrepo-parent
version: 1.3.0
parameters:
- key: dataDomain
type: string
required: true
ui:
group: Governance
label: Data domain
formType: text
composition:
- module: ingest
blueprintName: odm-blueprint-ingest-batch
blueprintVersion: 2.0.0
parameterMapping:
domain: dataDomain
- module: consume
blueprintName: odm-blueprint-consumer-api
blueprintVersion: 1.1.0
parameterMapping:
domain: dataDomain
instantiation:
strategy: polyrepo
targets:
- repositoryNamePostfix: "-pipeline"
createPolicy: must_exist
module: ingest
- repositoryNamePostfix: "-api"
createPolicy: create_if_missing
module: consume
Reserved parameters
During instantiation, Blindata pre-fills a handful of reserved parameters with values taken from the data product metadata and the current user profile (see the Instantiation Guide for more information).
You do not need to declare them explicitly: if your templates reference any of the reserved keys below, Blindata will bind the corresponding metadata automatically. You can still override the values in the wizard before launching the instantiation.
| Reserved key | Source | Purpose |
|---|---|---|
dpDomain |
Data Product metadata | Business domain the data product belongs to. |
dpName |
Data Product metadata | Technical name (lowercase alphanumeric and dashes). |
dpFqn |
Data Product metadata | Unique identifier / URN. |
dpDisplayName |
Data Product metadata | Human-readable display name. |
dpDescription |
Data Product metadata | High-level description. |
dpOwnerId |
User profile | Owner username / user identifier. |
dpOwnerName |
User profile | Owner display name. |