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.
