Skip to main content

Custom Guidebook Template Development

This guide provides technical documentation for developers looking to create custom guidebook templates. It covers the template structure, available variables, and best practices for building secure, functional templates.

Template Architecture

SendSquared guidebooks use a Pug (formerly Jade) template engine with Tailwind CSS for styling. Templates are server-side rendered with dynamic data interpolation.

Core Template Structure

doctype html
html(lang="en")
head
// Meta tags, fonts, external libraries
title #{company.name} | #{reservation.units[0].name}

body
nav
// Navigation structure
main
// Content sections
footer
// Footer content

// JavaScript functionality

Variable Interpolation System

The template system uses a secure interpolation function to replace variables in content blocks:

Available Context Objects

The interpolation system provides access to three main context objects:

company Object

  • company.name - Company name
  • company.address_1 - Primary address
  • company.address_2 - Secondary address
  • company.locality - City
  • company.region - State/Province
  • company.postal - ZIP/Postal code
  • company.country - Country

contact Object

  • contact.first_name - Guest first name
  • contact.last_name - Guest last name
  • contact.mobile_phone - Phone number
  • contact.primaryEmail.email_address - Primary email

reservation Object

  • reservation.reservation_number - Booking reference
  • reservation.uuid - Unique reservation ID
  • reservation.arrival_date - Check-in date
  • reservation.departure_date - Check-out date
  • reservation.nights - Number of nights
  • reservation.adults - Adult guest count
  • reservation.children - Child guest count
  • reservation.pets - Pet count
  • reservation.total_revenue - Total booking value (in cents)
  • reservation.units[0].name - Property name
  • reservation.units[0].address_1 - Property address
  • reservation.units[0].address_2 - Secondary property address
  • reservation.units[0].locality - Property city
  • reservation.units[0].region - Property state/province
  • reservation.units[0].postal - Property ZIP/postal code
  • reservation.units[0].country - Property country

guidebook Object

  • guidebook.name - Guidebook title
  • guidebook.description - Guidebook description (supports HTML)
  • guidebook.public_name - Public display name for the guidebook
  • guidebook.primary_color - Primary theme color (hex)
  • guidebook.secondary_color - Secondary theme color (hex)
  • guidebook.background_color - Background color (hex)
  • guidebook.backgroundAsset - Background image asset object
  • guidebook.backgroundAsset.url - Background image URL
  • guidebook.collapsible_headers - Boolean for collapsible section headers
  • guidebook.default_collapsed - Boolean for default collapsed state
  • guidebook.show_all_images - Boolean to show all images vs. single image
  • guidebook.pdf_exportable - Boolean for PDF export availability
  • guidebook.trip_advisor_link - TripAdvisor URL
  • guidebook.google_local_link - Google Reviews URL
  • guidebook.blocks - Array of guidebook blocks

guidebook.blocks Array Objects

Each block in the guidebook.blocks array contains:

Basic Block Properties:

  • block.id - Unique block identifier
  • block.name - Block title/name
  • block.description - Block content (supports HTML)
  • block.guidebook_block_type - Block type ('identity', 'houseGuides', 'houseAccess', 'areaGuides', 'restaurantGuides', 'activityGuides', 'legalTerms')
  • block.isVisible - Boolean visibility flag
  • block.assets - Array of media assets

Location Properties (for location-based blocks):

  • block.latitude - Latitude coordinate
  • block.longitude - Longitude coordinate
  • block.map_link - Link to map/directions

Asset Properties:

  • block.assets[].id - Asset unique identifier
  • block.assets[].name - Asset filename
  • block.assets[].url - Asset URL
  • block.assets[].mime_type - MIME type (image/jpeg, video/mp4, application/pdf, etc.)
  • block.assets[].thumbnail_width - Custom thumbnail width in pixels

Special Block Properties:

  • block.verification_assets - Number of required identity documents (identity blocks)
  • block.terms_text - Terms and conditions text (legal terms blocks)

Additional Template Variables

Display Control Variables:

  • showAddress - Boolean indicating if property address should be shown
  • hideEmail - Boolean to hide guest email address
  • hidePhoneNumber - Boolean to hide guest phone number
  • hidePrice - Boolean to hide reservation price
  • revealDate - Date when address information becomes visible
  • logo - Company logo URL
  • uploadPath - Path for identity document uploads
  • unitAssets - Array of property image assets
  • verificationAssets - Count of uploaded identity documents
  • termsAcceptances - Array of accepted legal terms

Formatting Helper Variables:

  • locale - Locale setting for date formatting
  • colors - Computed color scheme object
  • bgImage - Background image CSS properties

Variable Usage in Content

Variables are used in content blocks using double curly braces:

// In template
p Welcome #{contact.first_name} to #{reservation.units[0].name}

// In content blocks
p Welcome {{contact.first_name}} to {{company.name}}

Security Considerations

