WP Manifestindependent plugin directory
manifest / events / sc-room-booking

SC Room Bookings

Wordpress plugin for handling room bookings.

by Neil Sayers · github.com/neilsayers/sc-room-booking · website

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/neilsayers/sc-room-booking/archive/refs/heads/main.zip

A site-agnostic room/space booking manager. Guides you through naming your own bookable post types before anything is registered.

Copyright (C) 2026 Screen Candy (screencandy.co.uk). Licensed under the GNU General Public License v2 or later — see the bundled LICENSE file, or https://www.gnu.org/licenses/gpl-2.0.html, for the full terms.

Description

SC Room Bookings is built to be dropped into any WordPress site as-is — no build step, no Composer install. Rather than hard-coding a single "Room" post type, it's managed from one "Room Bookings" settings screen where you create your own room types with their own singular/plural labels (e.g. "Room" / "Rooms", "Court" / "Courts", "Desk" / "Desks") — up to 4 by default. That cap is filterable, e.g.:

add_filter('scrb_max_room_types', fn () => 8);

For each type, the underlying post type key and URL slug are locked in the moment it's created — renaming its labels afterwards never orphans existing rooms or breaks their links. Types can be deleted from the screen too, but only after typing the type's plural label to confirm — doing so moves every one of its rooms to Trash (a normal 30-day recovery window, nothing is hard-deleted) and cancels any bookings made against them (bookings themselves are never deleted, only marked Cancelled — see "Data model" below).

Each room gets a hand-written "Room Details" box: capacity, what it's suitable for, accessibility notes, one or more alternate layout options (each with its own capacity and photo — e.g. a hall that's 300 cleared, 220 theatre-style, 150 cabaret), one or more price options (a default price plus, optionally, further tiers like "Off-peak non-commercial"/"Peak commercial"), which days of the week it's available, its daily opening/closing time, a minimum booking length, and a buffer required between bookings. Facilities are a proper taxonomy (Taxonomies\FacilityTaxonomy) rather than free text per room — manage the shared list once from Room Bookings -> Facilities (WordPress's own term-manager screen, nothing custom-built), then just tick which apply on each room.

A booking always has a room, a start/end date-time, and a requester's name/email (phone and a free-text message are optional). Availability checking (Booking\AvailabilityChecker) weighs up the room's own opening days/hours, its minimum booking length, the site's minimum notice period, and — critically — every pending booking against that room as well as confirmed ones, so two people racing to request the same slot both see it as taken rather than the first approval silently bumping the second request.

Whether a booking needs an admin to confirm it before it's binding (the default) or is auto-confirmed on submission is a site-wide setting. Either way, new requests and status changes fire plain wp_mail() notifications (Notifications\BookingNotifications) — no queue, no third-party mail service, and easy to replace or extend by hooking scrb_booking_created / scrb_booking_status_changed directly.

Every room and every availability/booking action is available three ways — plain PHP functions (scrb_check_availability(), scrb_request_booking()), a public/versioned REST API (scrb/v1), or by reading the source directly — see Room Bookings -> Documentation in wp-admin for the full reference, including every hook.

