Best Practices
Follow these patterns to build reliable, performant Connected Apps with Vue.js.
SDK Initialization#
Always wait for app.loaded#
The SDK is ready to use only after the app.loaded event fires. Making API calls before this event will fail silently or throw errors.
// CORRECT — wait for the SDK to be readycreated() { window.facilioApp = FacilioAppSDK.init(); window.facilioApp.on('app.loaded', async (data) => { await this.loadData(); });}// WRONG — calling API before app.loadedcreated() { window.facilioApp = FacilioAppSDK.init(); this.loadData(); // will fail — SDK not ready yet}Initialize the SDK once in created()#
Create a single SDK instance in your Vue root component's created() lifecycle hook and store it on window for global access.
var app = new Vue({ el: '#app', data: { loading: true }, created() { window.facilioApp = FacilioAppSDK.init(); window.facilioApp.on('app.loaded', async (data) => { await this.init(); this.loading = false; }); }, methods: { async init() { this.user = window.facilioApp.getCurrentUser(); this.org = window.facilioApp.getCurrentOrg(); } }});Error Handling#
Wrap API calls in try-catch#
Every API call can fail due to network issues, permission errors, or invalid data. Always handle errors gracefully.
methods: { async fetchSiteData(siteId) { try { const response = await window.facilioApp.api.fetchRecord('site', { id: siteId }); return response; } catch (error) { console.error('Failed to fetch site:', error); window.facilioApp.interface.notify({ title: 'Error', message: 'Could not load site data. Please try again.', type: 'error', duration: 4000 }); return null; } }}Data Fetching#
Paginate large datasets#
Don't fetch all records at once. Use page and perPage to load data in chunks.
methods: { async fetchAllWorkOrders() { let allRecords = []; let page = 1; const perPage = 50; let hasMore = true;
while (hasMore) { const { list } = await window.facilioApp.api.fetchAll('workorder', { viewName: 'all', page, perPage }); allRecords = allRecords.concat(list); hasMore = list.length === perPage; page++; }
return allRecords; }}Use filters server-side#
Filter records through the API instead of fetching everything and filtering in JavaScript.
// CORRECT — filter on the serverconst { list } = await window.facilioApp.api.fetchAll('workorder', { viewName: 'all', filters: JSON.stringify({ status: { operator: 'is', value: ['Active'] } })});
// WRONG — fetch all, then filter in memoryconst { list } = await window.facilioApp.api.fetchAll('workorder', { viewName: 'all' });const active = list.filter(wo => wo.status === 'Active');Cache context data#
Values like getCurrentUser(), getCurrentOrg(), and getWidget() don't change during a session. Fetch them once in app.loaded and store in your Vue data.
data: { currentUser: null, currentOrg: null},created() { window.facilioApp = FacilioAppSDK.init(); window.facilioApp.on('app.loaded', () => { this.currentUser = window.facilioApp.getCurrentUser(); this.currentOrg = window.facilioApp.getCurrentOrg(); });}Form Widgets#
Listen to specific fields, not all changes#
Use form.{fieldName}.changed instead of form.changed when you only care about specific fields. This avoids unnecessary processing.
created() { window.facilioApp = FacilioAppSDK.init(); window.facilioApp.on('app.loaded', () => { // Efficient — only triggers when vendor field changes window.facilioApp.on('form.vendor.changed', (fieldValue) => { this.handleVendorChange(fieldValue); }); });}Validate before setting values#
When using setValue, validate the data before writing to avoid putting the form in an invalid state.
created() { window.facilioApp = FacilioAppSDK.init(); window.facilioApp.on('app.loaded', () => { window.facilioApp.on('form.quantity.changed', async (fieldValue) => { const quantity = fieldValue?.value; if (quantity && quantity > 0) { const unitPrice = await this.getUnitPrice(); window.facilioApp.interface.trigger('setValue', { fieldName: 'total', value: quantity * unitPrice }); } }); });}UI and Notifications#
Use notify() instead of alert()#
Browser alert() blocks the thread and looks out of place inside Facilio. Use the SDK's notification system.
methods: { showSuccess(message) { window.facilioApp.interface.notify({ title: 'Saved', message, type: 'success', duration: 3000, position: 'top-right' }); }}Resize dialogs appropriately#
For Custom Button (web only), use preset sizes: XS, S, M, L, XL. For Dialer, use width and height in px.
// Custom Button (web)window.facilioApp.interface.trigger('resize', { size: 'L' });
// Dialerwindow.facilioApp.interface.trigger('resize', { width: '320px', height: '480px' });Variables#
Use variables for configuration, not large data#
The variables store is designed for small key-value pairs like API tokens and settings — not for caching large datasets.
// Good — store a tokenawait window.facilioApp.variables.set('api_token', 'xxxx-xxxx');
// Bad — store a large datasetawait window.facilioApp.variables.set('all_records', JSON.stringify(hugeArray));Live Events#
Unsubscribe when no longer needed#
If you subscribe to live events, unsubscribe in the app.destroyed handler to prevent memory leaks.
data: { subscribedToken: null},created() { window.facilioApp = FacilioAppSDK.init(); window.facilioApp.on('app.loaded', async () => { this.subscribedToken = await window.facilioApp.liveevent.subscribeUserEvent('alarm_event'); window.facilioApp.on(this.subscribedToken, (data) => { this.handleAlarmEvent(data); }); }); window.facilioApp.on('app.destroyed', () => { if (this.subscribedToken) { window.facilioApp.liveevent.unsubscribe(this.subscribedToken); } });}Performance#
Debounce form field handlers#
Form field change events can fire rapidly. Debounce expensive operations like API calls.
data: { debounceTimer: null},created() { window.facilioApp = FacilioAppSDK.init(); window.facilioApp.on('app.loaded', () => { window.facilioApp.on('form.search.changed', (fieldValue) => { clearTimeout(this.debounceTimer); this.debounceTimer = setTimeout(() => { this.performSearch(fieldValue.value); }, 300); }); });}Show loading states#
Use Vue's reactive data to show a loading state immediately, then populate content as data arrives.
<div id="app"> <div v-if="loading" class="loading">Loading...</div> <div v-else-if="error" class="error">{{ error }}</div> <div v-else> <!-- content --> </div></div>data: { loading: true, error: null, workorders: []},created() { window.facilioApp = FacilioAppSDK.init(); window.facilioApp.on('app.loaded', async () => { try { const { list } = await window.facilioApp.api.fetchAll('workorder', { viewName: 'all', perPage: 20 }); this.workorders = list; } catch (err) { this.error = 'Failed to load work orders'; } finally { this.loading = false; } });}Security#
Never hardcode secrets#
Use the Variables API to store sensitive values like API keys. Never put them in your source code.
// Set once (e.g., during setup)await window.facilioApp.variables.set('external_api_key', userProvidedKey);
// Retrieve when neededconst apiKey = await window.facilioApp.variables.get('external_api_key');Rely on platform authentication#
The SDK handles authentication automatically. You don't need to manage tokens, API keys, or login flows for Facilio API access.