Developing CRM cards with Vue.js for Bitrix24

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Developing CRM Cards on Vue.js for Bitrix24

The standard deal, contact, or lead card in Bitrix24 covers 80% of needs. Problems begin with the remaining 20%: displaying data from an external system directly in the card, adding a button with non-trivial behavior, showing related objects as a table, or making fields dependent on one another. Bitrix24 provides an embedding mechanism — placement — through which a Vue application mounts directly inside the CRM card.

The Placement Mechanism in CRM

BX24.placement.getInterface() returns information about the current context — where exactly the card is open and which object is displayed:

window.BX24.init(() => {
  BX24.placement.getInterface((result) => {
    // result: { ID, ENTITY_TYPE_NAME, ENTITY_TYPE_ID }
    // e.g.: { ID: 123, ENTITY_TYPE_NAME: 'deal', ENTITY_TYPE_ID: 2 }
    initApp(result)
  })
})

Placement types for CRM:

  • CRM_DEAL_DETAIL_TAB — tab in the deal card
  • CRM_CONTACT_DETAIL_TAB — tab in the contact card
  • CRM_LEAD_DETAIL_TAB — tab in the lead card
  • CRM_DEAL_DETAIL_ACTIVITY — activities block

Vue Card Architecture

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

window.BX24.init(async () => {
  const placement = await getPlacement()
  const auth = BX24.getAuth()

  const app = createApp(App)
  app.use(createPinia())
  app.provide('entityId', placement.ID)
  app.provide('entityType', placement.ENTITY_TYPE_NAME)
  app.provide('auth', auth)
  app.mount('#app')
})

function getPlacement() {
  return new Promise(resolve => BX24.placement.getInterface(resolve))
}

Pinia for card state management is the optimal choice: supports DevTools, TypeScript, works without this.

Loading Entity Data

// stores/dealStore.js
import { defineStore } from 'pinia'
import { inject } from 'vue'

export const useDealStore = defineStore('deal', {
  state: () => ({
    deal: null,
    relatedContacts: [],
    externalData: null,
    loading: false,
  }),

  actions: {
    async loadDeal(id) {
      this.loading = true
      const [deal, contacts] = await Promise.all([
        bx24Call('crm.deal.get', { id }),
        bx24Call('crm.deal.contact.items.get', { id }),
      ])
      this.deal = deal
      this.relatedContacts = contacts
      this.loading = false
    },

    async loadExternalData(dealId) {
      // Data from an external system via a custom API
      const res = await fetch(`/api/deals/${dealId}/external`)
      this.externalData = await res.json()
    }
  }
})

Updating Card Fields

Updating a CRM object via REST:

async function updateDeal(id, fields) {
  return new Promise((resolve, reject) => {
    BX24.callMethod('crm.deal.update', {
      id,
      fields,
    }, (result) => {
      if (result.error()) reject(result.error())
      else resolve(result.data())
    })
  })
}

For multiple fields simultaneously — use a batch request. Do not update fields one by one in a loop — this creates a request queue and slows down the interface.

Custom Buttons and Actions

Adding a button to the card toolbar via BX24.placement.bindEvent:

BX24.placement.bindEvent('onAppOptionsSave', () => {
  // React to saving application settings
})

// Action button is registered via callMethod
BX24.callMethod('placement.bind', {
  PLACEMENT: 'CRM_DEAL_DETAIL_TAB',
  HANDLER: 'https://my-app.example.com/',
  TITLE: 'My Tab',
  DESCRIPTION: 'CRM card extension',
})

Inside the Vue application itself, buttons are regular components emitting events and calling REST methods.

Displaying Data from External Systems

A typical use case — showing a customer's order history from 1C or an ERP in the deal card:

<template>
  <div class="external-orders">
    <div v-if="store.loading" class="loader">Loading...</div>
    <table v-else>
      <thead>
        <tr>
          <th>Order Number</th>
          <th>Date</th>
          <th>Amount</th>
          <th>Status</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="order in store.externalData?.orders" :key="order.id">
          <td>{{ order.number }}</td>
          <td>{{ formatDate(order.date) }}</td>
          <td>{{ formatMoney(order.total) }}</td>
          <td>
            <span :class="statusClass(order.status)">
              {{ order.statusLabel }}
            </span>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

The server side is a proxy endpoint that authenticates with 1C, retrieves the data, and returns JSON. Bitrix24 does not access 1C directly from the frontend.

Reactive Updates on Changes

If a user changed a field in the standard card and switched to your tab — data needs to be reloaded:

// Check relevance when the tab gains focus
document.addEventListener('visibilitychange', () => {
  if (!document.hidden) store.loadDeal(entityId)
})

Alternatively — subscribe to events via BX24.placement.bindEvent('onAppOptionsSave', callback).

Styling to Match the Bitrix24 Interface

Use CSS variables and neutral components. Bitrix24 has its own design system, and a custom tab should look organic within it. Minimal shadows, thin borders, sans-serif fonts. Headless UI or custom components are preferable over heavy UI libraries with bold themes.

Timeline

A simple tab displaying REST data with an action button — 2–4 business days. A full-featured tab with external system data, editing, and synchronization — 1–3 weeks depending on the number of entities and the complexity of the logic.