CONTRIBUTING
Plugin Development
Learn how to create custom plugins to extend Invenicum’s functionality using the STAC framework.
Invenicum’s plugin system allows you to extend the application with custom UI components and integrations. Plugins are built using the STAC framework and interact with Invenicum’s core features through a comprehensive SDK.
Plugin Architecture Overview
STAC UI Definition
Declarative UI in JSON
Plugin Manifest
Metadata & configuration
SDK Actions
Native functionality calls
UI Slots
Predefined render locations
How Plugins Work
Getting Started
Set Up Your Development Environment
You’ll need:
Understand the STAC Framework
STAC allows you to build UIs declaratively using JSON. Here’s a minimal example:
{
"type": "container",
"child": {
"type": "text",
"data": "Hello from my plugin!"
}
}Create Your Plugin Structure
A plugin is a single JSON file with this top-level structure:
{
"id": "my-custom-plugin",
"name": "My Custom Plugin",
"version": "1.0.0",
"author": "your-github-username",
"description": "A brief description of what this plugin does",
"slot": "dashboard_top",
"ui": {
// STAC UI definition here
}
}Plugin Manifest
Every plugin requires these fields in its manifest:
idUnique identifier for your plugin
nameDisplay name
versionSemantic version (e.g., "1.0.0")
authorYour GitHub username
descriptionBrief description (max 200 chars)
slotWhere the plugin renders (see UI Slots)
uiSTAC UI definition
authorAvatarURL to author avatar image
isPublicWhether plugin is publicly available (default: false)
Example Manifest
{
"id": "dashboard-stats",
"name": "Enhanced Dashboard Stats",
"version": "1.2.0",
"author": "yourusername",
"description": "Display advanced statistics on your dashboard with charts and insights",
"slot": "dashboard_top",
"authorAvatar": "https://github.com/yourusername.png",
"isPublic": true,
"ui": {
"type": "container",
"child": {
"type": "text",
"data": "Stats go here"
}
}
}UI Slots
Plugins can render in various locations throughout the Invenicum app:
dashboard_topTop of dashboard
Summary widgets, important alerts
dashboard_bottomBottom of dashboard
Charts, recent activity
asset_detail_topTop of asset detail page
Custom asset actions
asset_detail_bottomBottom of asset detail page
Additional asset information
sidebarApp sidebar
Quick actions, navigation
Invenicum SDK Functions
The SDK allows plugins to interact with Invenicum’s core functionality via InvenicumSdkParser (sdk_plugin_parser.dart:21).
Available SDK Methods
getUserName()sdk_plugin_parser.dart:42Description
Get the current user’s name and display it as a toast notification.
Implementation
case 'getUserName':
ToastService.info(
"Usuario actual: ${userName ?? 'Invitado'}");
break;STAC Action
{
"type": "action",
"action": {
"type": "invenicum_sdk",
"method": "getUserName",
"params": {}
}
}showAlert(message)sdk_plugin_parser.dart:47Description
Display a success toast message to the user.
messageStringThe message to displayImplementation
case 'showAlert': ToastService.success( model.params['message'] ?? 'Acción ejecutada'); break;
STAC Action
{
"type": "action",
"action": {
"type": "invenicum_sdk",
"method": "showAlert",
"params": {
"message": "Plugin action completed!"
}
}
}navigate(path)sdk_plugin_parser.dart:51Description
Navigate to a specific route in the app.
pathStringRoute path to navigate toImplementation
case 'navigate':
final path = model.params['path'];
if (path != null) {
debugPrint("Navegando a: $path");
// Navigation implementation
}
break;STAC Action
{
"type": "action",
"action": {
"type": "invenicum_sdk",
"method": "navigate",
"params": {
"path": "/assets/create"
}
}
}logout()sdk_plugin_parser.dart:59Description
Log out the current user from the app.
Implementation
case 'logout':
context.read<AuthProvider>().logout();
ToastService.info("Sesión cerrada");
break;STAC Action
{
"type": "action",
"action": {
"type": "invenicum_sdk",
"method": "logout",
"params": {}
}
}Using SDK in a Button — Complete Example
{
"type": "elevatedButton",
"data": "Click Me",
"onPressed": {
"type": "invenicum_sdk",
"method": "showAlert",
"params": {
"message": "Hello from my plugin!"
}
}
}Building Your First Plugin
Create the Plugin File
Create a new file called quick-actions.json:
{
"id": "quick-actions",
"name": "Quick Actions",
"version": "1.0.0",
"author": "yourusername",
"description": "Quick action buttons for common tasks",
"slot": "dashboard_top",
"ui": {}
}Design the UI
Add the STAC UI definition to the “ui” field:
"ui": {
"type": "card",
"child": {
"type": "column",
"children": [
{
"type": "text",
"data": "Quick Actions",
"style": { "fontSize": 18, "fontWeight": "bold" }
},
{
"type": "row",
"mainAxisAlignment": "spaceEvenly",
"children": [
{
"type": "elevatedButton",
"data": "Add Asset",
"onPressed": {
"type": "invenicum_sdk",
"method": "navigate",
"params": { "path": "/assets/create" }
}
},
{
"type": "elevatedButton",
"data": "View Loans",
"onPressed": {
"type": "invenicum_sdk",
"method": "navigate",
"params": { "path": "/loans" }
}
}
]
}
]
}
}Test Locally
Publish to GitHub
Advanced Plugin Examples
{
"id": "counter-widget",
"name": "Counter Widget",
"version": "1.0.0",
"slot": "dashboard_top",
"ui": {
"type": "card",
"child": {
"type": "column",
"mainAxisAlignment": "center",
"children": [
{
"type": "text",
"data": "Counter: 0",
"id": "counterText"
},
{
"type": "row",
"mainAxisAlignment": "center",
"children": [
{
"type": "iconButton",
"icon": "remove",
"onPressed": {
"type": "invenicum_sdk",
"method": "showAlert",
"params": { "message": "Decrement clicked" }
}
},
{
"type": "iconButton",
"icon": "add",
"onPressed": {
"type": "invenicum_sdk",
"method": "showAlert",
"params": { "message": "Increment clicked" }
}
}
]
}
]
}
}
}{
"id": "user-greeting",
"name": "User Greeting",
"version": "1.0.0",
"slot": "dashboard_top",
"ui": {
"type": "card",
"child": {
"type": "column",
"children": [
{
"type": "text",
"data": "Welcome back!",
"style": {
"fontSize": 24,
"fontWeight": "bold"
}
},
{
"type": "elevatedButton",
"data": "Show My Username",
"onPressed": {
"type": "invenicum_sdk",
"method": "getUserName",
"params": {}
}
}
]
}
}
}Plugin Service API
The PluginService (plugin_service.dart:22) provides methods for plugin management:
Future<void> installPlugin(
Map<String, dynamic> pluginData) async {
await _dio.post('/plugins/install', data: pluginData);
}Future<void> toggleUserPlugin(
String pluginId, bool isActive) async {
await _dio.put('/plugins/user/toggle',
data: {'pluginId': pluginId, 'isActive': isActive});
}Future<List<Map<String, dynamic>>> getMyPlugins() async {
final response = await _dio.get('/plugins/installed');
return List<Map<String, dynamic>>.from(response.data);
}Future<List<Map<String, dynamic>>> getCommunityPlugins() async {
final response = await _dio.get('/plugins/community');
return List<Map<String, dynamic>>.from(response.data);
}Plugin Model Structure
class StorePlugin {
final String id;
final String name;
final String author;
final String version;
final String description;
final String slot;
final Map<String, dynamic>? ui;
final String? authorAvatar;
final int downloadCount;
final bool hasUpdate;
final String latestVersion;
final bool isMine;
final bool isActive;
final bool isPublic;
}Testing Your Plugin
Local Testing
Developer Mode
Enable developer mode in plugin settings
Upload
Upload your plugin JSON file
Verify
Check that it appears in the correct slot
Test Actions
Click buttons and verify SDK calls work
Check Console
Look for debug messages in Flutter DevTools
Testing Checklist
Publishing to Marketplace
Prepare for Release
Create GitHub Repository
Create a Release
Submit to Marketplace
Best Practices
Keep It Simple
Plugins should do one thing well. Don't try to cram too much functionality into a single plugin.
Follow Naming Conventions
- Use descriptive, unique plugin IDs
- Follow semantic versioning (MAJOR.MINOR.PATCH)
- Keep names under 50 characters
Optimize Performance
- Keep UI hierarchies shallow
- Avoid excessive nesting
- Don't make plugins too large
Provide Good UX
- Use clear, actionable button labels
- Provide feedback for user actions
- Handle errors gracefully
Document Your Plugin
- Write a clear description
- Include usage instructions in GitHub README
- Add comments explaining complex logic
Troubleshooting
Plugin Doesn’t Load
- Verify JSON is valid — use a JSON validator
- Check that all required manifest fields are present
- Ensure the
“slot”value is valid
SDK Functions Don’t Work
- Verify the
“type”is set to“invenicum_sdk” - Check that the
“method”name is correct (case-sensitive) - Ensure
“params”is an object, not an array
UI Doesn’t Render Correctly
- Check STAC documentation for proper widget structure
- Verify property names match STAC specifications
- Test with simpler UI first, then add complexity