Out of the box — no theme customisation needed — every room type gets its own listing page ([sc_rooms], or its own /{slug}/ archive URL) and single-room page ([sc_room id="123"], or the room's own permalink), so a site that's never touched a template file still has something worth looking at the moment it activates this plugin. A theme that wants its own look just adds a single-{post_type}.php/archive-{post_type}.php (or, on a Sage/Acorn theme, its own Blade partial) and this plugin's own version steps aside automatically — see Frontend\DefaultTemplates.

Dropping either shortcode into a specific page doesn't need typing it by hand either: a "SC Room Bookings" button in the classic editor's toolbar opens a small dialog ("show all rooms" or "show one room" from a dropdown) and inserts the right shortcode at the caret, and the block editor gets the same two options as proper Gutenberg blocks (Room Listing, Room Detail) with a live preview while editing.

How a visitor actually books is a site-wide choice (Room Bookings -> Basic settings -> Booking): no online booking at all (the default — a plain room directory, nothing more), a full request-a-time widget (Frontend\BookingWidget/scrb_render_booking_widget()) — a week-view calendar (FullCalendar, vendored, nothing else to install) where a visitor taps a start time then an end time, fills in their name and email, and submits straight through scrb_request_booking() above — or a link out to a third-party booking site instead (a booking_url field appears on each room's edit screen once this option's picked, and every "Book now" opens it in a new tab). Switching between them never touches anything already saved.

Some sites only want the room listing — not bookings at all. Ticking "Use SC Room Bookings in simple mode" on the main Room Bookings screen trims each room's edit screen down to just Capacity and Suitable for, and hides the Bookings, Documentation and Facilities menu items. Everything else (accessibility, layout options, pricing, availability, facilities, the booking system itself) stays exactly as it was, untouched, ready to reappear the moment the box is unticked again.

Data model

Rooms are regular posts (one post type per configured room type). Bookings are their own fixed scrb_booking post type — never renamed, never shown on wp-admin's native post-list/edit screens (manage them from Room Bookings -> Bookings instead), and never trashed by a room-type deletion, only marked Cancelled — a booking is a historical record of a request once it's been made, not disposable content.

Changelog

0.9.6

  • Added a proper GPLv2-or-later copyright notice (plugin header, readme, and a bundled LICENSE file) — this plugin isn't distributed on WordPress.org, but it's still a derivative work of WordPress's own GPL-licensed core (it runs entirely through WP's hooks/APIs), so the code itself has to stay GPL-compatible however it's sold.
  • Added self-hosted update checking (Setup\UpdateChecker, using the vendored, build-step-free plugin-update-checker library) pointed at this plugin's own GitHub repo, so installs get the normal wp-admin "Update available" notice without needing to be listed on WordPress.org. Releasing an update is just tagging the repo vX.Y.Z and pushing — no separate zip to build, matching this plugin's existing no-build-step approach.

0.9.5

  • Renamed the "Room Types" admin menu item to "Basic settings" (Documentation's own references to it updated to match) and made the Simple mode/Booking mode forms' submit buttons say "Save changes" instead of a bare "Save", matching the room-type relabelling form's button just below them on the same screen.

0.9.4

  • Restructured Room Bookings -> Documentation into two clear parts: a friendlier, less technical "Getting started" covering everyday set-up (creating a room type, adding rooms/facilities, reordering, the three booking modes, Simple mode, and using shortcodes/blocks) for non-technical site owners, followed by a "Technical detail" section (template overrides, PHP functions, REST API, hooks) for developers. Also removed every Sage/Acorn-specific reference from the documentation text — the plugin's own template-override behaviour already works the same on any theme, so the docs shouldn't call out one theme framework by name.

0.9.3

  • Fixed both Gutenberg blocks still rendering full width inside the block editor itself (0.9.2 only fixed the front end and the ServerSideRender preview's fetched HTML — the editor's own BlockListBlock wrapper element is built client-side and needed its own fix). Two gaps, both now closed: neither block's edit() function called wp.blockEditor.useBlockProps(), so the editor never recognised either block's root element as a proper block wrapper ([data-type="sc-room-bookings/room-listing"] matched nothing at all); and separately, a theme needs to declare add_theme_support('editor-styles') before WordPress adds .editor-styles-wrapper to the editor's DOM in the first place — without it, Gutenberg's own default .editor-styles-wrapper .wp-block { max-width: 840px } rule never applies to any block there, confirmed by measuring that even a plain core/paragraph block was full width. The theme-support half is a testbed theme fix, not a plugin one, but is called out here since it's a prerequisite for this plugin's blocks to size correctly in any theme's editor.

0.9.2

  • Fixed both Gutenberg blocks rendering full width instead of constrained to the theme's own content column like every other block. A dynamic block's save() returns null (there's nothing in post_content to give it the usual wp-block-* wrapper a static block's own saved markup would carry), so render.php is responsible for adding that wrapper itself via get_block_wrapper_attributes() — which neither block's render.php was doing, so there was no wp-block-* class anywhere on either block's output for a theme's own width-constraining CSS to target. Verified with real measurements: a plain paragraph, the block's wrapper, and the block's own content all now measure identically (960px on the testbed's theme) on both the published page and the editor's own preview.

0.9.1

  • Fixed the Gutenberg blocks' editor preview showing completely unstyled HTML — no card borders, plain blue links instead of styled titles, no pill badges — nothing like the real front end. assets/css/frontend.css was only ever enqueued on wp_enqueue_scripts, which never fires in the block editor's own admin context, so ServerSideRender's live preview had no stylesheet to render against at all. Fixed by registering (not just enqueueing) the style on init, early enough to exist in both contexts, and having both blocks' block.json reference that same handle as their own style — the mechanism WordPress provides specifically for a block's styles to load in the editor and the front end alike. A block's preview now matches the real front end exactly, same as it already did after publishing (that part was always correct — this was an editor-only gap).