The interpolation system includes safety measures:

  • Variables are evaluated in a controlled context
  • Invalid variable paths return the original placeholder
  • HTML content is sanitized when rendered

Theme Configuration

Color System

Templates support dynamic theming through CSS custom properties:

const colors = {
primary: guidebook?.primary_color || '#1E40AF',
secondary: guidebook?.secondary_color || '#6366F1',
background: guidebook?.background_color || '#F3F4F6'
}

Background Assets

Optional background images can be configured:

const bgImage = guidebook?.backgroundAsset ? 
{ page: `url('${guidebook.backgroundAsset.url}')` } : {}

Block Types and Rendering

Supported Block Types

Templates handle several predefined block types:

  • identity - Identity verification blocks
  • houseGuides - Property guide information
  • houseAccess - Access instructions
  • areaGuides - Local area information
  • restaurantGuides - Dining recommendations
  • activityGuides - Activity suggestions
  • legalTerms - Terms and conditions

Block Rendering Logic

each type in ['identity', 'houseGuides', 'houseAccess', 'areaGuides', 'restaurantGuides', 'activityGuides', 'legalTerms']
if guidebook.blocks.some(block => block.guidebook_block_type === type && (block.isVisible === undefined || block.isVisible === true))
.mb-8
h3.text-lg.font-bold.mb-4
// Block type specific icons and headers
.grid.grid-cols-1.gap-4
each block in guidebook.blocks.filter(b => b.guidebook_block_type === type && (b.isVisible === undefined || b.isVisible === true))
// Block content rendering

Dynamic Content Features

Collapsible Headers

Templates support collapsible content sections:

function toggleBlockContent(contentId) {
const content = document.getElementById(contentId);
// Toggle display logic
}

Media Galleries

Blocks can include image galleries with lightbox functionality:

if block.assets && block.assets.length > 0
.w-full.block-gallery.mt-4
// Swiper carousel for multiple images
// Single image display for single assets

Interactive Features

Map Integration

Templates include comprehensive Google Maps integration for displaying location-based blocks and property information:

let map;
let markers = [];
let infoWindows = [];

function initMap() {
const mapOptions = {
zoom: 15,
center: { lat: 0, lng: 0 },
mapTypeControl: true,
streetViewControl: true,
fullscreenControl: true,
styles: [
{
featureType: "poi",
elementType: "labels",
stylers: [{ visibility: "off" }]
}
]
};

map = new google.maps.Map(document.getElementById('map'), mapOptions);

// Set map center to property or company address
setMapCenter();

// Add markers for guidebook blocks
addBlockMarkers();
}

function setMapCenter() {
const geocoder = new google.maps.Geocoder();

// Try property address first if available
if (showAddress) {
const propertyAddress = buildAddressString(reservation.units[0]);
geocoder.geocode({ address: propertyAddress }, (results, status) => {
if (status === 'OK') {
const location = results[0].geometry.location;
map.setCenter(location);

// Add property marker
const propertyMarker = new google.maps.Marker({
map: map,
position: location,
title: reservation.units[0].name,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 10,
fillColor: '#FF0000',
fillOpacity: 1,
strokeWeight: 2,
strokeColor: '#FFFFFF'
}
});

// Add info window for property
const infoWindow = new google.maps.InfoWindow({
content: createPropertyInfoWindow(reservation.units[0])
});

propertyMarker.addListener('click', () => {
infoWindow.open(map, propertyMarker);
});
}
});
}
}

function addBlockMarkers() {
// Clear existing markers
markers.forEach(marker => marker.setMap(null));
infoWindows.forEach(window => window.close());
markers = [];
infoWindows = [];

// Add markers for each guidebook block with location data
blockLocations.forEach(block => {
if (block.latitude && block.longitude) {
const position = {
lat: parseFloat(block.latitude),
lng: parseFloat(block.longitude)
};

const marker = new google.maps.Marker({
position: position,
map: map,
title: block.name,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 8,
fillColor: getMarkerColor(block.type),
fillOpacity: 1,
strokeWeight: 2,
strokeColor: '#FFFFFF'
}
});

const infoWindow = new google.maps.InfoWindow({
content: createBlockInfoWindow(block)
});

marker.addListener('click', () => {
// Close other info windows
infoWindows.forEach(window => window.close());
infoWindow.open(map, marker);

// Scroll to block in list view
scrollToBlock(block.id);
});

markers.push(marker);
infoWindows.push(infoWindow);
}
});
}

function getMarkerColor(blockType) {
const colorMap = {
'restaurantGuides': '#FF6B6B', // Red for restaurants
'activityGuides': '#4ECDC4', // Teal for activities
'areaGuides': '#45B7D1', // Blue for area info
'houseGuides': '#96CEB4', // Green for house info
'houseAccess': '#FFEEAD' // Yellow for access info
};
return colorMap[blockType] || '#1E40AF';
}

