Skip to content

kandekore/Simple-calendly-for-WP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Calendly Service Scheduler

A WordPress plugin that syncs Calendly event types as WordPress service posts and provides an embeddable booking widget via shortcode. Visitors can browse available time slots and book appointments directly on your site — no redirect to Calendly.


Requirements

Requirement Minimum
WordPress 6.0
PHP 8.0
Calendly plan Any (booking via POST /invitees requires a paid plan)
Calendly API access Personal Access Token (PAT)

Installation

  1. Upload the calendly-service-scheduler folder to /wp-content/plugins/.
  2. Activate the plugin from Plugins > Installed Plugins.
  3. Go to Calendly Scheduler > Settings and enter your Calendly Personal Access Token.
  4. Go to Calendly Scheduler > Sync and click Sync Now to import your event types as service posts.
  5. Embed the booking widget on any page or post using the shortcode (see below).

Configuration

Authentication

Navigate to Calendly Scheduler > Settings.

Option Description
Personal Access Token Your Calendly PAT. Generate one at https://calendly.com/integrations/api_webhooks.
Sync Frequency How often the background sync runs (daily, twicedaily, hourly).
Booking Mode inline_iframe — renders the widget inline on the page.

Syncing Event Types

Go to Calendly Scheduler > Sync and click Sync Now. The plugin will:

  • Create a services custom post type entry for each active Calendly event type.
  • Update existing service posts when event type details change.
  • Deactivate (not delete) service posts for event types that are no longer active on Calendly.

A background cron job also runs the sync automatically at the configured frequency.


Shortcode Usage

[calendly_booking service="your-service-slug"]

Or by post ID:

[calendly_booking id="42"]

The shortcode for each service is shown pre-filled in the Calendly Event Type Details metabox on the service's edit screen. Click the field to select it, then copy and paste it into any page or post.

What the Widget Does

  1. Displays the service name and duration.
  2. Presents a date picker — the user must select a date before any API call is made.
  3. Fetches available time slots from Calendly for the chosen date.
  4. Allows the user to select a slot and enter their name and email.
  5. Books the appointment directly via the Calendly API and displays an on-page confirmation with cancel and reschedule links.

REST API

The plugin registers a custom REST namespace: calendly-scheduler/v1.

GET /wp-json/calendly-scheduler/v1/services/{id}/slots

Returns available time slots for a service on a given date.

Query parameters

Parameter Required Format Description
date Yes YYYY-MM-DD The date to fetch slots for.

Response

{
  "service_id": 42,
  "date": "2026-03-20",
  "slots": [
    { "start_time": "2026-03-20T09:00:00.000000Z", "status": "available", "invitees_remaining": 1 }
  ]
}

Slot-fetch rules

  • Past dates return an empty slots array immediately without calling the Calendly API.
  • For today's date, start_time sent to Calendly is the current UTC time plus a 90-second buffer, preventing rejections due to clock skew or network latency.
  • If the buffered time for today is already past 23:59:59Z, an empty array is returned.
  • Results for today are cached per UTC hour (refreshed every hour as slots fill up). Results for future dates are cached for 5 minutes using a plain date-based key.

POST /wp-json/calendly-scheduler/v1/services/{id}/book

Creates a booking (invitee) in Calendly. Requires a valid WordPress REST nonce in the X-WP-Nonce header.

Request body (JSON)

Field Required Description
start_time Yes ISO 8601 UTC start time from the slots response.
booker_name Yes Invitee full name.
booker_email Yes Invitee email address.
timezone Yes IANA timezone string (e.g. Europe/London). Auto-detected by the widget.

Response

{
  "status": "confirmed",
  "name": "Jane Smith",
  "email": "jane@example.com",
  "start_time": "2026-03-20T09:00:00.000000Z",
  "cancel_url": "https://calendly.com/cancellations/...",
  "reschedule_url": "https://calendly.com/reschedulings/..."
}

POST /wp-json/calendly-scheduler/v1/admin/sync

Triggers an on-demand sync. Requires manage_options capability.


Database Tables

Created on plugin activation via dbDelta.

{prefix}css_booking_log

Records every completed booking.

Column Type Description
id BIGINT UNSIGNED Primary key.
service_post_id BIGINT UNSIGNED WordPress post ID of the service.
event_type_uri VARCHAR(500) Calendly event type URI.
scheduling_url VARCHAR(1000) Calendly invitee URI returned after booking.
booker_email VARCHAR(200) Invitee email.
booker_name VARCHAR(200) Invitee name.
status ENUM initiated, completed, cancelled, error.
created_at DATETIME Row creation timestamp.
updated_at DATETIME Row last-updated timestamp.

{prefix}css_sync_log

Records the result of each sync run.

Column Type Description
id BIGINT UNSIGNED Primary key.
started_at DATETIME Sync start time.
completed_at DATETIME Sync end time (NULL if still running).
status ENUM running, success, error.
event_types_found INT Number of event types returned by Calendly.
services_created INT New service posts created.
services_updated INT Existing service posts updated.
services_deactivated INT Service posts deactivated (event type no longer active).
error_message TEXT Error detail, if any.

File Structure

