Work staff schedule not readable via the Velo backend

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? :slight_smile:

I am trying to read the staff schedule, visible in the Wix dashboard under staff schedule

Ahhhh, totally misunderstood “backend” (also a term used in code :sweat_smile: )

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.