function createBlockInfoWindow(block) {
let content = `
<div class="p-4 max-w-sm">
<h3 class="font-bold text-lg mb-2">${block.name}</h3>
`;

// Add image if available
if (block.assets && block.assets.length > 0) {
const firstImage = block.assets.find(asset =>
!asset.mime_type.includes('video') && !asset.mime_type.includes('pdf')
);
if (firstImage) {
content += `
<img src="${firstImage.url}" alt="${block.name}"
class="w-full h-32 object-cover rounded mb-2">
`;
}
}

content += `
<p class="text-sm text-gray-600 mb-2">${block.description}</p>
<button onclick="toggleView('list'); scrollToBlock('${block.id}')"
class="text-primary hover:underline text-sm">
View Details
</button>
</div>
`;

return content;
}

function scrollToBlock(blockId) {
const blockElement = document.getElementById(`block-${blockId}`);
if (blockElement) {
blockElement.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
}

Adding Custom Markers

To add custom markers for specific locations:

function addCustomMarker(lat, lng, title, content, color = '#1E40AF') {
const marker = new google.maps.Marker({
position: { lat: lat, lng: lng },
map: map,
title: title,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 8,
fillColor: color,
fillOpacity: 1,
strokeWeight: 2,
strokeColor: '#FFFFFF'
}
});

const infoWindow = new google.maps.InfoWindow({
content: content
});

marker.addListener('click', () => {
infoWindow.open(map, marker);
});

return marker;
}

Identity Verification

For identity blocks, templates include file upload functionality. You can call the upload handler with the following signature:

handleUpload(file, documentType)

Parameters:

  • file - The file object from the input element
  • documentType - String indicating the document type (e.g., 'document', 'passport', 'license')

The system handles the complete upload process including signed URL generation, S3 upload, and confirmation.

Legal terms blocks include acceptance functionality. You can call the acceptance handler with the following signature:

acceptLegalTerms(event, blockId)

Parameters:

  • event - The form submission event object
  • blockId - The unique identifier for the legal terms block

The system handles IP tracking, backend submission, and UI updates automatically.

Template Development Best Practices

Security Guidelines

  1. Variable Sanitization: Always sanitize user-generated content
  2. Context Limitation: Only expose necessary data objects
  3. XSS Prevention: Avoid using != for untrusted content
  4. Input Validation: Validate all user inputs on the server side

Performance Optimization

  1. Asset Optimization: Compress images and minimize file sizes
  2. Lazy Loading: Implement lazy loading for media assets
  3. Caching: Use appropriate caching headers for static assets
  4. CDN Integration: Leverage CDNs for external libraries

Responsive Design

  1. Mobile-First: Design for mobile devices first
  2. Breakpoint Strategy: Use Tailwind's responsive utilities
  3. Touch Interactions: Ensure touch-friendly interface elements
  4. Viewport Configuration: Set appropriate viewport meta tags

Accessibility

  1. Semantic HTML: Use proper HTML5 semantic elements
  2. Alt Text: Provide descriptive alt text for images
  3. Keyboard Navigation: Ensure keyboard accessibility
  4. Screen Reader Support: Test with screen reading software

External Library Integration

Required Libraries

Templates include several external libraries:

  • Tailwind CSS: Utility-first CSS framework
  • Font Awesome: Icon library
  • Google Fonts: Typography (Open Sans)
  • PhotoSwipe: Image lightbox functionality
  • Swiper: Touch slider/carousel functionality
  • Google Maps API: Map integration
  • html2pdf.js: PDF export functionality

Loading Strategy

// CDN-based loading for reliability
script(src="https://cdn.tailwindcss.com")
link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css")

Testing and Deployment

Template Validation

  1. Syntax Checking: Validate Pug template syntax
  2. Variable Testing: Test all variable interpolations
  3. Cross-Browser Testing: Verify compatibility across browsers
  4. Device Testing: Test on various screen sizes

Content Security

  1. Sanitization: Ensure all dynamic content is properly sanitized
  2. Variable Validation: Test with malicious variable inputs
  3. XSS Testing: Verify protection against cross-site scripting
  4. Content Injection: Test against content injection attacks

Customization Guidelines

Extending Block Types

When adding new block types:

  1. Update the block type enumeration
  2. Add appropriate rendering logic
  3. Include necessary styling classes
  4. Test variable interpolation

Adding Interactive Features

For new interactive elements:

  1. Use progressive enhancement principles
  2. Provide fallbacks for JavaScript-disabled users
  3. Ensure accessibility compliance
  4. Test across different devices

Styling Customization

When modifying styles:

  1. Use Tailwind utility classes when possible
  2. Create custom CSS only when necessary
  3. Maintain responsive design principles
  4. Test color contrast ratios

This documentation provides the foundation for building secure, functional guidebook templates while maintaining compatibility with the SendSquared platform's data structure and security requirements.