calendly-service-scheduler/
├── calendly-service-scheduler.php   # Plugin entry point, constants, activation hooks
├── uninstall.php                    # Cleanup on uninstall
├── includes/
│   ├── api/
│   │   ├── class-calendly-auth.php          # PAT auth helper
│   │   ├── class-calendly-client.php        # Low-level HTTP wrapper (wp_remote_*)
│   │   └── class-calendly-endpoints.php     # High-level Calendly API methods
│   ├── booking/
│   │   ├── class-booking-manager.php        # Slot retrieval, caching, booking creation
│   │   └── class-scheduling-link.php        # Single-use scheduling link helper
│   ├── cpt/
│   │   ├── class-service-cpt.php            # Registers the 'services' custom post type
│   │   └── class-service-meta.php           # Metabox for event type meta fields
│   ├── rest/
│   │   └── class-rest-controller.php        # REST route registration and handlers
│   ├── shortcode/
│   │   └── class-booking-shortcode.php      # [calendly_booking] shortcode handler
│   ├── sync/
│   │   └── class-event-type-sync.php        # Syncs Calendly event types to WP posts
│   ├── admin/
│   │   ├── class-admin-menu.php             # Admin menu registration
│   │   ├── class-settings-page.php          # Settings page and AJAX save handler
│   │   └── class-sync-page.php              # Manual sync page and AJAX trigger
│   ├── class-plugin.php                     # Core bootstrap / service locator (singleton)
│   ├── class-activator.php                  # Activation: tables, defaults, cron
│   └── class-deactivator.php               # Deactivation: cron cleanup
├── admin/
│   ├── css/admin.css
│   ├── js/admin.js
│   └── views/
│       ├── settings-page.php
│       ├── sync-page.php
│       └── metabox-service-details.php
├── public/
│   ├── css/public.css
│   ├── js/booking.js                        # Vanilla JS: slot loading, booking flow
│   └── views/
│       └── booking-widget.php               # Booking widget HTML template
└── templates/
    └── single-services.php                  # Single service post template

Security

  • All REST input is validated and sanitized using WordPress core callbacks (sanitize_text_field, sanitize_email, absint).
  • The booking endpoint is protected by a WordPress REST nonce (X-WP-Nonce), preventing CSRF from unauthenticated third parties.
  • The admin sync endpoint requires manage_options capability.
  • The ABSPATH guard (defined( 'ABSPATH' ) || exit) is present in every PHP file.

Frequently Asked Questions

Does this require a paid Calendly plan? Fetching available times works on any plan. Creating bookings directly via POST /invitees requires a paid Calendly plan. On free plans you can still link users to the Calendly scheduling page using the scheduling URL stored on the service post.

Why does the date picker have no default value? By design — the widget waits for the user to explicitly choose a date before calling the API. This avoids unnecessary API requests on page load and prevents edge-case errors when today's slots are already exhausted.

Slots show as unavailable for today — why? Calendly rejects any start_time in the past. The plugin automatically uses the current UTC time plus a 90-second buffer as start_time when today is selected. If all remaining time today falls within that buffer window, an empty slot list is returned.

How do I clear the slot cache? Today's slots are cached per UTC hour and expire automatically. Future date slots are cached for 5 minutes. You can also delete all transients prefixed css_slots_ from the database if you need to force a refresh.


Changelog

1.0.2 — 2026-03-17

Improvements

  • Shortcode pre-filled in metabox. The Calendly Event Type Details metabox on each service edit screen now shows a read-only shortcode field ([calendly_booking service="slug"]) populated from the post slug. Click the field to select it all for easy copy/paste — no need to construct the shortcode manually.
  • Post content preserved on sync updates. Subsequent syncs no longer overwrite the post_content (description) of existing service posts. The Calendly description is written only when a service post is first created; after that, any edits made directly in WordPress are retained across syncs. post_title and post_status continue to reflect the Calendly event type on every sync.

1.0.1 — 2026-03-17

Bug fixes

  • Date picker no longer defaults to today. The <input type="date"> is rendered without a value attribute. On load the slot area shows "Choose a date to see available times." and no API call is made until the user picks a date.
  • Removed auto-load on page init in JS. The JavaScript initWidget() function no longer calls loadSlots() on startup; slot fetching is triggered exclusively by the change event on the date input.
  • Fixed slot fetch failure for today's date. CSS_Booking_Manager::get_slots() now uses the current UTC time plus a 90-second buffer as start_time when the requested date is today, preventing Calendly from rejecting the request due to a past start_time.
  • Switched min attribute to gmdate(). The date input's min attribute now uses gmdate( 'Y-m-d' ) (UTC) instead of date() (server local time) for consistent behaviour across timezones.

New guards in CSS_Booking_Manager::get_slots()

  • Returns an empty array immediately for any past date, without calling the Calendly API.
  • Returns an empty array if today's buffered start_time is already at or past 23:59:59Z (no meaningful booking window left).
  • Uses an hourly transient cache key for today (css_slots_{uuid}_{date}_h{HH}) so cached results refresh each hour as slots fill up. Future dates continue to use the plain date-based key with a 5-minute TTL.

1.0.0 — Initial release

  • Registers a services custom post type for Calendly event types.
  • Syncs event types from Calendly via the admin sync page and a scheduled cron job.
  • Provides the [calendly_booking] shortcode to embed a booking widget.
  • Booking widget fetches available slots via the plugin's REST API and books directly via Calendly's POST /invitees endpoint.
  • On-page booking confirmation with cancel and reschedule links.
  • Automatic timezone detection in the browser via Intl.DateTimeFormat.
  • Booking log (css_booking_log) and sync log (css_sync_log) database tables.
  • Settings page for PAT configuration, sync frequency, and booking mode.
  • GPL v2 license.

About

A WordPress plugin that syncs Calendly event types as WordPress service posts and provides an embeddable booking widget via shortcode. Visitors can browse available time slots and book appointments directly on your site — no redirect to Calendly.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors