Ember CLI's blueprint system provides template-based code generation for creating applications, addons, components, and other Ember.js resources. Blueprints are customizable templates that scaffold code with proper file structure, naming conventions, and boilerplate content.
The core class for creating and managing blueprints.
class Blueprint {
/**
* Extend the Blueprint class to create custom blueprints
* @param options - Blueprint configuration options
* @returns Extended Blueprint class
*/
static extend(options: BlueprintOptions): typeof Blueprint;
/**
* Look up a blueprint by name
* @param name - Blueprint name
* @param options - Lookup options
* @returns Blueprint instance or null
*/
static lookup(name: string, options?: LookupOptions): Blueprint | null;
/**
* List all available blueprints
* @param options - List options
* @returns Array of blueprint information
*/
static list(options?: ListOptions): BlueprintInfo[];
/**
* Install files from the blueprint
* @param options - Installation options
* @returns Promise resolving when installation is complete
*/
install(options: InstallOptions): Promise<void>;
/**
* Uninstall files created by the blueprint
* @param options - Uninstallation options
* @returns Promise resolving when uninstallation is complete
*/
uninstall(options: UninstallOptions): Promise<void>;
}
interface BlueprintOptions {
/** Blueprint name */
name: string;
/** Blueprint description */
description?: string;
/** Blueprint path */
path?: string;
/** Available options */
availableOptions?: BlueprintOption[];
/** Anonymous options */
anonymousOptions?: string[];
/** Before install hook */
beforeInstall?: (options: any) => Promise<void>;
/** After install hook */
afterInstall?: (options: any) => Promise<void>;
/** File map function */
fileMapTokens?: (options: any) => { [key: string]: string };
/** Locals function for template variables */
locals?: (options: any) => { [key: string]: any };
}
interface BlueprintOption {
/** Option name */
name: string;
/** Option type */
type: any;
/** Default value */
default?: any;
/** Option aliases */
aliases?: string[];
/** Option description */
description?: string;
}
interface InstallOptions {
/** Entity name (e.g., component name) */
entity: Entity;
/** UI instance */
ui: UI;
/** Project instance */
project: Project;
/** Target directory */
target?: string;
/** Dry run mode */
dryRun?: boolean;
/** Additional options */
[key: string]: any;
}
interface Entity {
/** Entity name */
name: string;
/** Entity options */
options: { [key: string]: any };
}Ember CLI includes several built-in blueprints for common scaffolding needs.
// App blueprint - creates full Ember.js application
// Usage: ember new my-app
interface AppBlueprintOptions {
/** Skip npm installation */
skipNpm?: boolean;
/** Skip git initialization */
skipGit?: boolean;
/** Include ember-welcome-page */
welcome?: boolean;
/** Package manager to use */
packageManager?: 'npm' | 'pnpm' | 'yarn';
/** Base human language */
lang?: string;
/** Enable Embroider build system */
embroider?: boolean;
/** CI provider configuration */
ciProvider?: 'github' | 'none';
/** Include ember-data */
emberData?: boolean;
}
// Addon blueprint - creates Ember CLI addon
// Usage: ember addon my-addon
interface AddonBlueprintOptions {
/** Skip npm installation */
skipNpm?: boolean;
/** Skip git initialization */
skipGit?: boolean;
/** Package manager to use */
packageManager?: 'npm' | 'pnpm' | 'yarn';
/** Base human language */
lang?: string;
/** CI provider configuration */
ciProvider?: 'github' | 'none';
}// In-repo-addon blueprint - creates addon within existing app
// Usage: ember generate in-repo-addon my-addon
interface InRepoAddonBlueprintOptions {
/** Addon name */
name: string;
}
// Http-mock blueprint - creates mock HTTP server
// Usage: ember generate http-mock api/users
interface HttpMockBlueprintOptions {
/** Mock endpoint path */
path: string;
/** HTTP methods to mock */
methods?: string[];
}
// Http-proxy blueprint - creates HTTP proxy configuration
// Usage: ember generate http-proxy api
interface HttpProxyBlueprintOptions {
/** Proxy path */
path: string;
/** Target URL */
target?: string;
}These blueprints are typically used via ember generate command and are extensible by addons.
// Standard blueprints available via ember generate:
// - component: Ember components
// - route: Application routes
// - controller: Route controllers
// - template: Handlebars templates
// - service: Application services
// - helper: Template helpers
// - model: Ember Data models
// - adapter: Ember Data adapters
// - serializer: Ember Data serializers
// - transform: Ember Data transforms
// - initializer: App initializers
// - instance-initializer: Instance initializers
// - mixin: Ember mixins
// - util: utility functionsCreate custom blueprints for project-specific scaffolding needs.
/**
* Example custom blueprint extending the base Blueprint class
*/
const Blueprint = require('ember-cli/lib/models/blueprint');
module.exports = Blueprint.extend({
name: 'my-custom-blueprint',
description: 'Generates a custom component with service injection',
availableOptions: [
{ name: 'service', type: String, description: 'Service to inject' },
{ name: 'skip-test', type: Boolean, default: false }
],
anonymousOptions: ['name'],
/**
* Define template variables
* @param options - Blueprint options
* @returns Template locals object
*/
locals(options) {
return {
componentName: options.entity.name,
serviceName: options.service || 'store',
className: this.classifyEntityName(options.entity.name)
};
},
/**
* Hook called before installation
* @param options - Installation options
*/
async beforeInstall(options) {
// Validate options or perform setup
if (options.service && !this.serviceExists(options.service)) {
throw new Error(`Service ${options.service} does not exist`);
}
},
/**
* Hook called after installation
* @param options - Installation options
*/
async afterInstall(options) {
this.ui.writeLine(`β
Generated ${options.entity.name} component`);
if (options.service) {
this.ui.writeLine(`π§ Configured with ${options.service} service`);
}
}
});Blueprints follow a specific directory structure for organizing templates and configuration.
// Blueprint directory structure:
// blueprints/
// βββ my-blueprint/
// βββ index.js # Blueprint configuration
// βββ files/ # Template files
// βββ app/
// β βββ __path__/
// β βββ __name__.js
// βββ tests/
// βββ unit/
// βββ __path__/
// βββ __name__-test.js
interface BlueprintFileStructure {
/** Blueprint configuration file */
'index.js': string;
/** Template files directory */
files: {
/** File path with tokens */
[path: string]: string;
};
}
// File path tokens:
// __name__ # Entity name
// __path__ # Entity path
// __root__ # Project root
// __test__ # Test file suffixCustomize how blueprint file paths are resolved using token replacement.
/**
* Define custom file path tokens
* @param options - Blueprint options
* @returns Token replacement map
*/
fileMapTokens(options) {
return {
__name__: options.entity.name,
__path__: options.entity.name.replace(/\//g, path.sep),
__component__: `components/${options.entity.name}`,
__service__: `services/${options.serviceName || 'store'}`
};
}Blueprint files support template syntax for dynamic content generation.
// Template file example (files/app/components/__name__.js):
import Component from '@glimmer/component';
<% if (serviceName) { %>import { inject as service } from '@ember/service';<% } %>
export default class <%= className %>Component extends Component {
<% if (serviceName) { %>@service <%= serviceName %>;<% } %>
// Component implementation
}
// Test template (files/tests/unit/components/__name__-test.js):
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('<%= classifyEntityName(name) %>', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
// Test implementation
assert.ok(true);
});
});Utility functions available within blueprint contexts for string manipulation and path handling.
interface BlueprintUtils {
/**
* Convert string to camelCase
* @param str - Input string
* @returns camelCase string
*/
camelizeEntityName(str: string): string;
/**
* Convert string to PascalCase
* @param str - Input string
* @returns PascalCase string
*/
classifyEntityName(str: string): string;
/**
* Convert string to dasherized format
* @param str - Input string
* @returns dasherized-string
*/
dasherizeEntityName(str: string): string;
/**
* Get entity path for file placement
* @param entity - Entity object
* @returns File path string
*/
pathForEntity(entity: Entity): string;
}# Generate component
ember generate component user-profile
# Generate component with options
ember generate component navigation --pod
# Generate service
ember generate service auth
# Generate route with model
ember generate route users
# Generate addon
ember addon my-awesome-addon
# Generate in-repo addon
ember generate in-repo-addon shared-components// blueprints/feature/index.js
const Blueprint = require('ember-cli/lib/models/blueprint');
module.exports = Blueprint.extend({
name: 'feature',
description: 'Generates a complete feature with component, service, and route',
availableOptions: [
{ name: 'skip-route', type: Boolean, default: false },
{ name: 'skip-service', type: Boolean, default: false }
],
async afterInstall(options) {
const entityName = options.entity.name;
// Generate component
await this.addBlueprintToProject('component', entityName);
// Generate service unless skipped
if (!options.skipService) {
await this.addBlueprintToProject('service', entityName);
}
// Generate route unless skipped
if (!options.skipRoute) {
await this.addBlueprintToProject('route', entityName);
}
this.ui.writeLine(`β
Generated complete ${entityName} feature`);
}
});const Blueprint = require('ember-cli/lib/models/blueprint');
const Project = require('ember-cli/lib/models/project');
async function generateComponent(name, options = {}) {
const project = Project.closestSync(process.cwd());
const blueprint = Blueprint.lookup('component');
if (!blueprint) {
throw new Error('Component blueprint not found');
}
await blueprint.install({
entity: { name, options },
project,
ui: project.ui
});
}
// Generate component programmatically
generateComponent('user-card', { pod: true })
.then(() => console.log('Component generated'))
.catch(console.error);const Blueprint = require('ember-cli/lib/models/blueprint');
// List all available blueprints
const blueprints = Blueprint.list({
paths: [project.root, ...project.addonPackages]
});
console.log('Available blueprints:');
blueprints.forEach(bp => {
console.log(`- ${bp.name}: ${bp.description}`);
});
// Find specific blueprint
const componentBlueprint = Blueprint.lookup('component', {
paths: [project.root]
});
if (componentBlueprint) {
console.log('Component blueprint found at:', componentBlueprint.path);
}// Blueprint with conditional files
module.exports = Blueprint.extend({
name: 'conditional-component',
locals(options) {
return {
includeService: options.service,
includeTest: !options.skipTest
};
},
files() {
let files = ['app/components/__name__.js'];
if (this.options.includeService) {
files.push('app/services/__name__.js');
}
if (this.options.includeTest) {
files.push('tests/unit/components/__name__-test.js');
}
return files;
}
});// Extend existing blueprint
const ComponentBlueprint = require('ember-cli/blueprints/component');
module.exports = ComponentBlueprint.extend({
name: 'enhanced-component',
locals(options) {
const parentLocals = this._super.locals.call(this, options);
return Object.assign(parentLocals, {
includeTracking: true,
componentType: 'enhanced'
});
}
});// Test blueprint functionality
const { setupTest } = require('ember-cli-blueprint-test-helpers');
const { expect } = require('chai');
describe('my-blueprint', function() {
setupTest();
it('generates expected files', async function() {
await this.generate(['my-blueprint', 'foo']);
expect(file('app/foo.js')).to.exist;
expect(file('tests/unit/foo-test.js')).to.exist;
});
it('supports options', async function() {
await this.generate(['my-blueprint', 'foo', '--skip-test']);
expect(file('app/foo.js')).to.exist;
expect(file('tests/unit/foo-test.js')).to.not.exist;
});
});