Vuex/Pinia Setup for State Management in 1C-Bitrix
When two or more Vue components on a Bitrix page need to share common data — cart state, current user, catalog filters — a centralized store is required. Without it, each component makes a separate AJAX request to the same endpoint, data falls out of sync, and adding a product to the cart in one component doesn't update the counter in the header.
Pinia vs Vuex in 2024+ Projects
For new Vue 3 projects — Pinia. Vuex 4 is still maintained but has officially been superseded by Pinia. Key differences in the Bitrix context:
Pinia requires no mutations — state is changed directly in actions. Excellent TypeScript support. DevTools work out of the box. Less boilerplate.
Vuex is justified if the project is already on Vue 2 with Vuex 3, or if the team knows Vuex well and migration is not practical.
Store Initialization in a Bitrix Context
When multiple Vue applications are mounted on different page elements (the cart in the header and the product block at the bottom are separate createApp() calls), they don't automatically share a store. The solution — a singleton via window:
// store/index.js
import { createPinia } from 'pinia';
const pinia = window.__pinia || (window.__pinia = createPinia());
export default pinia;
// In each app.js
import pinia from './store/index.js';
const app = createApp(Component);
app.use(pinia);
app.mount('#mount-point');
Both Vue applications use the same Pinia instance — cart state is synchronized.
Cart Store for Bitrix
// stores/cart.js
import { defineStore } from 'pinia';
export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
loading: false,
initialized: false,
}),
getters: {
totalCount: (state) => state.items.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: (state) => state.items.reduce((sum, i) => sum + i.price * i.quantity, 0),
},
actions: {
async init() {
if (this.initialized) return;
this.loading = true;
const res = await fetch('/api/v1/cart/');
this.items = await res.json();
this.initialized = true;
this.loading = false;
},
async addItem(productId, quantity = 1) {
const res = await fetch('/api/v1/cart/add/', {
method: 'POST',
headers: { 'X-Bitrix-Csrf-Token': window.BX_STATE.csrf },
body: JSON.stringify({ productId, quantity }),
});
const updated = await res.json();
this.items = updated.items;
},
},
});
A custom Bitrix-side controller calls \Bitrix\Sale\Basket::loadItemsForFUser() and returns JSON. The CSRF token (bitrix_sessid()) is passed from PHP into window.BX_STATE.csrf.
Authorization and User Store
export const useUserStore = defineStore('user', {
state: () => ({
id: window.BX_STATE?.userId || null,
groups: window.BX_STATE?.userGroups || [],
priceTypeId: window.BX_STATE?.priceTypeId || 1,
}),
getters: {
isAuthorized: (state) => !!state.id,
isWholesale: (state) => state.groups.includes(WHOLESALE_GROUP_ID),
},
});
User data is initialized from window.BX_STATE, which is assembled in PHP once when the page loads. No AJAX requests needed to obtain basic user information.
State Persistence
For data that needs to persist between pages (e.g., selected catalog filters), use pinia-plugin-persistedstate:
pinia.use(piniaPluginPersistedstate);
export const useFiltersStore = defineStore('filters', {
state: () => ({ selectedBrands: [], priceRange: [0, 100000] }),
persist: { storage: sessionStorage },
});
sessionStorage is preferable to localStorage for filters — data resets when the tab is closed, preventing stale values from accumulating.
DevTools and Debugging
Vue DevTools (browser extension) shows the state of all Pinia stores in real time — a key tool during debugging. On production, Pinia DevTools are automatically disabled in Vite's production build. Mutation logging via Pinia $subscribe — for debugging complex update scenarios.
Typical setup timeline: for a project without an existing store, with 2–3 components that need shared data (cart, authorization, notifications) — 1–2 business days, including persistence setup and integration with Bitrix PHP controllers.







