Blocks Controller
Path: internal/api/v1/funnels/controllers/blocks.controller.js
Service: funnelBlockService
Module: Funnels
Overview
The Blocks controller manages reusable UI components (blocks) for the funnel builder. Blocks are pre-designed sections (headers, footers, hero sections, forms, etc.) that users can save and reuse across multiple funnels and steps.
Key Capabilities
- User Blocks: Save and manage account-specific blocks
- Global Blocks: Manage system-wide template blocks (admin)
- Block Library: Browse available blocks with filtering
- Cursor Pagination: Efficient browsing of large block collections
- Type Filtering: Filter blocks by type (header, footer, hero, etc.)
Methods
saveBlocks()
Saves a reusable block to the user's account library.
Route: POST /v1/funnels/blocks
Auth: Required (account-scoped)
Request Body
{
"blockData": {
"name": "Hero Section - Modern",
"type": "hero",
"thumbnail": "https://cdn.example.com/thumbnails/hero-modern.jpg",
"components": {
"version": "1.0",
"elements": [
{
"id": "hero-container",
"type": "container",
"settings": {
"background": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"padding": "80px 20px",
"textAlign": "center"
},
"children": [
{
"id": "hero-heading",
"type": "heading",
"tag": "h1",
"content": "Build Amazing Landing Pages",
"settings": {
"fontSize": "48px",
"fontWeight": "bold",
"color": "#ffffff"
}
},
{
"id": "hero-subheading",
"type": "paragraph",
"content": "Create high-converting funnels in minutes",
"settings": {
"fontSize": "20px",
"color": "#f0f0f0"
}
},
{
"id": "hero-cta",
"type": "button",
"content": "Get Started Free",
"settings": {
"backgroundColor": "#ff6b6b",
"color": "#ffffff",
"padding": "15px 40px",
"borderRadius": "5px"
}
}
]
}
]
},
"html": "<div class='hero-section'>...</div>",
"css": ".hero-section { /* styles */ }",
"tags": ["hero", "gradient", "modern"],
"category": "hero-sections"
}
}
Response
{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b32",
"account_id": "60d5ec49f1b2c72e8c8e4b19",
"name": "Hero Section - Modern",
"type": "hero",
"thumbnail": "https://cdn.example.com/thumbnails/hero-modern.jpg",
"components": {
/* component data */
},
"html": "<div class='hero-section'>...</div>",
"css": ".hero-section { /* styles */ }",
"tags": ["hero", "gradient", "modern"],
"category": "hero-sections",
"created_at": "2024-01-22T15:00:00Z",
"updated_at": "2024-01-22T15:00:00Z"
}
}
MongoDB Collections
| Collection | Operation | Purpose |
|---|---|---|
funnel_blocks | insertOne | Create new user block |
getBlocks()
Retrieves user's saved blocks with cursor pagination and filtering.
Route: GET /v1/funnels/blocks
Auth: Required (account-scoped)
Request Query Parameters
{
limit?: number; // Results per page (default: 20)
cursor?: string; // Cursor for pagination (ObjectId)
type?: string; // Filter by block type
}
Response
{
"success": true,
"message": "SUCCESS",
"data": {
"blocks": [
{
"_id": "60d5ec49f1b2c72e8c8e4b32",
"account_id": "60d5ec49f1b2c72e8c8e4b19",
"name": "Hero Section - Modern",
"type": "hero",
"thumbnail": "https://cdn.example.com/thumbnails/hero-modern.jpg",
"components": {
/* component data */
},
"html": "<div class='hero-section'>...</div>",
"css": ".hero-section { /* styles */ }",
"tags": ["hero", "gradient", "modern"],
"category": "hero-sections",
"created_at": "2024-01-22T15:00:00Z"
},
{
"_id": "60d5ec49f1b2c72e8c8e4b33",
"account_id": "60d5ec49f1b2c72e8c8e4b19",
"name": "Contact Form - Simple",
"type": "form",
"thumbnail": "https://cdn.example.com/thumbnails/form-simple.jpg",
"components": {
/* component data */
},
"html": "<form class='contact-form'>...</form>",
"css": ".contact-form { /* styles */ }",
"tags": ["form", "contact", "simple"],
"category": "forms",
"created_at": "2024-01-20T12:30:00Z"
}
],
"next_cursor": "60d5ec49f1b2c72e8c8e4b34",
"has_more": true,
"total": 45
}
}
MongoDB Collections
| Collection | Operation | Purpose |
|---|---|---|
funnel_blocks | find with cursor | Query user blocks with pagination |
Cursor Pagination
- First Request: Omit
cursorparameter to get first page - Subsequent Requests: Use
next_cursorfrom previous response - Cursor Format: MongoDB ObjectId as string
- Sorting: Blocks sorted by
created_atdescending (newest first)
deleteBlocks()
Deletes a user's saved block.
Route: DELETE /v1/funnels/blocks/:id
Auth: Required (account-scoped)
Request Parameters
{
id: string; // Block ID to delete
}
Response
{
"success": true,
"message": "SUCCESS"
}
MongoDB Collections
| Collection | Operation | Purpose |
|---|---|---|
funnel_blocks | deleteOne | Remove user block |
saveGlobalBlocks()
Saves a block to the global template library (admin only).
Route: POST /v1/funnels/blocks/global
Auth: Required (admin permissions)
Request Body
{
"blockData": {
"name": "Premium Hero - Gradient",
"type": "hero",
"thumbnail": "https://cdn.example.com/thumbnails/premium-hero.jpg",
"components": {
/* component data */
},
"html": "<div class='premium-hero'>...</div>",
"css": ".premium-hero { /* styles */ }",
"tags": ["hero", "premium", "gradient"],
"category": "hero-sections",
"featured": true,
"premium": false
}
}
Response
{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b34",
"is_global": true,
"name": "Premium Hero - Gradient",
"type": "hero",
"thumbnail": "https://cdn.example.com/thumbnails/premium-hero.jpg",
"components": {
/* component data */
},
"html": "<div class='premium-hero'>...</div>",
"css": ".premium-hero { /* styles */ }",
"tags": ["hero", "premium", "gradient"],
"category": "hero-sections",
"featured": true,
"premium": false,
"created_at": "2024-01-22T16:00:00Z"
}
}
MongoDB Collections
| Collection | Operation | Purpose |
|---|---|---|
funnel_global_blocks | insertOne | Create global block |
Authorization
- Requires admin permissions (verified in service layer)
- Standard users cannot access this endpoint
getGlobalBlocks()
Retrieves global template blocks available to all users.
Route: GET /v1/funnels/blocks/global
Auth: Required
Request Query Parameters
{
limit?: number; // Results per page (default: 20)
page?: number; // Page number (1-indexed)
cursor?: string; // Cursor for pagination (alternative to page)
type?: string; // Filter by block type
}
Response
{
"success": true,
"message": "SUCCESS",
"data": {
"blocks": [
{
"_id": "60d5ec49f1b2c72e8c8e4b34",
"is_global": true,
"name": "Premium Hero - Gradient",
"type": "hero",
"thumbnail": "https://cdn.example.com/thumbnails/premium-hero.jpg",
"components": {
/* component data */
},
"html": "<div class='premium-hero'>...</div>",
"css": ".premium-hero { /* styles */ }",
"tags": ["hero", "premium", "gradient"],
"category": "hero-sections",
"featured": true,
"premium": false,
"created_at": "2024-01-22T16:00:00Z"
},
{
"_id": "60d5ec49f1b2c72e8c8e4b35",
"is_global": true,
"name": "Testimonial Carousel",
"type": "testimonial",
"thumbnail": "https://cdn.example.com/thumbnails/testimonial-carousel.jpg",
"components": {
/* component data */
},
"html": "<div class='testimonial-carousel'>...</div>",
"css": ".testimonial-carousel { /* styles */ }",
"tags": ["testimonial", "carousel", "social-proof"],
"category": "testimonials",
"featured": false,
"premium": true,
"created_at": "2024-01-21T10:00:00Z"
}
],
"next_cursor": "60d5ec49f1b2c72e8c8e4b36",
"has_more": true,
"total": 158
}
}
MongoDB Collections
| Collection | Operation | Purpose |
|---|---|---|
funnel_global_blocks | find with cursor | Query global blocks |
Filtering
- Featured blocks: Prioritized in results (sorted first)
- Premium blocks: Flagged for paid accounts
- Type filtering: Exact match on
typefield - Search: Can be extended to support name/tag search
deleteGlobalBlocks()
Deletes a global template block (admin only).
Route: DELETE /v1/funnels/blocks/global/:id
Auth: Required (admin permissions)
Request Parameters
{
id: string; // Global block ID to delete
}
Response
{
"success": true,
"message": "SUCCESS"
}
MongoDB Collections
| Collection | Operation | Purpose |
|---|---|---|
funnel_global_blocks | deleteOne | Remove global block |
Authorization
- Requires admin permissions (verified in service layer)
- Standard users cannot delete global blocks
Data Models
User Block Document
{
_id: ObjectId;
account_id: ObjectId; // Owner account
name: string; // Block display name
type: string; // Block type (hero, form, footer, etc.)
thumbnail?: string; // Preview image URL
// Component structure
components: {
version: string;
elements: Array<{
id: string;
type: string;
tag?: string;
content?: string;
settings: Record<string, any>;
children?: Array<any>;
}>;
};
// Rendered output
html?: string; // Compiled HTML
css?: string; // Compiled CSS
// Metadata
tags?: string[]; // Searchable tags
category?: string; // Organization category
description?: string; // Block description
// Usage tracking
use_count?: number; // Times block used
last_used_at?: Date; // Last usage timestamp
created_at: Date;
updated_at: Date;
}
Global Block Document
{
_id: ObjectId;
is_global: true; // Flag for global blocks
name: string; // Block display name
type: string; // Block type
thumbnail?: string; // Preview image URL
// Component structure (same as user blocks)
components: {
version: string;
elements: Array<any>;
};
html?: string; // Compiled HTML
css?: string; // Compiled CSS
// Metadata
tags?: string[]; // Searchable tags
category?: string; // Organization category
description?: string; // Block description
// Template flags
featured: boolean; // Show in featured section
premium: boolean; // Requires premium account
// Usage analytics
total_uses?: number; // Total times used across all accounts
popularity_score?: number; // Calculated popularity metric
created_at: Date;
updated_at: Date;
created_by?: ObjectId; // Admin user who created
}
Block Types
Common Block Types
| Type | Description | Common Use Cases |
|---|---|---|
hero | Hero sections | Landing page headers, above-the-fold content |
form | Form sections | Lead capture, contact forms, checkout |
testimonial | Testimonial displays | Social proof, customer reviews |
pricing | Pricing tables | Product pricing, plan comparison |
features | Feature showcases | Product features, benefits |
cta | Call-to-action sections | Conversion-focused sections |
footer | Page footers | Navigation, legal links, contact info |
header | Page headers | Navigation bars, top sections |
gallery | Image galleries | Product images, portfolio |
video | Video embeds | YouTube, Vimeo, custom video |
faq | FAQ sections | Q&A, accordion content |
team | Team member displays | About page, team showcase |
contact | Contact sections | Contact info, maps, forms |
countdown | Countdown timers | Limited offers, event countdowns |
Business Logic & Workflows
Block Save Flow
sequenceDiagram
participant User
participant Controller
participant Service
participant Database
User->>Controller: POST /blocks (saveBlocks)
Controller->>Service: saveBlocks()
Service->>Database: Check duplicate name
alt Name Available
Service->>Service: Process component structure
Service->>Service: Generate HTML/CSS
Service->>Database: Insert block document
Database-->>Service: Block created
Service-->>Controller: Block data
Controller-->>User: 200 OK + block
else Name Exists
Service-->>Controller: Duplicate name error
Controller-->>User: 400 Bad Request
end
Block Usage Flow
sequenceDiagram
participant Builder
participant Blocks
participant Funnel
participant Database
Builder->>Blocks: GET /blocks (user + global)
Blocks->>Database: Query user blocks
Blocks->>Database: Query global blocks
Database-->>Blocks: Block collections
Blocks-->>Builder: Combined block list
Builder->>Builder: User selects block
Builder->>Funnel: Add block to step
Funnel->>Database: Save step with block components
Funnel->>Blocks: Increment block use_count
Database-->>Funnel: Step saved
Funnel-->>Builder: Success
Performance Considerations
Cursor Pagination
- More efficient than offset pagination for large datasets
- Uses
_idfield as cursor (always indexed) - Prevents page drift as data changes
- No expensive COUNT queries required
Query Optimization
- Indexed fields:
account_id,type,created_at,is_global - Compound indexes:
(account_id, type),(is_global, featured, type) - Thumbnail URLs: Served via CDN for fast loading
Caching Strategy
- Global blocks: Cached for 1 hour (rarely change)
- User blocks: No caching (frequently modified)
- Thumbnail images: CDN cached with long TTL
Integration Points
Funnel Builder
- Purpose: Drag-and-drop block insertion into funnel steps
- Integration: Blocks provide component JSON for builder
- Workflow: User selects block → Components copied to step
CDN/Asset Storage
- Purpose: Store and serve block thumbnails
- Integration: Thumbnail URLs point to CDN
- Upload: Admin uploads thumbnails to CDN, stores URL
Template Marketplace
- Purpose: Browse and purchase premium blocks
- Integration: Global blocks with
premium: trueflag - Monetization: Premium blocks require subscription
Security
Multi-Tenant Isolation
- User blocks: Always filtered by
account_id - Global blocks: Accessible to all authenticated users
- Delete operations: Verify account ownership
Authorization
- User blocks: Any authenticated user
- Global blocks (read): Any authenticated user
- Global blocks (write/delete): Admin only
Input Validation
- Component structure validation (prevent XSS)
- HTML sanitization for user-provided content
- CSS validation (prevent malicious styles)
Error Handling
Common Errors
// Block not found
{
"success": false,
"message": "Block not found",
"statusCode": 404
}
// Duplicate block name
{
"success": false,
"message": "Block with this name already exists",
"statusCode": 409
}
// Invalid block type
{
"success": false,
"message": "Invalid block type",
"statusCode": 400
}
// Unauthorized (global block operations)
{
"success": false,
"message": "Admin permissions required",
"statusCode": 403
}
Best Practices
When to Use This Controller
- ✅ Building drag-and-drop page builders
- ✅ Creating template marketplaces
- ✅ Implementing reusable component libraries
- ✅ Providing pre-designed sections
Common Patterns
- Combine user + global blocks: Merge both collections for complete library
- Type-based filtering: Allow users to filter by block type
- Preview images: Always include thumbnails for visual selection
- Version components: Use component version for backward compatibility
Edge Cases
- Large block libraries: Use cursor pagination for performance
- Deleted blocks in use: Blocks used in funnels remain even if deleted from library
- Premium blocks: Check account subscription before allowing usage
- Duplicate names: Allow duplicates between user blocks and global blocks
Related Documentation
- Funnels Controller: Using blocks in funnel steps
- Global Templates Controller: Template management
- Builder Components: Component structure specification
- Asset Management: Thumbnail upload and CDN integration
Last Updated: January 2025
API Version: v1
Maintained By: DashClicks Engineering