SC Room Bookings
Wordpress plugin for handling room bookings.
by Neil Sayers · github.com/neilsayers/sc-room-booking · website
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.zipA 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-checkerlibrary) 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 repovX.Y.Zand 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
ServerSideRenderpreview's fetched HTML — the editor's ownBlockListBlockwrapper element is built client-side and needed its own fix). Two gaps, both now closed: neither block'sedit()function calledwp.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 declareadd_theme_support('editor-styles')before WordPress adds.editor-styles-wrapperto 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 plaincore/paragraphblock 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()returnsnull(there's nothing inpost_contentto give it the usualwp-block-*wrapper a static block's own saved markup would carry), sorender.phpis responsible for adding that wrapper itself viaget_block_wrapper_attributes()— which neither block'srender.phpwas doing, so there was nowp-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.csswas only ever enqueued onwp_enqueue_scripts, which never fires in the block editor's own admin context, soServerSideRender's live preview had no stylesheet to render against at all. Fixed by registering (not just enqueueing) the style oninit, early enough to exist in both contexts, and having both blocks'block.jsonreference that same handle as their ownstyle— 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 whoserender.phpcalls straight intoRoomListingShortcode::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 listAdmin\ShortcodeButton's TinyMCE modal uses); both blocks preview live in the editor viaServerSideRender, so what you see while editing matches the front end exactly. No build step:block.json'seditorScriptpoints at a plain ES5 file registered with explicitwp-*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 existingrenderShortcode()), matchingRoomListingShortcode's own shape — needed by the Room Detail block, and now also directly callable from a theme template the same wayRoomListingShortcode::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 samemce_external_plugins/mce_buttonsfilters 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 foris_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(), andGET /rooms/{id}/schedule. No code changes. - Verified
Frontend\DefaultTemplatesend-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>), witharia-labelledbywired 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 explicitmargin: 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) andAvailabilityChecker::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 toGET /availability's existing one-candidate-slot-at-a-time check. - Added
Frontend\BookingWidgetand thescrb_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 newbooking_urlfield 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 withtype="room"or a comma list, limit withlimit="6") and[sc_room id="123"](one room's full detail — image, price table, facilities, accessibility). Both also callable directly from a theme template viaFrontend\RoomListingShortcode::render()/RoomDetailShortcode. - Added
scrb_get_room(?int $postId = null)alongside the existingscrb_get_rooms()— one room, shaped the same way, defaulting to the current post. Backed by the newFrontend\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 inassets/css/frontend.css. Steps aside the instant a theme adds its ownsingle-{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()'stypeargument (and so[sc_rooms]'s owntypeattribute) 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_urlsarray 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_variantsarray. - 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_optionsarray (replacing the old flatprice_amount/price_unit/contact_for_pricingkeys) 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.
amenitiesis renamedfacilitiesin 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.