0.9.0

  • Added two Gutenberg blocks — "Room Listing" and "Room Detail" — the block-editor equivalent of [sc_rooms]/ [sc_room]. Both are dynamic (server-rendered) blocks whose render.php calls straight into RoomListingShortcode::render()/RoomDetailShortcode::render(), the exact same rendering the shortcodes themselves use, so a block and its shortcode twin can never drift apart. Room Detail's Inspector panel has a dropdown of every configured room (same list Admin\ShortcodeButton's TinyMCE modal uses); both blocks preview live in the editor via ServerSideRender, so what you see while editing matches the front end exactly. No build step: block.json's editorScript points at a plain ES5 file registered with explicit wp-* script dependencies, not a webpack-generated one, same "drop it in, nothing to compile" philosophy as the rest of this plugin (Blocks\Blocks).
  • Added RoomDetailShortcode::render() (a static method alongside the existing renderShortcode()), matching RoomListingShortcode's own shape — needed by the Room Detail block, and now also directly callable from a theme template the same way RoomListingShortcode::render() already was.

0.8.1

  • The request-a-time widget's calendar no longer hides once both a start and end time are picked — it stays visible (the shaded range on it is the confirmation), and the contact form appears underneath it instead of replacing it, with a 1-second ease-in scroll bringing the form into view. Was a two-step "pick times, then the calendar's replaced by the form" flow; this was purely a design choice, not a technical constraint, and is a better match for how a visitor actually wants to see what they've picked against everything else on the calendar (the previous design's own reasoning, that showing one thing at a time keeps a small phone screen simple, turned out to matter less than being able to see the shaded range and the form together).
  • Fixed the dialog's title bar overlapping the calendar/form while scrolling — it was position: sticky, which looked messy once the widget's content could scroll further than one screen. It now scrolls away normally with everything else.

0.8.0

  • Added a "SC Room Bookings" classic-editor (TinyMCE) toolbar button: "Show all rooms" or "Show one room by ID" (a dropdown of every configured room), then "Insert Shortcode" drops [sc_rooms]/[sc_room id="X"] in at the caret — no more hand-typing either shortcode or looking up a room's post ID. Registered on the same mce_external_plugins/mce_buttons filters WordPress itself uses, so it also appears inside a "Classic" block's own mini toolbar in the block editor, not just a true classic-editor screen (Admin\ShortcodeButton).

0.7.5

  • Fixed the request-a-time widget doing nothing when [sc_room] was dropped onto an ordinary page rather than viewed on a room's own URL — its JS/CSS were only ever enqueued for is_singular($roomTypes), so a "View availability" button rendered by the shortcode anywhere else had no behaviour behind it. Now also enqueues whenever the current post's content contains the [sc_room] shortcode.

0.7.4

  • Documentation catch-up — the "Description" section above and Room Bookings -> Documentation in wp-admin had both fallen behind: neither mentioned the request-a-time booking widget at all, and the admin page was still only listing the original 3 REST routes/2 PHP functions from before shortcodes, DefaultTemplates, booking_url, or the widget existed. Both now cover all of it: the three booking modes, scrb_get_room(), the shortcodes, scrb_render_booking_widget(), and GET /rooms/{id}/schedule. No code changes.
  • Verified Frontend\DefaultTemplates end-to-end for the first time on a genuinely vanilla WordPress install (no Sage/Acorn, no theme template overrides) rather than only on a site where the Sage guard always steps aside — single-room.php, archive-room.php, [sc_rooms], and the booking widget all confirmed working correctly there too.

0.7.3

  • The dialog's "Book {Room}" title is now a real <h2> (was a styled <p>), with aria-labelledby wired up on the <dialog> itself — gives assistive tech a proper heading/landmark for the dialog's purpose. Purely markup; the CSS already fully controlled its look, so nothing changes visually.

0.7.2

  • Added a small "You have selected..." label above the chosen date/time in the request form, so it reads as an answer to a question rather than just a fact dropped on the page.

