Blueprints
Unified schema system - define once, use everywhere for validation, forms, models, and CRUD.
Overview
Blueprints are Domma's unified schema system. Define your data structure once, then use it everywhere:
Validation
Built-in + custom
Forms
Auto-generation
Models
Reactive data
CRUD
Complete system
<div class="grid grid-cols-4 grid-cols-md-4 gap-4 mb-4"><div class="card text-center">
<div class="card-body">
<span data-icon="check-circle" data-size="32" class="text-success mb-2"></span>
<h4 class="text-sm font-semibold">Validation</h4>
<p class="text-xs text-muted">Built-in + custom</p>
</div>
</div>
<div class="card text-center">
<div class="card-body">
<span data-icon="layout" data-size="32" class="text-primary mb-2"></span>
<h4 class="text-sm font-semibold">Forms</h4>
<p class="text-xs text-muted">Auto-generation</p>
</div>
</div>
<div class="card text-center">
<div class="card-body">
<span data-icon="database" data-size="32" class="text-info mb-2"></span>
<h4 class="text-sm font-semibold">Models</h4>
<p class="text-xs text-muted">Reactive data</p>
</div>
</div>
<div class="card text-center">
<div class="card-body">
<span data-icon="grid" data-size="32" class="text-warning mb-2"></span>
<h4 class="text-sm font-semibold">CRUD</h4>
<p class="text-xs text-muted">Complete system</p>
</div>
</div>
</div>Full Documentation: See the Reference Documentation for comprehensive guide with tutorials and examples.
Blueprint Anatomy
A blueprint is a JavaScript object defining field types, validation, and UI configuration.
const userBlueprint = {
name: {
type: 'string', // Field type
required: true, // Validation
minLength: 2,
maxLength: 50,
label: 'Full Name', // Form config
placeholder: 'Enter name'
},
email: {
type: 'email',
required: true,
label: 'Email Address',
validate: (value) => { // Custom validator
return /\S+@\S+/.test(value) || 'Invalid email';
}
},
age: {
type: 'number',
min: 0,
max: 120,
label: 'Age'
},
active: {
type: 'boolean',
default: true,
label: 'Active User'
}
};
<div class="blueprint-methods">
<span class="blueprint-method">type</span>
<span class="blueprint-method">required</span>
<span class="blueprint-method">default</span>
<span class="blueprint-method">min / max</span>
<span class="blueprint-method">minLength / maxLength</span>
<span class="blueprint-method">pattern</span>
<span class="blueprint-method">validate</span>
<span class="blueprint-method">label</span>
<span class="blueprint-method">placeholder</span>
<span class="blueprint-method">help</span>
</div>Built-in Types
Domma provides 7 built-in types for common data structures:
M.types.string
Text data
M.types.number
Numeric values
M.types.boolean
True/false
M.types.array
Lists
M.types.object
Nested data
M.types.date
Timestamps
M.types.any
No validation
<div class="grid grid-cols-4 grid-cols-md-3 gap-3"><div class="card">
<div class="card-body">
<code class="text-primary">M.types.string</code>
<p class="text-xs text-muted mt-1">Text data</p>
</div>
</div>
<div class="card">
<div class="card-body">
<code class="text-primary">M.types.number</code>
<p class="text-xs text-muted mt-1">Numeric values</p>
</div>
</div>
<div class="card">
<div class="card-body">
<code class="text-primary">M.types.boolean</code>
<p class="text-xs text-muted mt-1">True/false</p>
</div>
</div>
<div class="card">
<div class="card-body">
<code class="text-primary">M.types.array</code>
<p class="text-xs text-muted mt-1">Lists</p>
</div>
</div>
<div class="card">
<div class="card-body">
<code class="text-primary">M.types.object</code>
<p class="text-xs text-muted mt-1">Nested data</p>
</div>
</div>
<div class="card">
<div class="card-body">
<code class="text-primary">M.types.date</code>
<p class="text-xs text-muted mt-1">Timestamps</p>
</div>
</div>
<div class="card">
<div class="card-body">
<code class="text-primary">M.types.any</code>
<p class="text-xs text-muted mt-1">No validation</p>
</div>
</div>
</div>Blueprint Examples
Explore pre-built blueprints for common use cases.
<div id="blueprint-tabs"></div>/**
* Blueprint Examples - from public/showcase/blueprints.
*/
export function mount(root) {
const blueprints = {
contact: {
name: { type: 'string', required: true, minLength: 2, label: 'Full Name', placeholder: 'Enter your full name' },
email: { type: 'email', required: true, label: 'Email Address', placeholder: 'you@example.com' },
message: { type: 'textarea', required: true, minLength: 10, rows: 4, label: 'Message', placeholder: 'Your message here...' }
},
registration: {
email: { type: 'email', required: true, label: 'Email Address' },
password: {
type: 'password',
required: true,
minLength: 8,
label: 'Password',
help: 'At least 8 characters',
validate: (value) => {
if (!/[A-Z]/.test(value)) return 'Need uppercase letter';
if (!/[0-9]/.test(value)) return 'Need a number';
return true;
}
},
confirmPassword: {
type: 'password',
required: true,
label: 'Confirm Password',
validate: (value, data) => {
return value === data.password || 'Passwords do not match';
}
},
agreeToTerms: {
type: 'boolean',
required: true,
label: 'I agree to the Terms of Service',
validate: (value) => value || 'You must accept the terms'
}
},
settings: {
notifications: { type: 'boolean', default: true, label: 'Email Notifications' },
theme: { type: 'select', options: ['light', 'dark', 'auto'], default: 'auto', label: 'Theme' },
language: {
type: 'radio',
options: [
{ value: 'en', label: 'English' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' }
],
default: 'en',
label: 'Language'
}
},
product: {
name: {
type: 'string',
required: true,
maxLength: 100,
label: 'Product Name',
formConfig: {
span: 2, // Span 2 columns in grid layout
tooltip: 'The display name of your product'
}
},
sku: {
type: 'string',
required: true,
pattern: /^[A-Z0-9-]+$/,
label: 'SKU',
formConfig: {
placeholder: 'e.g. PROD-001',
tooltip: 'Stock Keeping Unit - unique product identifier'
}
},
price: {
type: 'number',
required: true,
min: 0,
step: 0.01,
label: 'Price (£)',
formConfig: {
placeholder: '0.00',
tooltip: 'Product price in GBP'
}
},
category: {
type: 'select',
required: true,
options: ['Electronics', 'Clothing', 'Home'],
label: 'Category',
formConfig: {
tooltip: 'Product category for organisation'
}
},
stock: {
type: 'number',
required: true,
min: 0,
label: 'Stock Quantity',
formConfig: {
placeholder: '0',
tooltip: 'Current available inventory'
}
},
description: {
type: 'textarea',
required: true,
maxLength: 1000,
rows: 5,
label: 'Description',
formConfig: {
span: 2, // Full width in 2-column grid
placeholder: 'Product description...',
helperText: 'Maximum 1000 characters',
tooltip: 'Detailed product description for customers'
}
},
inStock: {
type: 'boolean',
default: true,
label: 'In Stock',
formConfig: { span: 2 } // Full width
}
},
userProfile: {
firstName: {
type: 'string',
required: true,
minLength: 2,
label: 'First Name',
formConfig: { placeholder: 'Enter first name' }
},
lastName: {
type: 'string',
required: true,
minLength: 2,
label: 'Last Name',
formConfig: { placeholder: 'Enter last name' }
},
email: {
type: 'email',
required: true,
label: 'Email Address',
formConfig: {
placeholder: 'you@example.com',
helperText: 'We will never share your email'
}
},
phone: {
type: 'tel',
label: 'Phone Number',
formConfig: { placeholder: '+44 20 1234 5678' }
},
bio: {
type: 'textarea',
rows: 4,
maxLength: 500,
label: 'Biography',
formConfig: {
span: 2, // Full width
placeholder: 'Tell us about yourself...',
helperText: 'Maximum 500 characters'
}
},
website: {
type: 'url',
label: 'Website',
formConfig: {
placeholder: 'https://example.com',
hint: '(optional)'
}
},
role: {
type: 'select',
required: true,
options: ['Developer', 'Designer', 'Manager', 'Other'],
label: 'Role',
default: 'Developer'
},
newsletter: {
type: 'boolean',
label: 'Subscribe to newsletter',
default: false,
formConfig: { span: 2 }
}
}
};
// Initialize blueprint tabs - CREATE HTML FIRST!
const tabItems = [
{ label: 'Contact', value: 'contact' },
{ label: 'Registration', value: 'registration' },
{ label: 'Settings', value: 'settings' },
{ label: 'Product (Grid)', value: 'product' },
{ label: 'User Profile (Grid)', value: 'userProfile' }
];
// Build tabs HTML structure
let tabsHTML = '<div class="tabs"><div class="tab-list" role="tablist">';
tabItems.forEach((item, index) => {
tabsHTML += `<button type="button" class="tab-item${index === 0 ? ' active' : ''}" role="tab" data-tab="${item.value}">${item.label}</button>`;
});
tabsHTML += '</div><div class="tabs-content">';
tabItems.forEach((item, index) => {
tabsHTML += `<div class="tab-panel${index === 0 ? ' active' : ''}" role="tabpanel" data-panel="${item.value}" id="tab-${item.value}"></div>`;
});
tabsHTML += '</div></div>';
// Sanitize and insert HTML
$(root).find('#blueprint-tabs').html(tabsHTML);
// NOW initialize the tabs component
const tabsInstance = E.tabs($(root).find('#blueprint-tabs .tabs').get(0), {
animation: true,
onChange: (e) => {
const exampleKey = tabItems[e.index].value;
showBlueprintExample(exampleKey);
}
});
function showBlueprintExample(key) {
const blueprint = blueprints[key];
const container = $(root).find(`#tab-${key}`);
// Escape JSON for safe HTML insertion
const blueprintCode = JSON.stringify(blueprint, null, 2)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
const html = `
<div class="grid grid-cols-1 grid-cols-md-2 gap-4">
<div>
<h4 class="font-semibold mb-2">Blueprint Code</h4>
<pre class="blueprint-code">${blueprintCode}</pre>
</div>
<div>
<h4 class="font-semibold mb-2">Generated Form</h4>
<div class="result-pane" id="form-${key}"></div>
</div>
</div>
`;
container.html(html);
// Generate and render form - ONE-STEP API
// Use grid layout for product and userProfile examples
const formLayout = (key === 'product' || key === 'userProfile') ? 'grid' : 'stacked';
const formColumns = 2;
F.render(`#form-${key}`, blueprint, {}, {
layout: formLayout,
columns: formColumns,
submitText: 'Submit',
onSubmit: (data) => {
E.toast('Form submitted successfully!', { type: 'success' });
console.log('Submitted:', data);
},
onValidationError: (errors) => {
E.toast('Please fix validation errors', { type: 'error' });
console.error('Validation errors:', errors);
}
});
}
// Show first example
showBlueprintExample('contact');
return () => tabsInstance.destroy();
}Field-Level Styling
Control form layout and field appearance using formConfig properties.
Form-Level Options
F.render('#form', blueprint, {}, {
layout: 'grid', // or 'stacked', 'inline'
columns: 2, // 2 or 3 columns (grid only)
labelPosition: 'top', // or 'left', 'floating'
showLabels: true,
showHelperText: true
});
Field-Level Options
fieldName: {
type: 'string',
required: true,
label: 'Field Label',
formConfig: {
span: 2, // Span 2 columns
placeholder: 'Text', // Placeholder
helperText: 'Help', // Below field
hint: '(optional)', // Next to label
tooltip: 'Info', // Hover tooltip (NEW!)
disabled: false // Disable field
}
}
Grid Layout Example
Use layout: 'grid' with columns: 2 and formConfig.span to control field width:
const addressBlueprint = {
street: {
type: 'string',
required: true,
label: 'Street Address',
formConfig: { span: 2 } // Full width (spans 2 columns)
},
city: {
type: 'string',
required: true,
label: 'City'
// No span = takes 1 column (left side)
},
postcode: {
type: 'string',
required: true,
label: 'Postcode'
// No span = takes 1 column (right side)
},
country: {
type: 'select',
options: ['UK', 'USA', 'Canada'],
label: 'Country',
formConfig: { span: 2 } // Full width
}
};
// Render with grid layout
F.render('#form', addressBlueprint, {}, {
layout: 'grid',
columns: 2
});
Tip: The Product (Grid) and User Profile (Grid) tabs above demonstrate grid layout with field spanning.
Blueprint → Form Generation
Automatically generate forms from blueprints with full validation.
Blueprint Definition
const contactBlueprint = {
name: {
type: 'string',
required: true,
minLength: 2,
label: 'Full Name'
},
email: {
type: 'email',
required: true,
label: 'Email'
},
message: {
type: 'textarea',
required: true,
minLength: 10,
rows: 4,
label: 'Message'
}
};
// Generate and render form (one-step)
F.render(
'#contact-form', // selector
contactBlueprint, // schema
{}, // initial data
{ // options
layout: 'stacked',
onSubmit: (data) => {
console.log(data);
}
}
);
Generated Form
<div class="grid grid-cols-1 grid-cols-md-2 gap-4"><div>
<h4 class="font-semibold mb-2">Blueprint Definition</h4>
<pre class="code-block language-javascript">const contactBlueprint = {
name: {
type: 'string',
required: true,
minLength: 2,
label: 'Full Name'
},
email: {
type: 'email',
required: true,
label: 'Email'
},
message: {
type: 'textarea',
required: true,
minLength: 10,
rows: 4,
label: 'Message'
}
};
// Generate and render form (one-step)
F.render(
'#contact-form', // selector
contactBlueprint, // schema
{}, // initial data
{ // options
layout: 'stacked',
onSubmit: (data) => {
console.log(data);
}
}
);
</pre>
</div>
<div>
<h4 class="font-semibold mb-2">Generated Form</h4>
<div class="result-pane">
<div id="contact-form"></div>
</div>
</div>
</div>/**
* Blueprint → Form Generation - from public/showcase/blueprints.
*/
export function mount(root) {
// Contact Form Example - ONE-STEP API
const contactBlueprint = {
name: {
type: 'string',
required: true,
minLength: 2,
label: 'Full Name',
formConfig: {
tooltip: 'Enter your first and last name'
}
},
email: {
type: 'email',
required: true,
label: 'Email',
formConfig: {
tooltip: 'We\'ll never share your email with anyone'
}
},
message: {
type: 'textarea',
required: true,
minLength: 10,
rows: 4,
label: 'Message',
formConfig: {
tooltip: 'Let us know how we can help you'
}
}
};
F.render('#contact-form', contactBlueprint, {}, {
layout: 'stacked',
submitText: 'Send Message',
onSubmit: (data) => {
E.toast('Message sent!', { type: 'success' });
console.log('Form submitted:', data);
},
onValidationError: (errors) => {
E.toast('Please fix validation errors', { type: 'error' });
console.error('Validation errors:', errors);
}
});
}Blueprint → Reactive Model
Create reactive data models with automatic validation and change tracking.
Code
const user = M.create(userBlueprint, {
name: 'Alice',
email: 'alice@example.com',
age: 30
});
// Get values
user.get('name'); // 'Alice'
// Set values
user.set('age', 31);
// Validate
const errors = user.validate();
// Listen for changes
user.onChange((event) => {
console.log(event);
});
Try It
Model State
Current Data:
{}
Change Log:
No changes yet
<div class="grid grid-cols-1 grid-cols-md-2 gap-4"><div>
<h4 class="font-semibold mb-2">Code</h4>
<pre class="code-block language-javascript">const user = M.create(userBlueprint, {
name: 'Alice',
email: 'alice@example.com',
age: 30
});
// Get values
user.get('name'); // 'Alice'
// Set values
user.set('age', 31);
// Validate
const errors = user.validate();
// Listen for changes
user.onChange((event) => {
console.log(event);
});
</pre>
<div class="mt-3">
<h5 class="font-semibold mb-2">Try It</h5>
<div class="form-group">
<label class="form-label">Name</label>
<input type="text" id="model-name" class="form-input" value="Alice">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input type="email" id="model-email" class="form-input" value="alice@example.com">
</div>
<div class="form-group">
<label class="form-label">Age</label>
<input type="number" id="model-age" class="form-input" value="30">
</div>
<button type="button" class="btn btn-primary btn-sm" id="validate-model">Validate</button>
<button type="button" class="btn btn-secondary btn-sm" id="reset-model">Reset</button>
</div>
</div>
<div>
<h4 class="font-semibold mb-2">Model State</h4>
<div class="model-state">
<h4>Current Data:</h4>
<pre id="model-state-display">{}</pre>
</div>
<div class="model-state mt-3" id="validation-result" style="display: none;">
<h4>Validation Result:</h4>
<pre id="validation-display"></pre>
</div>
<div class="model-state mt-3">
<h4>Change Log:</h4>
<pre id="model-change-log">No changes yet</pre>
</div>
</div>
</div>/**
* Blueprint → Reactive Model - from public/showcase/blueprints.
*/
export function mount(root) {
// Reactive Model Demo
const userBlueprint = {
name: { type: 'string', required: true, minLength: 2 },
email: { type: 'email', required: true },
age: { type: 'number', min: 0, max: 120 }
};
const userModel = M.create(userBlueprint, {
name: 'Alice',
email: 'alice@example.com',
age: 30
});
let changeLog = [];
// Update state display
function updateModelState() {
$(root).find('#model-state-display').text(JSON.stringify(userModel.toJSON(), null, 2));
}
// Manual binding - M.bind() doesn't work with standard HTML inputs
$(root).find('#model-name').on('input', function() {
try {
userModel.set('name', $(this).val());
} catch (error) {
console.warn('Validation error (name):', error.message);
}
});
$(root).find('#model-email').on('input', function() {
try {
userModel.set('email', $(this).val());
} catch (error) {
console.warn('Validation error (email):', error.message);
}
});
$(root).find('#model-age').on('input', function() {
try {
const value = $(this).val();
userModel.set('age', value === '' ? null : parseInt(value, 10));
} catch (error) {
console.warn('Validation error (age):', error.message);
}
});
// Listen for changes
userModel.onChange((event) => {
changeLog.push(`${event.field}: ${event.oldValue} → ${event.newValue}`);
$(root).find('#model-change-log').text(changeLog.join('\n'));
updateModelState();
// Update input if changed programmatically
if (event.field === 'name') $(root).find('#model-name').val(event.newValue);
if (event.field === 'email') $(root).find('#model-email').val(event.newValue);
if (event.field === 'age') $(root).find('#model-age').val(event.newValue);
});
updateModelState();
// Validate button
$(root).find('#validate-model').on('click', () => {
const errors = userModel.validate();
// Display validation result
if (errors && errors.length > 0) {
$(root).find('#validation-display').text(JSON.stringify(errors, null, 2));
$(root).find('#validation-result').show();
E.toast('Validation errors found', { type: 'error' });
} else {
$(root).find('#validation-display').text('✓ All fields are valid');
$(root).find('#validation-result').show();
E.toast('All fields valid!', { type: 'success' });
}
});
// Reset button
$(root).find('#reset-model').on('click', () => {
userModel.reset();
changeLog = [];
$(root).find('#model-change-log').text('No changes yet');
$(root).find('#validation-result').hide();
updateModelState();
});
}Blueprint Composition
Combine, extend, and modify blueprints with composition methods.
const base = {
name: { type: 'string' },
email: { type: 'email' }
};
const extended = {
email: { required: true },
age: { type: 'number' }
};
const result = B.extend(base, extended);
// { name: {...}, email: {required: true}, age: {...} }
<div class="grid grid-cols-1 grid-cols-md-2 gap-4">
<div>
<pre class="code-block language-javascript">const base = {
name: { type: 'string' },
email: { type: 'email' }
};
const extended = {
email: { required: true },
age: { type: 'number' }
};
const result = B.extend(base, extended);
// { name: {...}, email: {required: true}, age: {...} }
</pre>
</div>
<div>
<button type="button" class="btn btn-primary mb-2" id="extend-demo">Run Example</button>
<div class="result-pane" id="extend-result" style="display: none;">
<h5>Result:</h5>
<pre id="extend-result-code"></pre>
</div>
</div>
</div>/**
* B.extend() - Merge Blueprints - from public/showcase/blueprints.
*/
export function mount(root) {
// Composition demos
$(root).find('#extend-demo').on('click', () => {
const base = { name: { type: 'string' }, email: { type: 'email' } };
const extended = { email: { required: true }, age: { type: 'number' } };
const result = B.extend(base, extended);
$(root).find('#extend-result-code').text(JSON.stringify(result, null, 2));
$(root).find('#extend-result').show();
});
}const full = {
name: { type: 'string' },
email: { type: 'email' },
age: { type: 'number' },
role: { type: 'string' }
};
const subset = B.pick(full, ['name', 'email']);
// { name: {...}, email: {...} }
<div class="grid grid-cols-1 grid-cols-md-2 gap-4">
<div>
<pre class="code-block language-javascript">const full = {
name: { type: 'string' },
email: { type: 'email' },
age: { type: 'number' },
role: { type: 'string' }
};
const subset = B.pick(full, ['name', 'email']);
// { name: {...}, email: {...} }
</pre>
</div>
<div>
<button type="button" class="btn btn-primary mb-2" id="pick-demo">Run Example</button>
<div class="result-pane" id="pick-result" style="display: none;">
<h5>Result:</h5>
<pre id="pick-result-code"></pre>
</div>
</div>
</div>/**
* B.pick() - Extract Fields - from public/showcase/blueprints.
*/
export function mount(root) {
$(root).find('#pick-demo').on('click', () => {
const full = {
name: { type: 'string' },
email: { type: 'email' },
age: { type: 'number' },
role: { type: 'string' }
};
const result = B.pick(full, ['name', 'email']);
$(root).find('#pick-result-code').text(JSON.stringify(result, null, 2));
$(root).find('#pick-result').show();
});
}const admin = {
name: { type: 'string' },
email: { type: 'email' },
role: { type: 'string' },
permissions: { type: 'array' }
};
const public = B.omit(admin, ['permissions']);
// { name: {...}, email: {...}, role: {...} }
<div class="grid grid-cols-1 grid-cols-md-2 gap-4">
<div>
<pre class="code-block language-javascript">const admin = {
name: { type: 'string' },
email: { type: 'email' },
role: { type: 'string' },
permissions: { type: 'array' }
};
const public = B.omit(admin, ['permissions']);
// { name: {...}, email: {...}, role: {...} }
</pre>
</div>
<div>
<button type="button" class="btn btn-primary mb-2" id="omit-demo">Run Example</button>
<div class="result-pane" id="omit-result" style="display: none;">
<h5>Result:</h5>
<pre id="omit-result-code"></pre>
</div>
</div>
</div>/**
* B.omit() - Remove Fields - from public/showcase/blueprints.
*/
export function mount(root) {
$(root).find('#omit-demo').on('click', () => {
const admin = {
name: { type: 'string' },
email: { type: 'email' },
role: { type: 'string' },
permissions: { type: 'array' }
};
const result = B.omit(admin, ['permissions']);
$(root).find('#omit-result-code').text(JSON.stringify(result, null, 2));
$(root).find('#omit-result').show();
});
}Full CRUD System
Blueprints power complete CRUD interfaces with tables, forms, and API integration.
CRUD Examples: For complete CRUD demonstrations with blueprints, see the Forms Showcase which includes full CRUD functionality with API integration, table management, and modal forms.
A typical CRUD setup with blueprints includes:
- Data Table - Display records with sorting, filtering, pagination
- Create Form - Modal form generated from blueprint
- Edit Form - Pre-populated form for updating records
- Delete Confirmation - Safe deletion with confirmation
- Validation - Automatic validation from blueprint rules
- API Integration - RESTful API calls or localStorage persistence
See It In Action: Visit the Forms Showcase → CRUD Section for live interactive examples.