Skip to main content
Version: Latest
Paid Feature

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:

  1. Clean State - Block is synced and unchanged locally
  2. Dirty State - Block has local modifications that haven't been saved to the synced version
  3. Outdated State - A newer version of the synced block is available

When changes are made to any instance of the block, users can:

  1. Update the Synced Block - Save local changes to make them available to all instances when they are loaded
  2. Restore the original - Discard local changes and restore the block to its last synced version
  3. 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:

  1. Log in to Unlayer Console
  2. Navigate to your project
  3. Go to Library > Blocks section
  4. Click "New Block"
  5. Fill in the block details and enable the sync option
  6. 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

  1. Sync Block Indicator: Purple badge showing "Synced Block" with sync timestamp
  2. Dirty State Warning: Visual indicator when block has unsaved changes
  3. Property Panel: Special "Synced Block" section in the properties panel

In Block Libraries

  1. Sync Icon: Special link icon next to synced blocks in block lists
  2. 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

  1. Plan Ahead: Identify reusable content before creating blocks
  2. Test First: Create and test blocks as regular blocks before converting to synced
  3. Communicate Changes: When updating synced blocks, ensure team members are aware of the impact
  4. Version Control: Consider creating new synced blocks for major changes rather than updating existing ones
  5. 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 updates syncMetadata.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);
});