Validate your design before export by detecting elements outside the page, protruding content, obscured text, and other issues that could affect the final output quality in headless environments.
Pre-export validation catches layout and quality issues before export, preventing problems like cropped content, hidden text, and elements missing from the final output. Production-quality designs require elements to be properly positioned within the page boundaries.
This guide demonstrates how to detect elements outside the page, find protruding content, identify obscured text, and integrate validation into the export workflow in headless Node.js environments.
Getting Element Bounds#
To detect spatial issues, we need to get the bounding box of elements in global coordinates. The getGlobalBoundingBox* methods return positions that account for all transformations:
const x = engine.block.getGlobalBoundingBoxX(blockId);const y = engine.block.getGlobalBoundingBoxY(blockId);const width = engine.block.getGlobalBoundingBoxWidth(blockId);const height = engine.block.getGlobalBoundingBoxHeight(blockId);return [x, y, x + width, y + height];This returns coordinates as [x1, y1, x2, y2] representing the top-left and bottom-right corners of the element. The overlap between element and page bounds is calculated as the intersection area divided by the element’s total area. An overlap of 0 means completely outside, while 1 (100%) means fully inside.
Detecting Elements Outside the Page#
Elements completely outside the page won’t appear in the exported output. We find these by checking for blocks with zero overlap with the page bounds:
const blockBounds = getBoundingBox(engine, blockId);const overlap = calculateOverlap(blockBounds, pageBounds);
if (overlap === 0) { // Element is completely outside the pageThese issues are categorized as errors because the content is completely missing from the export.
Detecting Protruding Elements#
Elements that extend beyond the page boundaries will be partially cropped in the export. For each block, compare its bounds against the page bounds and calculate the overlap ratio:
// Compare element bounds against page boundsconst blockBounds = getBoundingBox(engine, blockId);const overlap = calculateOverlap(blockBounds, pageBounds);
// Protruding: partially inside (overlap > 0) but not fully inside (overlap < 1)if (overlap > 0 && overlap < 0.99) {An overlap between 0% and 100% indicates the element is partially inside the page. These issues are warnings because the content is partially visible but may not appear as intended.
Finding Obscured Text#
Text hidden behind other elements may be unreadable in the final export. First, get the stacking order and all text blocks:
const children = engine.block.getChildren(page);const textBlocks = engine.block.findByType('text');The getChildren() method returns blocks in stacking order - elements later in the array are rendered on top. For each text block, check if any non-text element above it overlaps with its bounds:
// Elements later in children array are rendered on topconst blocksAbove = children.slice(textIndex + 1);
for (const aboveId of blocksAbove) { // Skip text blocks - they don't typically obscure other text if (engine.block.getType(aboveId) === '//ly.img.ubq/text') continue;
const overlap = calculateOverlap( getBoundingBox(engine, textId), getBoundingBox(engine, aboveId) );
if (overlap > 0) { // Text is obscured by element above itWe skip text-on-text comparisons since transparent text backgrounds don’t typically obscure other text. When overlap is detected, we flag the text as potentially obscured.
Checking Placeholder Content#
Placeholders mark areas where users must add content before export. First, find all placeholder blocks in the design:
const placeholders = engine.block.findAllPlaceholders();Then inspect each placeholder’s fill to determine if content has been added. Get the fill block and check its type to determine the validation logic:
const fillId = engine.block.getFill(blockId);if (!fillId || !engine.block.isValid(fillId)) return false;
const fillType = engine.block.getType(fillId);
// Check image fill - empty URI means unfilled placeholderif (fillType === '//ly.img.ubq/fill/image') { const imageUri = engine.block.getString(fillId, 'fill/image/imageFileURI'); return imageUri !== '' && imageUri !== undefined;}For image placeholders, check if the fill/image/imageFileURI property has a value. An empty or undefined URI indicates the placeholder hasn’t been filled. Unfilled placeholders are treated as errors that block export, ensuring users complete all required content before exporting.
Integrating with Export#
In headless environments, run validation before export and handle results programmatically. Errors block the export entirely, while warnings can be logged but allow the export to proceed:
// Validate design before exportconst result = validateDesign(engine);
console.log('=== Pre-Export Validation ===');
// Log all issues for debuggingif (result.errors.length > 0) { console.error(`Found ${result.errors.length} error(s):`); result.errors.forEach((err) => console.error(` - ${err.blockName}: ${err.message}`) );}
if (result.warnings.length > 0) { console.warn(`Found ${result.warnings.length} warning(s):`); result.warnings.forEach((warn) => console.warn(` - ${warn.blockName}: ${warn.message}`) );}
// Block export for errorsif (result.errors.length > 0) { console.error('\nExport blocked: Fix errors before exporting'); process.exit(1);}
// Allow export with warningsif (result.warnings.length > 0) { console.log('\nProceeding with export despite warnings...');} else { console.log('\nValidation passed - no issues found');}
// Export the designconst outputDir = './output';if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
const blob = await engine.block.export(page, { mimeType: 'image/png' });const buffer = Buffer.from(await blob.arrayBuffer());writeFileSync(`${outputDir}/validated-design.png`, buffer);console.log('Export successful: output/validated-design.png');When validation fails, log the issues and exit with an error code so calling systems know the export was blocked.
API Reference#
| Method | Purpose |
|---|---|
engine.block.getGlobalBoundingBoxX(id) | Get element’s global X position |
engine.block.getGlobalBoundingBoxY(id) | Get element’s global Y position |
engine.block.getGlobalBoundingBoxWidth(id) | Get element’s global width |
engine.block.getGlobalBoundingBoxHeight(id) | Get element’s global height |
engine.block.findByType(type) | Find all blocks of a specific type |
engine.block.getChildren(id) | Get child blocks in stacking order |
engine.block.getType(id) | Get the block’s type string |
engine.block.getName(id) | Get the block’s display name |
engine.block.isValid(id) | Check if block exists |
engine.block.findAllPlaceholders() | Find all placeholder blocks |
engine.block.getFill(id) | Get the fill block |
engine.block.getString(id, property) | Get a string property value |