I’m having trouble with accessing Work staff schedule using Velo back end
I am trying to access Work staff schedule hours using the backend but i get blank fields. I can seed (write a new schedule) but cannot read it back. Any nice souls out there that can help?
Are you able to share the code that you’ve used to try and read?
Ahhhh, totally misunderstood “backend” (also a term used in code )
I’m not sure I follow the issue 100% - are you able to explain a little further?
I have a written a backend file which can seed staff work schedule. This works fine. can see the the Wix dashboard updated as shown in the screen shot. Now I am trying to read it back, I can access the staff data (Name, ID, etc) but not the hours. Hope this makes sense. The backend code i am using is
// backend/http-functions.js
import { ok, badRequest, serverError } from ‘wix-http-functions’;
import { resources, sessions } from ‘wix-bookings-backend’;
/** Utility: Check if an object has any non-empty arrays in its values */
function hasAny(obj) {
return obj && Object.values(obj).some(arr => Array.isArray(arr) && arr.length > 0);
}
/** Utility: “YYYY-MM-DD” + “HH:mm” → LocalDateTime parts */
function toLocalParts(dateStr, timeStr) {
const [y, m, d] = String(dateStr || ‘’).split(‘-’).map(Number);
const [hh, mm] = String(timeStr || ‘’).split(‘:’).map(Number);
if (![y, m, d, hh, mm].every(Number.isFinite)) {
**throw** **new** Error(\`Invalid date/time: "${dateStr} ${timeStr}" (expected "YYYY-MM-DD" + "HH:mm")\`);
}
return { year: y, monthOfYear: m, dayOfMonth: d, hourOfDay: hh, minutesOfHour: mm };
}
/** Weekly RRULE for MO|TU|WE|TH|FR|SA|SU; optional UNTIL=…Z */
function rruleWeekly(dayCode, untilUtc) {
const base = `FREQ=WEEKLY;INTERVAL=1;BYDAY=${dayCode}`;
return untilUtc ? `${base};UNTIL=${untilUtc}` : base;
}
/* ------------ CORE IMPLEMENTATIONS ------------ */
async function createStaffUsingBusinessHoursImpl({ name, email, phone, description = ‘’, businessScheduleId }) {
if (!name || !businessScheduleId) throw new Error(‘name and businessScheduleId are required’);
const resourceInfo = { name, email, phone, description, tags: [‘staff’] };
const scheduleInfos = [{
availability: {
start: **new** Date(),
linkedSchedules: \[\],
linkedScheduleIds: \[\]
}
}];
const resource = await resources.createResource(resourceInfo, scheduleInfos, { suppressAuth: true });
return { resource, scheduleId: resource?.scheduleIds?.[0] || null };
}
async function createStaffWithCustomHoursImpl(payload) {
const {
name, email, phone, description = '',
timezone = 'Europe/London',
startDate,
untilUtc,
weekly = {}
} = payload || {};
if (!name) throw new Error(‘name is required’);
if (!startDate) throw new Error(‘startDate (YYYY-MM-DD) is required’);
const resourceInfo = { name, email, phone, description, tags: [‘staff’] };
const scheduleInfos = [{ availability: { start: new Date(), linkedSchedules: } }];
const resource = await resources.createResource(resourceInfo, scheduleInfos, { suppressAuth: true });
const scheduleId = resource?.scheduleIds?.[0];
if (!scheduleId) throw new Error(‘No scheduleId on created resource’);
const days = [‘MO’, ‘TU’, ‘WE’, ‘TH’, ‘FR’, ‘SA’, ‘SU’];
const createdSessions = ;
for (const dayCode of days) {
**const** ranges = weekly\[dayCode\];
**if** (!Array.isArray(ranges) || !ranges.length) **continue**;
**for** (**const** range **of** ranges) {
**const** \[**from**, to\] = String(range).split('-');
**if** (!**from** || !to) **throw** **new** Error(\`Invalid time range "${range}" for ${dayCode}. Use "HH:mm-HH:mm".\`);
**const** startParts = toLocalParts(startDate, **from**);
**const** endParts = toLocalParts(startDate, to);
**const** sessionInfo = {
scheduleId,
start: { localDateTime: startParts, timeZone: timezone },
end: { localDateTime: endParts, timeZone: timezone },
type: 'WORKING_HOURS',
recurrence: rruleWeekly(dayCode, untilUtc)
};
**const** session = **await** sessions.createSession(sessionInfo, { suppressAuth: **true** });
createdSessions.push(session);
}
}
return { resource, scheduleId, sessions: createdSessions };
}
/* -------------- HTTP FUNCTIONS (LIVE = /_functions/… ) -------------- */
export function get_ping(_request) {
return ok({ body: { pong: true } });
}
export async function get_businessScheduleId(_request) {
try {
**const** q = **await** resources.queryResourceCatalog().limit(50).find({ suppressAuth: **true** });
**const** item = q.items?.find(i => Array.isArray(i?.slugs) && i.slugs.some(s => s?.name === 'business'));
**const** scheduleId = item?.resource?.scheduleIds?.\[0\] || **null**;
**return** ok({ body: { scheduleId } });
} catch (err) {
**return** serverError({ body: { error: String(err?.message || err) } });
}
}
export async function post_createStaffCustom(request) {
try {
**const** body = **await** request.body.json();
**const** result = **await** createStaffWithCustomHoursImpl(body);
**return** ok({ body: result });
} catch (err) {
**return** badRequest({ body: { error: String(err?.message || err) } });
}
}
export async function post_createStaffInherit(request) {
try {
**const** body = **await** request.body.json();
**const** result = **await** createStaffUsingBusinessHoursImpl(body);
**return** ok({ body: result });
} catch (err) {
**return** badRequest({ body: { error: String(err?.message || err) } });
}
}
export async function get_resourceCatalog(_request) {
try {
**const** q = **await** resources.queryResourceCatalog().limit(200).find({ suppressAuth: **true** });
**const** items = (q.items || \[\]).map(i => ({
slugs: (i.slugs || \[\]).map(s => s?.name).filter(Boolean),
name: i?.resource?.name,
tags: i?.resource?.tags,
scheduleIds: i?.resource?.scheduleIds || \[\]
}));
**return** ok({ body: { count: items.length, items } });
} catch (err) {
**return** serverError({ body: { error: String(err?.message || err) } });
}
}
export async function get_businessScheduleIdSafe(_request) {
try {
**const** q = **await** resources.queryResourceCatalog().limit(200).find({ suppressAuth: **true** });
**const** items = q.items || \[\];
**let** match = items.find(i => Array.isArray(i?.slugs) && i.slugs.some(s => s?.name === 'business'));
**if** (!match) {
match = items.find(i =>
(i?.resource?.tags || \[\]).includes('business') ||
/business/**i**.test(i?.resource?.name || '')
);
}
**const** scheduleId = match?.resource?.scheduleIds?.\[0\] || **null**;
**return** ok({ body: { scheduleId, hint: match ? { name: match.resource?.name, slugs: match.slugs } : **null** } });
} catch (err) {
**return** serverError({ body: { error: String(err?.message || err) } });
}
}
export async function get_staffList(_request) {
try {
**const** q = **await** resources.queryResourceCatalog().limit(200).find({ suppressAuth: **true** });
**const** items = (q.items || \[\])
.filter(i => (i?.resource?.tags || \[\]).includes('staff'))
.map(i => ({
name: i?.resource?.name,
email: i?.resource?.email,
scheduleId: i?.resource?.scheduleIds?.\[0\] || **null**
}));
**return** ok({ body: { count: items.length, items } });
} catch (err) {
**return** serverError({ body: { error: String(err?.message || err) } });
}
}
export async function get_staffWorkingHours(_request) {
try {
console.log('Starting get_staffWorkingHours');
**const** cat = **await** resources.queryResourceCatalog().limit(200).find({ suppressAuth: **true** });
console.log('Resources found:', cat.items.length);
**let** businessScheduleId = **null**;
**const** bizMatch = cat.items.find(i => Array.isArray(i?.slugs) && i.slugs.some(s => s?.name === 'business')) ||
cat.items.find(i => (i?.resource?.tags || \[\]).includes('business')) ||
cat.items.find(i => /business/**i**.test(i?.resource?.name || ''));
businessScheduleId = bizMatch?.resource?.scheduleIds?.\[0\] || **null**;
console.log('Business schedule ID:', businessScheduleId, 'Details:', bizMatch ? { name: bizMatch.resource?.name, slugs: bizMatch.slugs } : 'None');
**const** staffItems = cat.items
.filter(i => (i?.resource?.tags || \[\]).includes('staff'))
.map(i => ({
name: i?.resource?.name || 'Unnamed',
resourceId: i?.resource?.\_id ?? **null**,
scheduleId: i?.resource?.scheduleIds?.\[0\] ?? **null**
}));
console.log('Staff found:', staffItems.length, 'Details:', staffItems);
**const** allScheduleIds = Array.**from**(**new** Set(\[
...staffItems.map(s => s.scheduleId).filter(Boolean),
...(businessScheduleId ? \[businessScheduleId\] : \[\])
\]));
console.log('Schedules to query:', allScheduleIds);
**const** allSessions = \[\];
**for** (**const** scheduleId **of** allScheduleIds) {
**try** {
**const** res = **await** sessions.querySessions()
.eq('scheduleId', scheduleId)
.eq('type', 'WORKING_HOURS')
.limit(1000)
.find({ suppressAuth: **true** });
console.log(\`Sessions for schedule ${scheduleId}:\`, res.items.length, 'Details:', res.items.map(s => ({
start: s.start?.localDateTime,
end: s.end?.localDateTime,
recurrence: s.recurrence
})));
allSessions.push(...res.items);
} **catch** (err) {
console.error(\`Error querying sessions for schedule ${scheduleId}:\`, err);
}
}
console.log('Total sessions found:', allSessions.length);
// Define helper functions within the function scope
**const** DAY_ORDER = \['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'\];
**const** parseByDay = (rr = '') => {
**const** m = /(?:^|;)BYDAY=(\[A-Z,\]+)/**i**.exec(rr);
**return** m ? m\[1\].toUpperCase().split(',').map(d => d.trim()).filter(d => DAY_ORDER.includes(d)) : \[\];
};
**const** fmtTime = (ldt) => {
**if** (!ldt) **return** '';
**const** hh = String(ldt.hourOfDay || 0).padStart(2, '0');
**const** mm = String(ldt.minutesOfHour || 0).padStart(2, '0');
**return** \`${hh}:${mm}\`;
};
**const** weeklyFromSessions = (sessions) => {
**const** weekly = { MO: \[\], TU: \[\], WE: \[\], TH: \[\], FR: \[\], SA: \[\], SU: \[\] };
**for** (**const** s **of** sessions) {
**if** (s.type !== 'WORKING_HOURS') **continue**;
**const** start = fmtTime(s.start?.localDateTime);
**const** end = fmtTime(s.end?.localDateTime);
**const** range = \`${start}-${end}\`;
**if** (!start || !end || range === '-') **continue**;
**const** days = parseByDay(s.recurrence);
**if** (days?.length) **for** (**const** d **of** days) **if** (weekly\[d\]) weekly\[d\].push(range);
}
**for** (**const** d **of** DAY_ORDER) weekly\[d\].sort((a, b) => a.localeCompare(b));
**return** weekly;
};
**const** out = staffItems.map(s => {
**const** staffSessions = allSessions.filter(ses => ses.scheduleId === s.scheduleId);
**let** weekly = weeklyFromSessions(staffSessions);
console.log(\`Staff ${s.name} (schedule ${s.scheduleId}) sessions:\`, staffSessions.length, 'Hours:', weekly);
**if** (!hasAny(weekly) && businessScheduleId) {
**const** businessSessions = allSessions.filter(ses => ses.scheduleId === businessScheduleId);
weekly = weeklyFromSessions(businessSessions);
console.log(\`Staff ${s.name} business fallback (schedule ${businessScheduleId}) sessions:\`, businessSessions.length, 'Hours:', weekly);
}
**return** { ...s, weekly };
});
out.sort((a, b) => a.name.localeCompare(b.name));
console.log('Final output:', out);
**return** ok({ body: { items: out, dayOrder: DAY_ORDER, labels: { MO: 'Mon', TU: 'Tue', WE: 'Wed', TH: 'Thu', FR: 'Fri', SA: 'Sat', SU: 'Sun' } } });
} catch (err) {
console.error('Error in get_staffWorkingHours:', err);
**return** serverError({ body: { error: String(err?.message || err) } });
}
}
export async function get_debugSessions(request) {
try {
**const** { query } = request;
**const** sid = query?.sid;
**if** (!sid) {
**return** ok({ body: { error: 'Provide sid query param' } });
}
**const** res = **await** sessions.querySessions()
.eq('scheduleId', String(sid))
.limit(1000)
.find({ suppressAuth: **true** });
**const** items = (res.items || \[\]).map(s => ({
\_id: s.\_id,
type: s.type,
scheduleId: s.scheduleId,
start: s.start?.localDateTime,
end: s.end?.localDateTime,
recurrence: s.recurrence
}));
**return** ok({ body: { count: items.length, items } });
} catch (err) {
**return** serverError({ body: { error: String(err?.message || err) } });
}
}
export async function post_seedScheduleHours(request) {
try {
**const** { scheduleId, timezone = 'Europe/London', startDate, untilUtc, weekly = {} } =
**await** request.body.json();
**if** (!scheduleId) **throw** **new** Error('scheduleId is required');
**if** (!startDate) **throw** **new** Error('startDate (YYYY-MM-DD) is required');
**const** days = \['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'\];
**const** created = \[\];
**for** (**const** day **of** days) {
**const** ranges = weekly\[day\];
**if** (!Array.isArray(ranges) || !ranges.length) **continue**;
**for** (**const** range **of** ranges) {
**const** \[**from**, to\] = String(range).split('-');
**if** (!**from** || !to) **throw** **new** Error(\`Invalid time range "${range}" for ${day}. Use "HH:mm-HH:mm".\`);
**const** sessionInfo = {
scheduleId,
start: { localDateTime: toLocalParts(startDate, **from**), timeZone: timezone },
end: { localDateTime: toLocalParts(startDate, to), timeZone: timezone },
type: 'WORKING_HOURS',
recurrence: rruleWeekly(day, untilUtc)
};
**const** s = **await** sessions.createSession(sessionInfo, { suppressAuth: **true** });
created.push(s);
}
}
**return** ok({ body: { scheduleId, createdCount: created.length } });
} catch (err) {
**return** badRequest({ body: { error: String(err?.message || err) } });
}
}
Subject: Issue Reading Seeded Working Hours in Velo Backend for Wix Bookings
Description: I’m experiencing an issue with my Velo backend code on my Wix site (feetinneed.co.uk) where I can seed staff working hours into the Wix Bookings Dashboard Work Schedule using the post_seedScheduleHours function, but I cannot read them back via the get_debugSessions or get_staffWorkingHours functions. The hours for staff member “Alex Smith” (scheduleId: “38cf0188-c2eb-4493-8f68-ac562d0480ae”) are visible in the Work Schedule (Monday 8:00 AM – 12:00 PM and 1:00 PM – 4:00 PM, Wednesday 9:00 AM – 5:00 PM, Friday 9:00 AM – 5:00 PM), but the backend returns no sessions (count: 0) and an empty weekly field.
Steps Taken:
-
Successfully seeded hours using post_seedScheduleHours, which returns { scheduleId: ‘38cf0188-c2eb-4493-8f68-ac562d0480ae’, createdCount: 4 } with a 200 status.
-
Verified the hours appear permanently in the Work Schedule.
-
Checked get_debugSessions, which consistently returns { count: 0, items: }.
-
Tested get_staffWorkingHours, which returns a 200 status but an empty weekly field for all 13 staff members.
-
Reviewed Velo Logs, but no clear errors are logged during seeding or querying.
-
Tried different startDate values (e.g., “2025-08-22”, “2025-08-23”) and waited over 45 minutes for propagation.
-
Confirmed the scheduleId matches Alex Smith via get_staffList.
Current Setup:
-
Using Velo with wix-bookings-backend and wix-http-functions.
-
Backend code (http-functions.js) includes functions: get_ping, get_businessScheduleId, post_createStaffCustom, post_createStaffInherit, get_resourceCatalog, get_businessScheduleIdSafe, get_staffList, get_staffWorkingHours, get_debugSessions, and post_seedScheduleHours.
-
No custom mirror collection (e.g., BookingsHoursMirror) is used, adhering to a single source of truth from Wix Bookings.
Issue Details:
-
Seeding works and updates the Dashboard, but the seeded WORKING_HOURS sessions aren’t retrievable via get_debugSessions or reflected in get_staffWorkingHours.
-
Suspect the data might be stored in a schedule configuration layer (e.g., wix-bookings-backend.schedules) rather than the sessions API, or there’s a Wix API bug preventing session persistence.
-
Manual re-saving of hours in the Work Schedule hasn’t yet been tested post-seeding.
Request for Help:
-
Can anyone confirm where Wix Bookings stores the standard working hours visible in the Work Schedule, and how to access them via Velo (e.g., a schedules API like getAvailability)?
-
Is this a known issue with sessions.createSession() not persisting data despite a successful createdCount response?
-
Any suggestions to debug further or workarounds to read the Dashboard hours directly into get_staffWorkingHours?
Additional Information:
-
Site URL: feetinneed.co.uk
-
Date of Issue: August 22, 2025
-
Velo Code and Outputs Available: Happy to share upon request.