Getting Started
Set up BOA and create your first block in under 10 minutes.
Prerequisites
Before you begin, make sure you have the following installed on your machine:
- Node.js v18+ (recommend v22)
- A terminal (bash, PowerShell, etc.)
Installation
Install the BOA CLI globally from npm with a single command.
Install the CLI
Install @boa-framework/core globally. This gives you the boa command
system-wide:
bashnpm install -g @boa-framework/core
Verify the installation
Confirm that the CLI is accessible from any directory:
bashboa --help
You should see a list of available commands including init, run,
test, validate, and more.
boa command is now available globally. You can use it from any project directory. To update
to the latest version, run npm update -g @boa-framework/core.
Create Your First Project
With the CLI installed, let’s create a project from scratch and build a working block.
Initialize a new project
Create a new directory and scaffold a BOA project inside it:
bashmkdir MyProject && cd MyProject
boa init
This creates the following structure:
project.boa— the full project overview (read this first in every session)blocks.boa— the block registry (one line per block)src/— source directories for each layer (Primitives, Capabilities, DomainBlocks)workflows/— directory for workflow definitions
Create a block
Use the CLI to scaffold a new domain block called Greet:
bashboa block create Greet --layer domain --runtime node
This creates src/DomainBlocks/Greet/ containing:
block.boa— the block manifest (identity, contract, rules, tests)index.ts— the TypeScript implementation file
Define the block manifest
Open src/DomainBlocks/Greet/block.boa and define the block’s contract, rules, and test
fixtures:
block.boaBLOCK Greet 1.0.0
LAYER domain
RUNTIME node
ENTRY index.js
DESC Generates a greeting message for a user.
INTENT User wants to greet someone by name.
RULE Output format is "Hello, {name}!".
RULE If name is empty, use "World".
TAGS greeting, string, demo
IN name:string?
OUT message:string
FIXTURE {"name": "Alice"} -> {"message": "Hello, Alice!"}
FIXTURE {} -> {"message": "Hello, World!"}
ERR ValidationError
Each line declares something specific: INTENT preserves why the block exists,
RULE captures business constraints, and FIXTURE defines self-testing
input/output pairs.
Implement the block
Open src/DomainBlocks/Greet/index.ts and write the implementation. The block reads a JSON
envelope from stdin and writes a JSON result to stdout:
index.tsasync function main() {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
const envelope = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
const { name } = envelope.input;
const greeting = name ? `Hello, ${name}!` : "Hello, World!";
console.log(JSON.stringify({
success: true,
output: { message: greeting }
}));
}
main();
Compile TypeScript
Compile the .ts file to .js so the runtime can execute it:
bashnpx tsc --outDir src/DomainBlocks/Greet \
--target ES2022 \
--module Node16 \
--moduleResolution Node16 \
src/DomainBlocks/Greet/index.ts
Test the block
Run the fixtures you declared in block.boa to verify everything works:
bashboa test Greet@1.0.0
Expected output:
Greet@1.0.0
Fixture 1: PASS
Fixture 2: PASS
2 fixtures passed, 0 failed
FIXTURE declaration. This lets you verify behavior with a
single command and catches regressions automatically when you run boa validate.
Create a Workflow
Workflows chain multiple blocks together declaratively. Let’s create a second block and wire them into a workflow.
Create another block
Create an UpperCase block that converts text to uppercase:
bashboa block create UpperCase --layer primitive --runtime node
Define its manifest in src/Primitives/UpperCase/block.boa:
block.boaBLOCK UpperCase 1.0.0
LAYER primitive
RUNTIME node
ENTRY index.js
DESC Converts a text string to uppercase.
INTENT Convert any text to all uppercase letters.
RULE Output is the input text in all uppercase.
RULE If text is empty, return empty string.
TAGS string, transform, uppercase
IN text:string!
OUT result:string
FIXTURE {"text": "hello"} -> {"result": "HELLO"}
FIXTURE {"text": ""} -> {"result": ""}
And implement it in src/Primitives/UpperCase/index.ts:
index.tsasync function main() {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
const envelope = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
const { text } = envelope.input;
console.log(JSON.stringify({
success: true,
output: { result: text.toUpperCase() }
}));
}
main();
Write the workflow
Create workflows/greet/workflow.boa to chain the two blocks together. The MAP
directive wires data from one step’s output to the next step’s input:
workflow.boaWORKFLOW GreetAndShout 1.0.0
DESC Greet a user and convert to uppercase.
STEP greet = Greet@1.0.0
MAP name <- _initial.name
STEP shout = UpperCase@1.0.0
MAP text <- greet.message
The workflow engine reads name from the initial input, passes it to Greet, then
pipes the greeting message into UpperCase.
Create an input file
Create input.json with the data you want to pass into the workflow:
input.json{
"name": "Alice"
}
--input-file instead of piping JSON with echo. Windows shells strip
quotes from JSON strings, which causes parsing errors.
Run the workflow
Execute the workflow and see both blocks run in sequence:
bashboa run workflows/greet/workflow.boa --input-file input.json
Expected output:
{
"result": "HELLO, ALICE!"
}
The initial input {"name": "Alice"} flows through Greet (producing
"Hello, Alice!") then through UpperCase (producing
"HELLO, ALICE!").
Validate everything
Run the full validation suite to confirm all blocks, fixtures, and workflow wiring are correct:
bashboa validate
Expected output:
Blocks: 2 registered
Fixtures: 4 passed, 0 failed
Workflows: 1 valid
Status: OK All valid
Create a UI Block
BOA also supports blocks that run in the browser. UI blocks are pure ES module functions — no stdin/stdout, just plain input and output. Let’s create one.
Create the block
Use the CLI to scaffold a UI block:
bashboa block create FormatGreeting --layer ui-block --runtime js
Write the block
UI blocks export a default function that takes an input object and returns an output object. No stdin, no JSON parsing — just a plain function:
index.js// Pure UI block — no side effects
export default function formatGreeting(input) {
const { name, greeting } = input;
return {
html: `<h1>${greeting}</h1><p>Welcome, ${name}.</p>`
};
}
And define the manifest in block.boa:
block.boaBLOCK FormatGreeting 1.0.0
LAYER ui-block
RUNTIME js
ENTRY index.js
DESC Formats a greeting into displayable HTML.
INTENT Render a greeting message as HTML for the browser.
RULE Output is an HTML string with the greeting and name.
TAGS ui, greeting, html, format
IN name:string!
IN greeting:string!
OUT html:string
FIXTURE {"name":"Alice","greeting":"Hello, Alice!"}
-> {"html":"<h1>Hello, Alice!</h1><p>Welcome, Alice.</p>"}
Test it
Run the fixtures to verify:
bashboa test FormatGreeting@1.0.0
UI blocks use the same testing infrastructure as backend blocks. The only difference is how the runtime invokes them.
import(). Both share the same block.boa manifest format and the same
testing tools.
Next Steps
You now have a working BOA project with blocks, a workflow, and a UI block. Here’s where to go next:
Architecture
Understand the five-layer model, URP, and how blocks compose into systems.
.boa Format
Complete syntax reference for block.boa, workflow.boa, blocks.boa, and project.boa.
Examples
Real-world code samples: order processing, tax calculation, multi-step workflows.
CLI Tools
Full command reference for boa init, run, test,
validate, impact, and more.