Concierge Worker
A Cloudflare Worker in Rust that provides forms, calendars, and event/venue booking. Embed forms and calendars in your sites using HTMX or iframes.
Features
Forms
- Dynamic Forms - Create forms with custom fields via admin dashboard
- Field Types - Text, email, mobile, long text, file upload
- Customizable Styling - Full CSS variable control per form
- File Uploads - R2 storage with configurable size limits
- Auto-Responders - Send acknowledgement messages via SMS, WhatsApp, or Email
- Digest Notifications - Receive daily or weekly summaries of submissions
Calendars & Bookings
- Multiple Calendars - Create and manage independent calendars
- Booking Links - Public forms for users to book time slots
- Booking Approval - Auto-accept or require manual admin approval
- View Links - Embeddable calendar views (week, month, year, list)
- iCal Feeds - Subscribe to calendars from other apps
- Instagram Integration - Auto-import events from posts using AI
Admin
- Dashboard - Manage all forms and calendars in one place
- Dark Mode - Automatic dark mode based on system preference
- Cloudflare Access - Secure authentication via Zero Trust
Quick Start
Architecture
┌─────────────────────────────────────────────────────────┐
│ Cloudflare Edge │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Worker │ │ D1 │ │ KV │ │
│ │ (Rust) │──│ (SQLite) │ │ (Key-Value) │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ │ │ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ R2 │ │ Cloudflare AI │ │
│ │ (Storage) │ │ (Instagram) │ │
│ └─────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
- D1 - Stores bookings and form submissions
- KV - Stores form/calendar configurations
- R2 - Stores file uploads
- AI - Extracts events from Instagram posts
Deployment
Prerequisites
- Cloudflare account
- Nix with flakes enabled (recommended)
- Or: Rust toolchain with
wasm32-unknown-unknowntarget
Setup
1. Clone and enter dev environment
git clone https://github.com/ananthb/concierge-worker.git
cd concierge-worker
# Using Nix (recommended)
nix develop
# Or using direnv
direnv allow
2. Create Cloudflare resources
# Create D1 database
wrangler d1 create concierge-worker
# Create KV namespace
wrangler kv namespace create concierge-worker
# Create R2 bucket (for file uploads)
wrangler r2 bucket create concierge-worker-uploads
3. Update wrangler.toml
Replace the placeholder IDs with the values from the commands above:
[[d1_databases]]
binding = "DB"
database_name = "concierge-worker"
database_id = "YOUR_DATABASE_ID" # from wrangler d1 create
[[kv_namespaces]]
binding = "KV"
id = "YOUR_KV_ID" # from wrangler kv namespace create
[[r2_buckets]]
binding = "UPLOADS"
bucket_name = "concierge-worker-uploads"
4. Run database migrations
wrangler d1 migrations apply concierge-worker
5. Deploy
Option A: Deploy from local machine
wrangler deploy
Option B: Deploy via GitHub Actions
- Go to your GitHub repo Settings > Secrets and variables > Actions
- Add a secret named
CLOUDFLARE_API_TOKENwith a Cloudflare API token - Push to
mainbranch to trigger automatic deployment
Cloudflare Access Setup
The admin dashboard requires Cloudflare Access for authentication.
- Go to Cloudflare Zero Trust
- Navigate to Access > Applications
- Click Add an application > Self-hosted
- Configure:
- Application name: Concierge Worker Admin
- Session duration: 24 hours (or your preference)
- Application domain:
your-worker.your-subdomain.workers.dev - Path:
/admin
- Add a policy to control who can access (e.g., email ends with
@yourdomain.com) - Save
Local Development
# Run locally with simulated bindings
wrangler dev
# Run locally with real D1/KV/R2 (requires account)
wrangler dev --remote
For local development, set ENVIRONMENT = "development" in wrangler.toml to bypass Access authentication.
Environment Variables
| Variable | Description | Required |
|---|---|---|
ENVIRONMENT | Set to development to bypass auth | No |
See Configuration for secrets setup.
Configuration
All sensitive configuration is stored as Cloudflare Workers secrets.
Setting Secrets
Use the Wrangler CLI to set secrets:
wrangler secret put SECRET_NAME
You’ll be prompted to enter the value securely.
Environment Variables
| Variable | Description | Required |
|---|---|---|
ENVIRONMENT | Set to development to bypass auth | No |
All Secrets Reference
| Secret | Purpose |
|---|---|
TWILIO_SID | Twilio Account SID |
TWILIO_TOKEN | Twilio Auth Token |
TWILIO_FROM_SMS | SMS sender number (e.g., +15551234567) |
TWILIO_FROM_WHATSAPP | WhatsApp sender (e.g., whatsapp:+15551234567) |
TWILIO_FROM_EMAIL | Email sender address for Twilio/SendGrid |
SENDGRID_API_KEY | SendGrid API key (for Twilio email) |
WHATSAPP_ACCESS_TOKEN | WhatsApp Business API access token |
WHATSAPP_PHONE_NUMBER_ID | WhatsApp Business API phone number ID |
RESEND_API_KEY | Resend API key |
RESEND_FROM | Resend sender address |
GOOGLE_SERVICE_ACCOUNT_JSON | Google service account JSON (for Sheets) |
ENCRYPTION_KEY | Token encryption key (for Instagram) |
INSTAGRAM_APP_ID | Meta/Instagram app ID |
INSTAGRAM_APP_SECRET | Meta/Instagram app secret |
Notification Channels
Important: The Responders and Digest tabs in the admin UI are only visible when at least one notification channel is configured.
Twilio SMS
wrangler secret put TWILIO_SID
wrangler secret put TWILIO_TOKEN
wrangler secret put TWILIO_FROM_SMS
Twilio WhatsApp
wrangler secret put TWILIO_SID
wrangler secret put TWILIO_TOKEN
wrangler secret put TWILIO_FROM_WHATSAPP
WhatsApp Business API (Meta)
For direct WhatsApp Business API integration:
wrangler secret put WHATSAPP_ACCESS_TOKEN
wrangler secret put WHATSAPP_PHONE_NUMBER_ID
Twilio Email (via SendGrid)
wrangler secret put SENDGRID_API_KEY
wrangler secret put TWILIO_FROM_EMAIL
Resend Email
wrangler secret put RESEND_API_KEY
wrangler secret put RESEND_FROM
Channel Detection
The admin UI automatically detects which channels are available:
| Channel | Required Secrets |
|---|---|
| Twilio SMS | TWILIO_SID + TWILIO_FROM_SMS |
| Twilio WhatsApp | TWILIO_SID + TWILIO_FROM_WHATSAPP |
| Twilio Email | SENDGRID_API_KEY + TWILIO_FROM_EMAIL |
| Resend Email | RESEND_API_KEY + RESEND_FROM |
Google Sheets Integration
For syncing form submissions to Google Sheets:
wrangler secret put GOOGLE_SERVICE_ACCOUNT_JSON
The value should be the entire JSON content of your Google Cloud service account key file.
Instagram Integration
For automatic event extraction from Instagram posts:
# Generate with: openssl rand -hex 32
wrangler secret put ENCRYPTION_KEY
wrangler secret put INSTAGRAM_APP_ID
wrangler secret put INSTAGRAM_APP_SECRET
Setting up Meta App
- Go to Meta for Developers
- Create an app with Instagram Basic Display product
- Configure OAuth redirect URI:
https://your-worker.workers.dev/instagram/callback - Add test users while in development mode
Forms
Create dynamic forms with custom fields, styling, and automated responses.
Creating a Form
- Go to the admin dashboard (
/admin) - Click + Create Form
- Configure the form settings
Form Settings
Basic Settings
| Field | Description |
|---|---|
| Form Name | Internal name (shown in admin) |
| Slug | URL path (e.g., contact → /f/contact) |
| Form Title | Displayed to users |
| Submit Button Text | Button label |
| Success Message | Shown after submission |
| Allowed Origins | Domains that can embed this form |
| Google Sheet URL | Optional: sync submissions to a spreadsheet |
Field Types
| Type | HTML Input | Validation |
|---|---|---|
| Text | <input type="text"> | None |
<input type="email"> | Email format | |
| Mobile | <input type="tel"> | Phone format |
| Long Text | <textarea> | None |
| File | <input type="file"> | Size limit |
Field Configuration
Each field has:
- Label - Displayed above the input
- Field ID - Used in templates and data (e.g.,
name,email) - Type - Input type (see above)
- Required - Whether the field must be filled
Styling
Forms can be styled using CSS variables. See CSS Customization for details.
Show/Hide Title
Toggle “Show form title” in the Styling tab to hide the title when embedding.
Custom CSS
Add custom CSS rules in the Styling tab:
.contact-form {
max-width: 500px;
}
button {
text-transform: uppercase;
}
Viewing Responses
- Go to admin dashboard
- Click Responses next to the form
- View all submissions in a table
Archiving Forms
Archive forms to make them read-only without deleting data:
- Click Archive in the dashboard
- Form becomes read-only
- Click Unarchive to restore
Deleting Forms
Warning: This permanently deletes the form and all submissions.
- Open the form editor
- Scroll to Danger Zone
- Click Delete Form
Calendars & Bookings
Create calendars with time slots for appointments, events, or venue bookings.
Creating a Calendar
- Go to the admin dashboard (
/admin) - Click + New Calendar
- Configure calendar settings
Calendar Settings
Basic Settings
| Field | Description |
|---|---|
| Name | Calendar name |
| Description | Optional description |
| Timezone | Calendar timezone |
| Allowed Domains | Domains that can embed |
| Custom CSS | Additional styling |
Time Slots
Configure when bookings are available:
- Go to calendar editor
- Click Bookings tab
- Click Configure Available Slots
Recurring Slots
Set weekly availability:
| Field | Description |
|---|---|
| Day of Week | Monday, Tuesday, etc. |
| Start Time | When slots begin |
| End Time | When slots end |
| Duration | Length of each slot (minutes) |
| Capacity | Max bookings per slot |
Specific Date Slots
Add availability for specific dates (overrides weekly pattern).
Booking Links
Public pages where users can book time slots.
Creating a Booking Link
- Go to calendar editor
- Click Bookings tab
- Click + Add Booking Link
Booking Link Settings
| Field | Description |
|---|---|
| Name | Link name |
| Slug | URL path |
| Duration | Booking duration (minutes) |
| Confirmation Message | Shown after booking |
| Hide Title | Hide name when embedded |
| Auto-Accept | Confirm immediately or require approval |
Custom Fields
Add fields to collect information during booking:
- Name, Email, Phone (built-in)
- Custom text fields
- Notes/comments
Booking Approval
When Auto-Accept is disabled:
- Booking is created with “Pending” status
- Admin receives notification (if configured)
- Admin clicks approval link
- Customer receives confirmation
View Links
Public calendar views that can be embedded or shared.
View Types
| Type | Description |
|---|---|
| Week | 7-day view with time grid |
| Month | Monthly calendar grid |
| Year | Yearly overview |
| List | Scrolling list of events |
Creating a View Link
- Go to calendar editor
- Click Settings tab
- Click + Add View Link
iCal Feeds
Subscribe to the calendar from other apps (Google Calendar, Apple Calendar, etc.):
- Go to calendar editor
- Click Settings tab
- Click + Add Feed Link
- Copy the feed URL with token
Events
Manual Events
Create events directly:
- Go to calendar editor
- Click Events tab
- Click Open Event Editor
Instagram Integration
Automatically import events from Instagram posts:
- Configure Instagram secrets (see Configuration)
- Go to Events tab
- Click Connect Instagram Account
- Authorize access
- Events are synced hourly via cron trigger
Auto-Responders
Send automatic messages when forms are submitted or bookings are made.
Prerequisites
Configure at least one notification channel. See Configuration.
Note: The Responders tab only appears when channels are configured.
Form Responders
Send acknowledgement messages to form submitters.
Setup
- Open form editor
- Go to Responders tab
- Click + Add Responder
Configuration
| Field | Description |
|---|---|
| Name | Internal name |
| Channel | SMS, WhatsApp, or Email |
| Target Field | Form field containing recipient (email/phone) |
| Subject | Email subject (email only) |
| Body | Message content |
| Enabled | Toggle on/off |
| Use AI | Generate response with AI |
Template Placeholders
Use {{field_id}} to insert field values:
Hi {{name}},
Thank you for contacting us. We received your message:
"{{message}}"
We'll get back to you at {{email}} soon.
Booking Responders
Customer Notifications
Send confirmation to customers when bookings are confirmed:
- Open booking link editor
- Find Customer Notifications section
- Click + Add Responder
Available placeholders:
| Placeholder | Description |
|---|---|
{{name}} | Customer name |
{{email}} | Customer email |
{{date}} | Booking date |
{{time}} | Booking time |
Example:
Hi {{name}},
Your booking for {{date}} at {{time}} has been confirmed.
Thank you!
Admin Notifications
Receive notifications when bookings need approval:
- Open booking link editor
- Disable Auto-Accept
- Find Admin Notifications section
- Click + Add Responder
Additional placeholder:
| Placeholder | Description |
|---|---|
{{approve_url}} | Link to approve booking |
Example:
New booking request:
Name: {{name}}
Email: {{email}}
Date: {{date}}
Time: {{time}}
Approve: {{approve_url}}
AI-Powered Responses
Enable Use AI to generate personalized responses:
- Toggle Use AI on
- Write a system prompt describing desired behavior
- The AI uses form data to generate contextual responses
Example prompt:
You are a helpful assistant for a photography studio.
Thank the customer for their inquiry and provide a brief,
friendly response based on their message.
Keep it under 3 sentences.
Channel-Specific Notes
SMS
- Keep messages under 160 characters for single-segment
- Longer messages are split and may cost more
- Must use approved message templates for first contact
- Can send free-form messages within 24-hour window
- Include unsubscribe info for marketing emails
- HTML formatting is supported
Digests
Receive periodic summaries of new form submissions and bookings.
Prerequisites
Configure at least one notification channel. See Configuration.
Note: The Digest tab only appears when channels are configured.
Form Digests
Get summaries of new form submissions.
Setup
- Open form editor
- Go to Digest tab
- Select frequency
- Add recipients
Configuration
| Field | Description |
|---|---|
| Frequency | None, Daily, or Weekly |
| Recipients | List of notification recipients |
Adding Recipients
- Click + Add Recipient
- Configure:
- Name - Recipient identifier
- Channel - SMS, WhatsApp, or Email
- Address - Phone number or email
- Enabled - Toggle on/off
Digest Content
Form digests include:
- Form name
- Number of new submissions
- For each submission:
- Timestamp
- All field values
Example email digest:
New submissions for form: Contact Form
You have 3 new response(s) since the last digest.
--- Response #1 (2024-03-15 10:30:00) ---
Name: John Doe
Email: john@example.com
Message: I'd like to learn more about your services.
--- Response #2 (2024-03-15 14:45:00) ---
Name: Jane Smith
Email: jane@example.com
Message: Do you offer weekend appointments?
...
Booking Digests
Get summaries of new bookings.
Setup
- Open calendar editor
- Go to Digest tab
- Select frequency
- Add recipients
Digest Content
Booking digests include:
- Calendar name
- Number of new bookings
- For each booking:
- Timestamp
- Customer name and contact
- Date and time
- Duration
- Status (Pending/Confirmed)
- Notes (if any)
Example:
New bookings for calendar: Appointments
You have 2 new booking(s) since the last digest.
--- Booking #1 (2024-03-15 09:00:00) ---
Name: John Doe
Email: john@example.com
Phone: +1234567890
Date: 2024-03-20
Time: 2:00 PM
Duration: 30 minutes
Status: Confirmed
--- Booking #2 (2024-03-15 11:30:00) ---
Name: Jane Smith
Email: jane@example.com
Date: 2024-03-21
Time: 10:00 AM
Duration: 60 minutes
Status: Pending
Notes: First-time consultation
Frequency
| Option | When Sent |
|---|---|
| None | Disabled |
| Daily | Every day (via cron) |
| Weekly | Every week (via cron) |
Digests are sent via Cloudflare’s cron trigger. The exact timing depends on your cron configuration in wrangler.toml:
[triggers]
crons = ["0 * * * *"] # Every hour
Multiple Recipients
Add multiple recipients to send the same digest to different people or channels:
- Send email to admin and SMS to on-call staff
- CC multiple team members
- Use different channels for redundancy
iframe Embedding
Embed forms, booking pages, and calendar views using iframes.
Basic Usage
Forms
<iframe
src="https://your-worker.workers.dev/f/contact"
style="border: none; width: 100%; min-height: 500px;">
</iframe>
Booking Forms
<iframe
src="https://your-worker.workers.dev/book/{calendar_id}/{slug}"
style="border: none; width: 100%; min-height: 600px;">
</iframe>
Calendar Views
<iframe
src="https://your-worker.workers.dev/view/{calendar_id}/{slug}"
style="border: none; width: 100%; min-height: 500px;">
</iframe>
Auto-Resizing
iframes don’t automatically resize to fit content. Options:
1. Fixed Height
Set a minimum height that accommodates your content:
<iframe src="..." style="min-height: 600px;"></iframe>
2. JavaScript Resize
Use a library like iframe-resizer:
<script src="https://cdn.jsdelivr.net/npm/iframe-resizer@4/js/iframeResizer.min.js"></script>
<iframe id="form" src="..."></iframe>
<script>iFrameResize({}, '#form')</script>
Query Parameters
Customize appearance via URL parameters:
Inline CSS
<iframe src="https://your-worker.workers.dev/f/contact?css=button{background:green}"></iframe>
External Stylesheet
<iframe src="https://your-worker.workers.dev/f/contact?css_url=https://example.com/form.css"></iframe>
Hide Title
<iframe src="https://your-worker.workers.dev/book/{id}/{slug}?hide_title=true"></iframe>
Calendar View Options
<!-- Start on specific date -->
<iframe src=".../view/{id}/{slug}?date=2024-03-15"></iframe>
<!-- Force specific view -->
<iframe src=".../view/{id}/{slug}?view=month"></iframe>
<!-- Booking: show more days -->
<iframe src=".../book/{id}/{slug}?days=14"></iframe>
See Query Parameters for full reference.
Cross-Origin Considerations
Allowed Origins
For security, configure allowed origins in the form/calendar settings:
- Open form or calendar editor
- Add your domain to Allowed Origins
- Leave empty to allow all (not recommended for production)
Example:
https://example.com
https://www.example.com
Cookie Issues
Third-party cookies may be blocked by browsers. This can affect:
- Session persistence
- CSRF protection
Solutions:
- Use HTMX embedding instead (same-origin)
- Host the worker on a subdomain of your site
- Use the form as a standalone page with redirect
Styling Tips
Remove iframe Border
<iframe src="..." style="border: none;"></iframe>
Responsive Width
<iframe src="..." style="width: 100%; max-width: 600px;"></iframe>
Transparent Background
The form/calendar will use its configured background color. To make it transparent:
<iframe src="...?css=body{background:transparent}"></iframe>
Then ensure your page has a background color.
Example: Complete Form Embed
<!DOCTYPE html>
<html>
<head>
<style>
.form-container {
max-width: 600px;
margin: 2rem auto;
}
.form-container iframe {
border: none;
width: 100%;
min-height: 500px;
}
</style>
</head>
<body>
<div class="form-container">
<iframe src="https://your-worker.workers.dev/f/contact?css=body{background:transparent}"></iframe>
</div>
</body>
</html>
HTMX Embedding
Embed forms and calendars directly into your page using HTMX for a seamless experience.
Why HTMX?
Compared to iframes:
- No iframe sizing issues - Content is part of your page
- Better styling control - Your CSS applies directly
- Smoother interactions - No iframe refresh on submit
- SEO friendly - Content is in the DOM
Setup
1. Include HTMX
Add HTMX to your page:
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
2. Configure Allowed Origins
Add your domain to the form/calendar’s Allowed Origins setting:
https://example.com
https://www.example.com
Forms
Basic Embed
<div hx-get="https://your-worker.workers.dev/f/contact"
hx-trigger="load"
hx-swap="innerHTML">
Loading form...
</div>
With Loading Indicator
<div hx-get="https://your-worker.workers.dev/f/contact"
hx-trigger="load"
hx-swap="innerHTML"
hx-indicator="#form-loading">
<div id="form-loading" class="htmx-indicator">
Loading...
</div>
</div>
Calendar Views
<div hx-get="https://your-worker.workers.dev/view/{calendar_id}/{slug}"
hx-trigger="load"
hx-swap="innerHTML">
Loading calendar...
</div>
With Date Parameter
<div hx-get="https://your-worker.workers.dev/view/{calendar_id}/{slug}?date=2024-03-15"
hx-trigger="load"
hx-swap="innerHTML">
</div>
Booking Forms
<div hx-get="https://your-worker.workers.dev/book/{calendar_id}/{slug}"
hx-trigger="load"
hx-swap="innerHTML">
Loading booking form...
</div>
Styling
Since HTMX content is part of your page, you have full CSS control.
Override CSS Variables
<style>
/* Form variables */
:root {
--cf-primary-color: #007bff;
--cf-border-radius: 8px;
}
/* Calendar variables */
:root {
--cal-primary: #28a745;
--cal-bg: transparent;
}
</style>
Target Form Elements
<style>
.contact-form {
max-width: 500px;
margin: 0 auto;
}
.contact-form button {
text-transform: uppercase;
}
.form-group label {
font-weight: bold;
}
</style>
Hide Title via CSS
<style>
.contact-form h1 {
display: none;
}
</style>
Query Parameters
Pass parameters to customize the embedded content:
<!-- Hide title -->
<div hx-get=".../f/contact?hide_title=true" ...></div>
<!-- Custom view -->
<div hx-get=".../view/{id}/{slug}?view=month" ...></div>
<!-- Inline CSS override -->
<div hx-get=".../f/contact?css=button{background:green}" ...></div>
Form Submission
Forms use HTMX’s hx-post for submission:
- User fills form
- Submit button triggers HTMX POST
- Success message replaces form
- After 3 seconds, form reloads automatically
Custom Success Handling
Override the default behavior:
<div hx-get="https://your-worker.workers.dev/f/contact"
hx-trigger="load"
hx-swap="innerHTML"
hx-on::after-settle="handleFormLoad(event)">
</div>
<script>
function handleFormLoad(event) {
// Add custom event listeners to the loaded form
const form = event.target.querySelector('form');
if (form) {
form.addEventListener('htmx:afterRequest', function(e) {
if (e.detail.successful) {
// Custom success handling
console.log('Form submitted successfully');
}
});
}
}
</script>
Error Handling
Handle network errors gracefully:
<div hx-get="https://your-worker.workers.dev/f/contact"
hx-trigger="load"
hx-swap="innerHTML"
hx-on::response-error="this.innerHTML = 'Failed to load form'">
Loading...
</div>
Complete Example
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
.form-container {
max-width: 600px;
margin: 2rem auto;
padding: 1rem;
}
/* Override form styles */
:root {
--cf-primary-color: #6366f1;
--cf-border-radius: 12px;
--cf-bg-color: transparent;
}
.contact-form {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator {
display: block;
}
</style>
</head>
<body>
<div class="form-container">
<div hx-get="https://your-worker.workers.dev/f/contact"
hx-trigger="load"
hx-swap="innerHTML"
hx-indicator=".loading">
<div class="loading htmx-indicator">Loading form...</div>
</div>
</div>
</body>
</html>
CSS Customization
Customize the appearance of forms, calendars, and booking pages.
Customization Methods
1. Admin Settings
Edit CSS variables and custom CSS in the admin UI:
- Open form or calendar editor
- Go to Styling tab
- Modify CSS variables or add custom CSS
2. Query Parameters
Override styles via URL:
<!-- Inline CSS -->
<iframe src=".../f/contact?css=button{background:green}"></iframe>
<!-- External stylesheet -->
<iframe src=".../f/contact?css_url=https://example.com/style.css"></iframe>
3. Page CSS (HTMX only)
When using HTMX embedding, your page CSS applies directly.
CSS Variables
Form Variables
| Variable | Description | Default |
|---|---|---|
--cf-font-family | Font family | inherit |
--cf-font-size | Base font size | 1rem |
--cf-text-color | Text color | #333333 |
--cf-bg-color | Page background | transparent |
--cf-form-bg | Form background | #ffffff |
--cf-border-color | Border color | #dddddd |
--cf-border-radius | Border radius | 4px |
--cf-primary-color | Button color | #0070f3 |
--cf-primary-hover | Button hover | #0060df |
--cf-input-padding | Input padding | 0.5rem |
Calendar Variables
| Variable | Description | Default |
|---|---|---|
--cal-primary | Accent color | #0070f3 |
--cal-text | Text color | #333333 |
--cal-bg | Background | #ffffff |
--cal-border-radius | Border radius | 4px |
--cal-font | Font family | system-ui |
See CSS Variables Reference for the complete auto-generated list.
CSS Classes
Form Classes
| Class | Element |
|---|---|
.contact-form | Form container |
.form-group | Field wrapper |
label | Field labels |
input, textarea | Input fields |
button | Submit button |
.success | Success message |
.error | Error message |
Calendar Classes
| Class | Element |
|---|---|
.calendar-view | Main container |
.calendar-header | Navigation header |
.calendar-grid | Day grid |
.event | Event item |
.booking-form | Booking form |
.time-slots | Time slot container |
.time-slot | Individual slot button |
Examples
Dark Theme Form
:root {
--cf-bg-color: #1a1a1a;
--cf-form-bg: #2d2d2d;
--cf-text-color: #e0e0e0;
--cf-border-color: #444444;
--cf-primary-color: #3b9eff;
}
Rounded Modern Style
:root {
--cf-border-radius: 12px;
--cf-input-padding: 0.75rem;
}
.contact-form {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
button {
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
Minimal Calendar
:root {
--cal-primary: #000000;
--cal-bg: transparent;
--cal-border-radius: 0;
}
.event {
border-left: 3px solid var(--cal-primary);
background: transparent;
}
Brand Colors
/* Use your brand colors */
:root {
--cf-primary-color: #6366f1; /* Indigo */
--cf-primary-hover: #4f46e5;
--cal-primary: #6366f1;
}
Using External Stylesheets
Host a CSS file and reference it:
<iframe src=".../f/contact?css_url=https://example.com/brand.css"></iframe>
Your external CSS can override any styles:
/* brand.css */
:root {
--cf-primary-color: #your-color;
}
.contact-form {
font-family: 'Your Font', sans-serif;
}
Responsive Design
Forms and calendars are responsive by default. Override breakpoints:
@media (max-width: 600px) {
.contact-form {
padding: 1rem;
}
.time-slots {
grid-template-columns: repeat(2, 1fr);
}
}
Hiding Elements
Hide Title
Via query param:
?hide_title=true
Via CSS:
.contact-form h1,
.booking-form h1 {
display: none;
}
Hide Specific Fields
.form-group:has(input[name="phone"]) {
display: none;
}
Print Styles
Add print-specific styles:
@media print {
.calendar-header button {
display: none;
}
.event {
break-inside: avoid;
}
}
CSS Variables Reference
This page is auto-generated from the source code.
Form CSS Variables
Use these variables to customize form appearance:
| Variable | Description |
|---|---|
--cf-bg-color | Page background color |
--cf-border-color | Input border color |
--cf-border-radius | Border radius |
--cf-font-family | Font family |
--cf-font-size | Base font size |
--cf-form-bg | Form container background |
--cf-input-padding | Input padding |
--cf-primary-color | Primary/button color |
--cf-primary-hover | Primary hover color |
--cf-text-color | Main text color |
Calendar CSS Variables
Use these variables to customize calendar and booking views:
| Variable | Description |
|---|---|
--cal-bg | Background color |
--cal-border-radius | Border radius |
--cal-font | Font family |
--cal-primary | Primary/accent color |
--cal-text | Text color |
Usage Examples
Override via Query Parameter
<iframe src=".../f/contact?css=--cf-primary-color:green"></iframe>
Override in Page CSS (HTMX)
:root {
--cf-primary-color: #6366f1;
--cal-primary: #6366f1;
}
Override in Admin Settings
- Open form/calendar editor
- Go to Styling tab
- Add Custom CSS:
:root {
--cf-primary-color: #your-color;
}
Query Parameters Reference
This page is auto-generated from the source code.
Form Embedding
| Parameter | Description | Example |
|---|---|---|
css | Inline CSS to apply | ?css=button{background:green} |
css_url | URL to external stylesheet | ?css_url=https://example.com/style.css |
Calendar & Booking Views
| Parameter | Description | Example |
|---|---|---|
css | Inline CSS to apply | ?css=.event{color:blue} |
css_url | URL to external stylesheet | ?css_url=https://example.com/cal.css |
date | Starting date for view | ?date=2024-03-15 |
view | View type (week/month/year/endless) | ?view=month |
days | Number of days to show (booking) | ?days=14 |
hide_title | Hide the title | ?hide_title=true |
iCal Feeds
| Parameter | Description | Example |
|---|---|---|
token | Authentication token | ?token=abc123 |
Combining Parameters
Multiple parameters can be combined:
/f/contact?css=button{background:green}&hide_title=true
/view/{id}/{slug}?date=2024-03-15&view=month&css_url=https://example.com/cal.css
Channel Configuration Reference
This page is auto-generated from the source code.
Available Channels
The following notification channels are supported:
| Channel | Provider | Use Case |
|---|---|---|
twilio_sms | Twilio | SMS to mobile numbers |
twilio_whatsapp | Twilio | WhatsApp via Twilio |
meta_whatsapp | Meta | WhatsApp Business API |
twilio_email | SendGrid via Twilio | Email notifications |
resend_email | Resend | Email notifications |
Required Secrets
Twilio SMS
wrangler secret put TWILIO_SID
wrangler secret put TWILIO_TOKEN
wrangler secret put TWILIO_FROM_SMS # e.g., +15551234567
Twilio WhatsApp
wrangler secret put TWILIO_SID
wrangler secret put TWILIO_TOKEN
wrangler secret put TWILIO_FROM_WHATSAPP # e.g., whatsapp:+15551234567
WhatsApp Business API (Meta)
wrangler secret put WHATSAPP_ACCESS_TOKEN
wrangler secret put WHATSAPP_PHONE_NUMBER_ID
Twilio Email (via SendGrid)
wrangler secret put SENDGRID_API_KEY
wrangler secret put TWILIO_FROM_EMAIL # e.g., noreply@yourdomain.com
Resend Email
wrangler secret put RESEND_API_KEY
wrangler secret put RESEND_FROM # e.g., noreply@yourdomain.com
Channel Detection
The admin UI shows Responders and Digest tabs only when channels are available:
| Channel | Detection |
|---|---|
| Twilio SMS | TWILIO_SID and TWILIO_FROM_SMS are set |
| Twilio WhatsApp | TWILIO_SID and TWILIO_FROM_WHATSAPP are set |
| Twilio Email | SENDGRID_API_KEY and TWILIO_FROM_EMAIL are set |
| Resend Email | RESEND_API_KEY and RESEND_FROM are set |
Note: If you don’t see the Responders or Digest tabs in the admin UI, ensure at least one channel’s secrets are configured.
URL Routes Reference
This page is auto-generated from the source code.
Public Routes
| Route | Method | Description |
|---|---|---|
/f/{slug} | GET | Render public form |
/f/{slug}/submit | POST | Submit form |
/book/{calendar_id}/{slug} | GET | Render booking form |
/book/{calendar_id}/{slug}/submit | POST | Submit booking |
/book/{calendar_id}/{slug}/approve/{booking_id} | GET | Approve pending booking |
/book/{calendar_id}/{slug}/cancel/{booking_id} | GET | Cancel booking |
/view/{calendar_id}/{slug} | GET | Render calendar view |
/feed/{calendar_id}/{slug} | GET | iCal feed |
Admin Routes
| Route | Method | Description |
|---|---|---|
/admin | GET | Admin dashboard |
/admin/forms/new | GET | New form editor |
/admin/forms/{slug} | GET | Edit form |
/admin/forms/{slug} | PUT | Update form |
/admin/forms/{slug} | DELETE | Delete form |
/admin/forms/{slug}/responses | GET | View form responses |
/admin/forms/{slug}/archive | POST | Archive form |
/admin/forms/{slug}/unarchive | POST | Unarchive form |
/admin/calendars | POST | Create calendar |
/admin/calendars/{id} | GET | Edit calendar |
/admin/calendars/{id} | PUT | Update calendar |
/admin/calendars/{id} | DELETE | Delete calendar |
/admin/calendars/{id}/archive | POST | Archive calendar |
/admin/calendars/{id}/unarchive | POST | Unarchive calendar |
/admin/calendars/{id}/bookings | GET | View all bookings |
/admin/calendars/{id}/slots | GET | Configure time slots |
/admin/calendars/{id}/events | GET | Manage events |
/admin/calendars/{id}/booking | POST | Create booking link |
/admin/calendars/{id}/booking/{link_id} | GET | Edit booking link |
/admin/calendars/{id}/view | POST | Create view link |
/admin/calendars/{id}/view/{link_id} | GET | Edit view link |
/admin/calendars/{id}/feed | POST | Create feed link |
Instagram Routes
| Route | Method | Description |
|---|---|---|
/instagram/auth/{calendar_id} | GET | Start Instagram OAuth |
/instagram/callback | GET | OAuth callback |
/instagram/disconnect/{calendar_id}/{source_id} | DELETE | Disconnect account |
Static Assets
| Route | Description |
|---|---|
/logo.svg | Application logo |