GxP Store
The GxP Store (gxpPortalConfigStore) is a Pinia store that provides reactive state management and platform integration for your plugin.
Importing the Store
import { useGxpStore } from '@gx-runtime/stores/gxpPortalConfigStore';
// In your component setup
const store = useGxpStore();
Store Sections
The store contains several reactive sections populated from your app-manifest.json and the platform:
| Section | Description | Source |
|---|---|---|
pluginVars | Plugin settings/configuration | settings in manifest |
stringsList | Translatable strings | strings.default in manifest |
assetList | Asset URLs | assets in manifest |
triggerState | Dynamic runtime state | triggerState in manifest |
dependencyList | External dependencies | Platform-injected |
permissionFlags | Granted permissions | Platform-injected |
theme | Platform theme colors | Platform-injected |
router | Navigation methods | Platform-injected |
Getter Methods
Use these methods to safely access store values with fallbacks:
getString(key, defaultValue)
Get a string from stringsList:
const title = store.getString('welcome_title', 'Welcome');
const button = store.getString('btn_submit', 'Submit');
getSetting(key, defaultValue)
Get a setting from pluginVars:
const color = store.getSetting('primary_color', '#000000');
const timeout = store.getSetting('idle_timeout', 30);
const enabled = store.getSetting('feature_enabled', false);
getAsset(key, defaultValue)
Get an asset URL from assetList:
const logo = store.getAsset('logo', '/fallback-logo.png');
const hero = store.getAsset('hero_image', '/placeholder.jpg');
getState(key, defaultValue)
Get a value from triggerState:
const step = store.getState('current_step', 1);
const isActive = store.getState('is_active', false);
hasPermission(permission)
Check if a permission is granted:
if (store.hasPermission('camera')) {
// Camera access is available
}
if (store.hasPermission('bluetooth')) {
// Bluetooth access is available
}
Update Methods
updateString(key, value)
Update a string value:
store.updateString('dynamic_message', 'Processing your request...');
updateSetting(key, value)
Update a setting value:
store.updateSetting('current_mode', 'advanced');
updateAsset(key, url)
Update an asset URL:
store.updateAsset('user_avatar', 'https://example.com/avatar.jpg');
updateState(key, value)
Update trigger state:
store.updateState('current_step', 2);
store.updateState('is_loading', true);
store.updateState('selected_item', { id: 123, name: 'Item' });
addDevAsset(key, filename)
Add a development asset with the dev server URL prefix:
// Automatically prefixes with dev server URL
store.addDevAsset('temp_image', 'screenshot.png');
// Result: https://localhost:3060/dev-assets/images/screenshot.png
Dependency API Client
The recommended way to make API calls is through the dependency system using callApi(). This method uses the operations defined in your app-manifest.json dependencies.
callApi(operationId, identifier, additionalData)
Call an API operation defined in your dependencies:
const store = useGxpStore();
// GET request - list resources
const items = await store.callApi('access-points.index', 'access_points');
// GET request - single resource (path parameter)
const item = await store.callApi('access-points.show', 'access_points', {
access_point: 123 // Path parameter
});
// POST request - create resource
const newItem = await store.callApi('access-points.store', 'access_points', {
name: 'Main Entrance',
location: 'Building A'
});
// PUT request - update resource
const updated = await store.callApi('access-points.update', 'access_points', {
access_point: 123, // Path parameter
name: 'Updated Name' // Body data
});
// DELETE request
await store.callApi('access-points.destroy', 'access_points', {
access_point: 123
});
Parameters:
| Parameter | Type | Description |
|---|---|---|
operationId | string | The operation key from your dependency's operations object |
identifier | string | The dependency identifier from app-manifest.json |
additionalData | object | Path parameters and/or request body data (optional) |
Returns: response.data from the API response
How it works:
- Looks up the dependency by
identifierindependencyList - Finds the operation by
operationIdin the dependency'soperations - Parses the method and path from the operation value (e.g.,
"get:/v1/...") - Substitutes path parameters from
additionalData(e.g.,{access_point}→123) - Makes the HTTP request with remaining data as query params (GET) or body (POST/PUT)
- Returns
response.data
Example with error handling:
try {
const accessPoints = await store.callApi('access-points.index', 'access_points');
console.log('Loaded', accessPoints.length, 'access points');
} catch (error) {
console.error('Failed to load access points:', error.message);
}
Before using callApi, make sure you've added the dependency to your app-manifest.json. Use gxdev add-dependency to generate the configuration automatically.
Low-Level API Client
For direct API calls without the dependency system, use these methods:
apiGet(endpoint, params)
const response = await store.apiGet('/events/123');
const events = await store.apiGet('/events', { status: 'active' });
apiPost(endpoint, data)
const result = await store.apiPost('/checkin', {
attendee_id: 456,
timestamp: new Date().toISOString()
});
apiPut(endpoint, data)
await store.apiPut('/attendees/456', {
checked_in: true
});
apiDelete(endpoint)
await store.apiDelete('/sessions/789');
Socket.IO Integration
The store provides methods for real-time communication via Socket.IO:
emitSocket(channel, event, data)
Send a socket event:
store.emitSocket('primary', 'checkin-complete', {
attendee_id: 123,
badge_printed: true
});
listenSocket(channel, event, callback)
Listen for socket events:
store.listenSocket('primary', 'session-updated', (data) => {
console.log('Session updated:', data);
store.updateState('current_session', data);
});
useSocketListener(dependencyId, event, callback)
Set up a socket listener for a specific dependency:
store.useSocketListener('badge-printer', 'print-complete', (result) => {
if (result.success) {
store.updateState('badge_printing', false);
}
});
Reactive Usage in Templates
The store is fully reactive. Use it directly in your templates:
<template>
<div :style="{ backgroundColor: store.getSetting('bg_color', '#fff') }">
<h1>{{ store.getString('title', 'Default Title') }}</h1>
<p v-if="store.triggerState.is_loading">Loading...</p>
<div v-for="item in store.triggerState.items" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
<script setup>
import { useGxpStore } from '@gx-runtime/stores/gxpPortalConfigStore';
const store = useGxpStore();
</script>
Watching Store Changes
Use Vue's watch to react to store changes:
import { watch } from 'vue';
import { useGxpStore } from '@gx-runtime/stores/gxpPortalConfigStore';
const store = useGxpStore();
// Watch a specific state value
watch(
() => store.triggerState.current_step,
(newStep, oldStep) => {
console.log(`Step changed from ${oldStep} to ${newStep}`);
}
);
// Watch multiple values
watch(
() => [store.triggerState.is_active, store.pluginVars.mode],
([isActive, mode]) => {
if (isActive && mode === 'kiosk') {
startKioskMode();
}
}
);
Computed Properties
Create computed properties based on store values:
import { computed } from 'vue';
import { useGxpStore } from '@gx-runtime/stores/gxpPortalConfigStore';
const store = useGxpStore();
const isReady = computed(() =>
!store.triggerState.is_loading &&
store.triggerState.data !== null
);
const formattedCount = computed(() =>
`${store.triggerState.checked_in_count} of ${store.pluginVars.total_expected}`
);
Theme Integration
Access platform theme values:
const store = useGxpStore();
// Theme colors
const primaryColor = store.theme?.primary || '#1976D2';
const backgroundColor = store.theme?.background || '#ffffff';
// Use in styles
const buttonStyle = computed(() => ({
backgroundColor: store.theme?.primary,
color: store.theme?.onPrimary
}));
Best Practices
- Use getters with defaults - Always provide fallback values
- Keep state updates atomic - Update one value at a time when possible
- Use computed for derived state - Don't duplicate logic
- Clean up listeners - Remove socket listeners when components unmount
- Avoid deep nesting - Keep
triggerStaterelatively flat for reactivity