Skip to main content

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.