This feature is only available in paid plans. Learn More
Synced Blocks
What are Synced Blocks?
Synced Blocks allow users to create and maintain reusable content blocks that stay synchronized across multiple designs. When a Synced Block is updated, those changes become available to all instances of that block. The updates are applied when designs containing those blocks are loaded, but only if the instance hasn't been locally modified (is not "dirty"). This feature is perfect for maintaining consistency across designs where certain elements need to remain identical.
How Synced Blocks Work
When a user creates a Synced Block, the system assigns it a unique sync ID and stores metadata about its synchronization status. The block can then be used across multiple designs, all connected to the original version.
Sync States and Behavior
Synced Blocks can exist in different states:
- Clean State - Block is synced and unchanged locally
- Dirty State - Block has local modifications that haven't been saved to the synced version
- Outdated State - A newer version of the synced block is available
When changes are made to any instance of the block, users can:
- Update the Synced Block - Save local changes to make them available to all instances when they are loaded
- Restore the original - Discard local changes and restore the block to its last synced version
- Convert to Standard Block - Turn the block into a regular block, breaking the sync connection
Intelligent Sync Logic
The system uses sophisticated logic to determine when to apply updates:
- Dirty State Protection - Local changes are never overwritten automatically. Blocks marked as "dirty" remain unchanged until manually updated or restored.
- Timestamp Comparison - Updates are only applied if the synced block is newer than the local version.
- Auto-save Behavior - Regular blocks auto-save on changes, but synced blocks require manual confirmation to prevent unintended updates to linked designs.
Sync Metadata Structure
Synced Blocks maintain their status through several properties:
- Sync ID - Unique identifier connecting all instances of the same block
- Sync Enabled - Whether the block is currently part of the synchronization system
- Updated At - Timestamp indicating when the block was last updated
- Dirty - Boolean flag indicating if the block has unsaved local changes
Enable or Disable Feature
Synced Blocks are an advanced feature that requires both feature enablement and user entitlements. The feature must be enabled in two places:
Feature Configuration
Developers can control whether Synced Blocks are available in the editor using the following configuration:
unlayer.init({
features: {
syncedBlocks: true, // Enables synced blocks feature
},
});
Entitlements
Users must also have the syncedBlocks
entitlement enabled in their subscription plan. Both the feature flag and entitlement must be true
for synced blocks functionality to be available.
The editor automatically checks both conditions and only shows synced blocks options when both are satisfied.
Using Synced Blocks
Creating a Synced Block
Users can create a Synced Block through the Unlayer Console by enabling sync functionality when creating a new block.
To create Synced Blocks in your project:
- Log in to Unlayer Console
- Navigate to your project
- Go to Library > Blocks section
- Click "New Block"
- Fill in the block details and enable the sync option
- The block will be created with sync capabilities enabled
JavaScript API
If you want to save and load synced blocks from your own servers, you can use the following API.
When you load a design containing synced blocks, the editor automatically calls your blocks provider with the syncIds
found in the design.
Load Synced Blocks
unlayer.registerProvider('blocks', function (params, done) {
console.log('blocks provider called with:', params);
// For regular block loading:
// params = { userId: 123, displayMode: 'email', category: '', search: '' }
// For synced block loading (when design contains sync blocks):
// params = { userId: 123, displayMode: 'email', syncIds: ['sync-id-1', 'sync-id-2'] }
if (params.syncIds) {
// Called when loading a design with synced blocks
// Return only blocks that match the requested syncIds
const syncBlocks = blocks.filter((block) =>
params.syncIds.includes(block.syncMetadata?.id),
);
done(syncBlocks);
} else {
// Called for regular block browsing in the editor
done(blocks);
}
});
Example synced block data structure:
const blocks = [
{
id: 'block-123',
name: 'Header Block',
data: {
/* Your block design data */
},
// Required for synced blocks
syncMetadata: {
id: 'sync-456', // Unique sync identifier
enabled: true, // Must be true for synced blocks
updatedAt: '2024-01-15T10:30:00Z', // Last update timestamp
},
},
];
Events
unlayer.registerCallback('block:modified', function (existingBlock, done) {
console.log('block:modified', existingBlock);
// Update the synced block in your database here
if (existingBlock.syncMetadata?.enabled) {
// Handle synced block update
updateSyncBlockInDatabase(existingBlock)
.then((savedBlock) => {
// IMPORTANT: Update timestamp for proper sync
savedBlock.syncMetadata.updatedAt = new Date().toISOString();
done(savedBlock);
})
.catch((error) => {
console.error('Failed to save sync block:', error);
done(existingBlock); // Fallback to original data
});
} else {
done(existingBlock);
}
});
Error Handling
// Handle provider errors gracefully
unlayer.registerProvider('blocks', function (params, done) {
try {
if (params.syncIds) {
fetchSyncBlocks(params.syncIds)
.then((blocks) => done(blocks))
.catch((error) => {
console.error('Failed to load sync blocks:', error);
done([]); // Return empty array as fallback
});
} else {
fetchAllBlocks()
.then((blocks) => done(blocks))
.catch((error) => {
console.error('Failed to load blocks:', error);
done([]); // Graceful degradation
});
}
} catch (error) {
console.error('Provider error:', error);
done([]); // Always call done() to prevent hanging
}
});
Managing Synced Blocks in the Editor
When a Synced Block is selected in the editor, users will see a special "Synced Block" panel in the property editor with sync management options:
Clean State (Unmodified)
When the block hasn't been modified locally:
- Shows sync status information
- Displays last sync timestamp
- Offers option to convert to a standard block (removing sync)
Dirty State (Modified)
When the block has local changes that haven't been saved:
- Shows warning about unsaved changes
- Offers three options:
- Update Block - Save changes to the synced block (affects all instances)
- Restore Original - Discard local changes and restore from synced version
- Convert to Standard Block - Remove sync and keep local changes
Visual Indicators
Synced Blocks are clearly identified throughout the interface:
In the Editor
- Sync Block Indicator: Purple badge showing "Synced Block" with sync timestamp
- Dirty State Warning: Visual indicator when block has unsaved changes
- Property Panel: Special "Synced Block" section in the properties panel
In Block Libraries
- Sync Icon: Special link icon next to synced blocks in block lists
- Sync Metadata: Shows sync status and last update information
For Content Within Synced Blocks
When editing content inside a synced block (but not the block itself), a placeholder message appears directing users to select the entire row to manage sync settings.
Best Practices
When to Use Synced Blocks
- Branding Elements: Logo blocks, company signatures, or branded sections
- Legal Content: Disclaimers, unsubscribe sections, or compliance text
- Contact Information: Address blocks, social media links, or contact details
When NOT to Use Synced Blocks
- Dynamic Content: Blocks that frequently change or need customization per design
- Campaign-Specific Content: Content that varies between different campaigns or audiences
- One-off Designs: Content that won't be reused across multiple designs
Design Workflow Tips
- Plan Ahead: Identify reusable content before creating blocks
- Test First: Create and test blocks as regular blocks before converting to synced
- Communicate Changes: When updating synced blocks, ensure team members are aware of the impact
- Version Control: Consider creating new synced blocks for major changes rather than updating existing ones
- Regular Audits: Periodically review synced blocks to ensure they're still relevant and being used
Performance Considerations
- Synced blocks are loaded on-demand when designs are opened
- The system includes intelligent caching to minimize API calls
- Updates only occur when both feature flags and entitlements are enabled
- Dirty state tracking prevents unnecessary overwrites and preserves user intent
Troubleshooting
Synced Block Options Not Visible
- Verify the
syncedBlocks
feature is enabled in your configuration - Ensure user has
syncedBlocks
entitlement in their subscription - Check that you're selecting the row-level element, not content within it
Changes Not Syncing
- Confirm the block has a valid sync ID and is enabled
- Check if the block is in a "dirty" state (has unsaved local changes)
- Verify timestamp comparison - newer versions may not override older ones
Auto-save Not Working
- This is expected behavior for synced blocks
- Synced blocks require manual save confirmation to prevent unintended updates
- Use the "Update Block" button to save changes to all instances
Common Development Issues
Provider never receives syncIds:
// Check feature flags and entitlements
unlayer.init({
projectId: 'your-project',
features: {
syncedBlocks: true, // Must be enabled
},
});
// Verify user has entitlements
{
entitlements: {
syncedBlocks: true;
}
}
Sync blocks not updating across designs:
- Ensure
block:modified
callback updatessyncMetadata.updatedAt
- Check that timestamp format is ISO string:
new Date().toISOString()
- Verify the updated block is returned with correct
syncMetadata.id
Editor hangs or freezes:
- Always call
done()
in your provider, even on errors - Implement timeouts for async operations
- Use
done([])
as fallback for errors
Memory leaks with many sync blocks:
- Implement proper caching with expiration
- Clear old cache entries periodically
- Avoid storing large objects in global scope
Debug logging:
// Enable comprehensive logging
unlayer.registerProvider('blocks', function (params, done) {
console.log('Provider called:', {
timestamp: new Date().toISOString(),
params,
hasSyncIds: !!params.syncIds,
syncIdCount: params.syncIds?.length || 0,
});
// Your provider logic...
done(blocks);
});