0.7.1

  • Fixed the request-a-time widget's dialog not being centred on screen — a Tailwind-based theme's global margin reset was silently breaking the <dialog> element's own default centring, restored with an explicit margin: auto.
  • Fixed a real booking-blocking bug: after "Change times" (or completing one selection), the calendar's day columns rendered collapsed on top of each other, and picking a second start/end time afterwards would silently fail — visible to users as "I can't seem to book more than 30 minutes". Root cause was the same in both cases: hiding the calendar's container element and showing it again leaves FullCalendar's own internal size measurement stale, since it has no way to know the container's visibility changed. Fixed by calling calendar.updateSize() immediately after un-hiding it.
  • The provisional "you tapped a start time" marker no longer intercepts clicks near it (rendered as a background event, like blocked ranges already were, instead of a normal foreground one) — a smaller contributing cause of the same "can't pick an end time near the start" symptom above.
  • Added a fade-in/out transition to the dialog and its backdrop (respects prefers-reduced-motion), and restyled "Change times" as a bordered secondary button with a back arrow, and the calendar's prev/next/today buttons with slightly more rounded corners — all pure CSS/JS polish, no behaviour change.

0.7.0

  • Added a third Booking option (Room Bookings -> Room Types): "Visitors can request a time and discuss by email". Every room gets a "View availability" button showing a week view (FullCalendar, vendored at assets/vendor/fullcalendar the same way SC Events Manager vendors Leaflet) — tap a start time, then an end time (deliberately not press-and-drag, which fights page scroll on a phone), fill in name/email (phone and a message are optional), submit. That request goes through scrb_request_booking() exactly as if it came through the REST API directly, so every existing piece — availability checking, the admin/pending/confirmed/declined workflow (Room Bookings -> Bookings), both email notifications — needed zero changes; this only adds the front-end widget that was missing (see scrb_request_booking()'s own long-standing "No public-facing booking form/calendar UI yet" note, now out of date).
  • Added GET /rooms/{id}/schedule (scrb/v1) and AvailabilityChecker::blockedRanges(): a room's opening days/hours/minimum booking length/buffer plus every existing booking's already-buffered blocked range in a date window — what the new calendar widget needs to shade a week view, as opposed to GET /availability's existing one-candidate-slot-at-a-time check.
  • Added Frontend\BookingWidget and the scrb_render_booking_widget(?int $postId = null) template tag — same "plugin draws a sensible default, theme can call the same building block directly" split as scrb_get_room()/RoomDetail.

0.6.0

  • Added a "Booking" section to the main Room Bookings settings screen: "No online booking link" (the previous, only behaviour) or "Send visitors to a third-party website to book". The second option adds a "Booking link" field to every room's edit screen (Room Bookings -> Room Types), and RoomDetail/[sc_room]/DefaultTemplates' bundled single-room.php all show a "Book now" button opening it in a new tab whenever a room has one set — a room without a link set just shows no "Book now" at all. Exposed as the new booking_url field via scrb_get_rooms()/scrb_get_room()/REST, so a theme can build its own "Book now" against it too (e.g. civic-centre-uckfield's own content-single-room.blade.php/room-accordion.blade.php). This is deliberately just a link-out, not a payment integration — see Settings::bookingMode()'s own docblock for why a future gateway (e.g. via SC Commerce) would be a new mode here, not a rename of this one.

0.5.0

  • Added front-end shortcodes: [sc_rooms] (a grid of every configured room type, narrow with type="room" or a comma list, limit with limit="6") and [sc_room id="123"] (one room's full detail — image, price table, facilities, accessibility). Both also callable directly from a theme template via Frontend\RoomListingShortcode::render()/RoomDetailShortcode.
  • Added scrb_get_room(?int $postId = null) alongside the existing scrb_get_rooms() — one room, shaped the same way, defaulting to the current post. Backed by the new Frontend\RoomListing::find().
  • Added Frontend\DefaultTemplates: a single-room/archive-room view served automatically the moment this plugin's activated on a site whose theme hasn't customised that room type's template yet, via the plain CSS in assets/css/frontend.css. Steps aside the instant a theme adds its own single-{post_type}.php/ archive-{post_type}.php, and never engages at all on a Sage/Acorn theme (which always owns its whole template hierarchy itself) — see that class's own docblock for the reasoning.
  • RoomListing::query()'s type argument (and so [sc_rooms]'s own type attribute) now accepts a comma-separated list of room type keys, not just one.

0.4.2

  • featured_image_url (scrb_get_rooms()/REST) now requests WordPress's 'large' image size instead of 'medium' — it's used by full-width slideshow/card displays in more than one theme template now, and 'medium' (300px max) was visibly soft stretched that wide.

0.4.1

  • Added a "Gallery images" field to the Room Details box — up to 10 extra photos an admin can attach to a room on top of its Featured Image, picked via the same media modal Layout options already uses (multi-select this time). Exposed as the new gallery_image_urls array via scrb_get_rooms()/REST, for a theme to build a slideshow or gallery from — doesn't include the Featured Image itself, which a consumer is expected to show first.

0.4.0

  • Capacity can now have one or more "layout options" alongside the plain Capacity figure — each with its own seating/configuration style (Clear, Theatre-style, Cabaret, Classroom, Boardroom, U-shape, Banquet, Standing reception — Support\LayoutTypes), its own capacity number, and an optional photo. A room that only ever needs one number can leave this alone entirely; scrb_get_rooms()/REST expose it as the new layout_variants array.
  • Price restructured again: what was a single amount+unit+"contact for pricing" is now up to 6 price options per room, each with its own optional label (e.g. "Off-peak non-commercial", "Peak commercial") — added and removed in the browser via the room-edit screen's new "Add price option" button. The first row is always a room's plain default price. Exposed as the new price_options array (replacing the old flat price_amount/price_unit/ contact_for_pricing keys) via scrb_get_rooms() and the REST rooms endpoint — a breaking change to that data contract, acceptable for now as this plugin isn't yet in use on more than one site. Rooms priced before this version have their old single price read as that first row automatically (Support\RoomMeta::priceOptions()), so nothing already entered is lost, only left for an admin to re-save into the new shape.
  • Added an Accessibility field (free text) to the Room Details box.
  • The Amenities taxonomy is renamed Facilities — a better fit for venue-hire spaces like a hall or conference room. Every existing term and every room's existing selections carry over automatically on upgrade (Setup\Upgrader renames the taxonomy at the DB level rather than starting a new one) — nothing needs re-ticking. amenities is renamed facilities in scrb_get_rooms()/REST output for the same reason the price fields changed shape above.
  • Added Room Bookings -> Reorder rooms: a drag-and-drop list (one per configured room type) that saves automatically on drop. Stored in each room's own menu_order — no new postmeta — and scrb_get_rooms()/REST order by it first, falling back to the previous oldest-first-by-date order for any room never dragged, so a site that never opens this screen sees no change at all.

0.3.1

  • The "SC Room Bookings" settings menu now reads "SC Room Bookings" in the admin sidebar (not just the page title) and has moved down near the bottom of the menu, alongside SC Events Manager and SC Maps's own settings screens — keeps the admin sidebar's top area for actual content rather than plugin settings.

0.3.0

  • Added "Simple mode" (Room Bookings -> Room Types): a checkbox for sites that only want SC Room Bookings to list rooms, not take bookings. Trims the Room Details box down to Capacity and Suitable for, hides Amenities' checkbox meta box on the room-edit screen, and hides the Bookings/Documentation/Amenities admin menu items. Nothing already saved is touched either way, so it's fully reversible.

0.2.0

  • Room Details box now renders above the content editor (edit_form_after_title, the same technique SC Events Manager's own Event Details box uses) as one postbox split into "Details" and "Availability" headings, rather than two separate boxes below the editor — a common look across both plugins' admin screens.
  • Added a "Suitable for" field to rooms (e.g. "Events and shows").
  • Amenities are now a proper taxonomy with their own Room Bookings -> Amenities screen, tickable per room, instead of free text.
  • Price restructured: a "Contact us for pricing" checkbox alongside a numeric amount + per-hour/session/day/person unit, rather than one free-text field.
  • Added Frontend\RoomListing as the single shared query behind scrb_get_rooms() and the REST rooms endpoint, and fixed room ordering to oldest-first.
  • Settings::addRoomType() no longer overwrites an existing room type of the same key — it now returns the existing type instead, so a second, unrelated call can never silently clobber real room data.

0.1.0

  • Initial framework: room types (add/relabel/delete, mirroring — and starting with the delete action SC Events Manager had to add later), the Room Details meta box, the Booking data model and AvailabilityChecker, the Bookings admin screen (confirm/decline/cancel), email notifications, and the PHP/REST integration surfaces.
  • No public-facing booking form/calendar UI yet — scrb_request_booking()/POST scrb/v1/bookings are ready for a theme or headless front end to build one against.