Custom Apps Development Guide
Introduction
Custom Apps in CoCore allow you to extend the platform with tailored functionality that directly leverages your print production data. Whether you need a simple dashboard widget, a specialized workflow tool, or a complete business application, Custom Apps provide the framework to build it without worrying about hosting, deployment, or infrastructure.
Think of Custom Apps as your personal development sandbox within CoCore - a place where your unique business logic meets your production data. Unlike traditional software development where you need to manage servers, databases, API integrations, and deployment pipelines, Custom Apps let you focus purely on solving business problems. The platform handles everything else.
The Power of Platform-Native Development
When you build a Custom App, you're not building an external system that needs to integrate with CoCore - you're building inside CoCore itself. This fundamental difference means:
- Your app has immediate access to all data through the same GraphQL API that powers the core platform
- Authentication and permissions are automatically handled using the existing user context
- Your app appears seamlessly within the CoCore interface, not as a bolted-on external tool
- Updates to the platform automatically benefit your custom apps (new APIs, improved performance, etc.)
Why Custom Apps?
Zero Infrastructure: No servers, deployment pipelines, or hosting costs. You write code, we handle everything else - hosting, scaling, security, backups, and availability.
Direct Data Access: Native GraphQL integration with all your CoCore data. Query jobs, orders, customers, inventory, and more without building a single API endpoint or managing authentication tokens.
Instant Deployment: Changes are live immediately. No CI/CD pipelines, no deployment windows, no downtime. Save your code and it's instantly available to your users.
Version Control: Built-in versioning for safe updates and rollbacks. Test new versions while keeping the stable version running. Roll back instantly if issues arise.
Secure by Default: Runs in sandboxed iframes with controlled permissions. Your code can't accidentally break the core platform, and the platform's security model protects your custom functionality.
Context Aware: Apps automatically receive context about where they're embedded. A widget on a job page knows which job it's viewing. An order dashboard knows the current order status. No complex state management needed.
Cost Effective: No additional hosting fees, no DevOps team needed, no infrastructure maintenance. Your subscription includes unlimited custom apps and executions.
Collaborative Development: Multiple team members can work on different apps simultaneously. Business analysts can create simple apps while developers build complex solutions.
The Traditional Development Nightmare vs. Custom Apps Reality
Without Custom Apps: The 6-Month Journey to Nowhere
Let's be honest about what building custom functionality typically looks like in the print industry. You identify a simple need - maybe tracking job profitability or creating a customer portal. Here's what traditionally happens:
Month 1-2: Planning and Architecture
- Research and select a technology stack (React? Angular? Vue? Next.js?)
- Design a database schema (PostgreSQL? MongoDB? MySQL?)
- Plan API architecture (REST? GraphQL? gRPC?)
- Set up authentication system (OAuth? JWT? SAML?)
- Design data synchronization strategy with your print system
- Write technical specifications
- Get budget approval for infrastructure
Month 3-4: Building the Foundation
- Set up development environment
- Configure build pipelines and deployment systems
- Implement authentication and authorization
- Build API endpoints for data access
- Create data synchronization jobs
- Handle error logging and monitoring
- Set up backup strategies
- Implement security measures
Month 5: Actually Building Your Feature
- Finally start on the actual business logic
- Realize your data model doesn't quite work
- Discover edge cases in the synchronization
- Find out the authentication doesn't handle all scenarios
- Debug production issues that don't happen locally
Month 6 and Beyond: The Maintenance Trap
- Keep servers updated and patched
- Monitor and fix data sync issues
- Handle scaling when usage grows
- Update dependencies for security vulnerabilities
- Train new developers on your custom architecture
- Document everything for the person who replaces you
The Result? You've spent 6 months and significant budget to build infrastructure, and barely touched the actual problem you were trying to solve. Your simple job profitability tracker has become a full IT project with ongoing maintenance costs.
With Custom Apps: The Same Day Solution
Now let's see the same scenario with Custom Apps:
Hour 1: Identify and Define
- Notice that job profitability tracking is inefficient
- Sketch out what information you need to display
- Identify which GraphQL queries will get your data
Hour 2-3: Build and Test
// Start building immediately - no setup required
const ProfitabilityTracker = {
template: `
<div class="profit-tracker">
<h2>Job Profitability Analysis</h2>
<div v-for="job in jobs" class="job-card">
<h3>{{ job.name }}</h3>
<div class="stats">
<span>Revenue: ${{ job.revenue }}</span>
<span>Cost: ${{ job.cost }}</span>
<span :class="profitClass(job.margin)">
Margin: {{ job.margin }}%
</span>
</div>
</div>
</div>
`,
// Your business logic here - not infrastructure code
}Hour 4: Deploy and Iterate
- Save your code - it's instantly live
- Get feedback from users immediately
- Make adjustments based on real usage
- Add features as needed
The Result? You solved the actual business problem in an afternoon. No servers to maintain, no security patches to apply, no data synchronization to debug. Just pure business value.
The Hidden Costs You Avoid
When you build traditional applications, you're not just building your feature. You're taking on:
Infrastructure Costs
- Server hosting: $100-500/month
- Database hosting: $50-200/month
- CDN and bandwidth: $50-100/month
- SSL certificates: $10-100/year
- Domain names: $15-50/year
Development Tool Costs
- CI/CD pipeline: $50-200/month
- Error tracking: $25-100/month
- Log management: $50-200/month
- Performance monitoring: $50-150/month
Human Costs
- DevOps time: 10-20 hours/month
- Security updates: 5-10 hours/month
- Bug fixes from integration issues: 20-40 hours/month
- Documentation and knowledge transfer: Immeasurable
Opportunity Costs
- Every hour spent on infrastructure is an hour not spent on business problems
- Every developer debugging deployment is not building features
- Every meeting about architecture is not talking to customers
Real-World Comparison
Traditional Approach:
- 6 developers
- 6 months
- $250,000 budget
- Result: A customer portal that needs constant maintenance
Custom Apps Approach:
- 1 developer
- 1 week
- $0 additional infrastructure
- Result: A customer portal that evolves with the platform
Focus on What Matters
The genius of Custom Apps isn't just that they're easier to build - it's that they let you focus entirely on solving business problems. You're not a software company; you're a print company. Your competitive advantage isn't in building better authentication systems or more efficient database queries. It's in understanding your customers, optimizing your workflows, and delivering exceptional print products.
Custom Apps let you be a print expert who happens to code, not a developer who happens to work in print.
Architecture Overview
Understanding the Custom Apps Ecosystem
Custom Apps operate within a sophisticated yet approachable architecture designed to balance power with simplicity. At its core, the system provides a secure runtime environment where your custom code can execute with full access to platform capabilities while maintaining strict isolation from the core system.
The architecture follows a micro-frontend pattern where each Custom App is essentially a self-contained application that can be composed into the larger CoCore platform. This means you can have dozens of custom apps running simultaneously, each serving a specific purpose, without them interfering with each other or the main platform.
App Types
Custom Apps support two primary development models, each optimized for different use cases and developer preferences:
1. Simple Apps - Rapid Development for Immediate Needs
Simple Apps are inline HTML/CSS/JavaScript applications perfect for quick solutions and prototypes. They're ideal when you need to:
- Create a quick dashboard widget in under an hour
- Build a custom report without setting up a development environment
- Prototype an idea before committing to a full application
- Allow non-developers to create basic functionality using templates
- Implement single-purpose tools that don't require complex state management
Example Use Cases:
- A custom price calculator for specific product types
- A visual job status board for the production floor
- A customer satisfaction survey embedded in order confirmations
- Quick data transformation tools for imports/exports
2. External Apps - Professional Applications with Modern Tooling
External Apps are full-featured applications with build processes, bundling, and modern development workflows. Choose this approach when you need to:
- Build complex, multi-page applications with routing
- Use TypeScript for type safety and better developer experience
- Implement sophisticated state management across multiple components
- Leverage npm packages and third-party libraries
- Create reusable component libraries for multiple apps
- Implement comprehensive testing strategies
Example Use Cases:
- Complete CRM replacement tailored to your business model
- Advanced scheduling system with drag-and-drop and AI optimization
- Multi-step workflow automation builders with visual programming
- Complex financial dashboards with real-time analytics
Embedding Contexts
Apps can be embedded in various locations throughout CoCore, with each context providing specific data and capabilities:
- Page Apps (
page) - Standalone full-page applications - Dashboard Widgets (
dashboard) - Compact dashboard components - Job Context (
job) - Tools embedded in job management screens - Order Context (
order) - Order-specific functionality - Customer Context (
customer) - Customer relationship tools - Estimate Context (
estimate) - Estimation and quoting tools - Shipment Context (
shipment) - Shipping and logistics widgets - Schedule View (
schedule) - Production scheduling integrations - Kiosk Mode (
kiosk) - Customer-facing or shop floor displays - Overview Panels - Specialized views for different departments:
- CRM Overview (
crm_overview) - Finance Overview (
finance_overview) - Production Overview (
production_overview) - Materials Overview (
materials_overview) - Planning Overview (
planning_overview) - Fulfillment Overview (
fulfillment_overview)
- CRM Overview (
Getting Started
Your First Custom App in 5 Minutes
The beauty of Custom Apps is that you can start creating value immediately, without any setup or configuration. Let's walk through creating your first app - a simple tool that shows how many jobs are currently in production.
Quick Start with Simple Apps
The fastest way to create a Custom App is using the Simple App type with Vue.js. Simple Apps are perfect for getting started because they require zero tooling - just write your code directly in the CoCore interface and it runs immediately.
Here's the complete configuration for a working dashboard widget:
// App Configuration - This goes in the CoCore Custom Apps interface
{
"name": "Production Status Monitor",
"handle": "production-monitor",
"version": 1,
"kind": "dashboard",
"config": {
"__typename": "CustomAppSimpleType",
"template": `
<div id='app' class='p-4'>
<div class='text-2xl font-bold mb-2'>
Jobs in Production: {{ activeJobs }}
</div>
<div class='text-sm text-gray-600'>
Last updated: {{ lastUpdate }}
</div>
</div>
`,
"script": `
const { createApp } = Vue;
createApp({
data() {
return {
activeJobs: 0,
lastUpdate: new Date().toLocaleTimeString()
};
},
mounted() {
// Fetch active jobs when component loads
this.fetchActiveJobs();
// Update every 30 seconds
setInterval(this.fetchActiveJobs, 30000);
},
methods: {
async fetchActiveJobs() {
// This is where you'd add the GraphQL query
// For now, using mock data
this.activeJobs = Math.floor(Math.random() * 20);
this.lastUpdate = new Date().toLocaleTimeString();
}
}
}).mount('#app');
`,
"styles": `
/* Optional custom styles */
#app {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
}
`
}
}This simple example demonstrates several key concepts:
- Immediate Execution: The code runs as soon as you save it
- Vue.js Integration: Full Vue 3 framework available without any imports
- Styling Freedom: Use inline styles, Tailwind classes, or DaisyUI components
- Live Updates: The widget updates automatically every 30 seconds
From Concept to Production: Building a Real Order Status Widget
Let's build something more substantial - a production-ready dashboard widget that displays real-time order statistics. This example will demonstrate how to connect to the GraphQL API, handle loading states, and create an engaging user interface.
The Business Need
Imagine your sales team needs to see at a glance:
- How many orders came in today
- Total revenue for the day
- Comparison with yesterday's performance
- Which orders need immediate attention
Instead of building a separate application or relying on static reports, we'll create a live dashboard widget that updates throughout the day.
<!-- Template -->
<div id="sales-widget" class="p-4">
<div class="stats shadow">
<div class="stat">
<div class="stat-title">Today's Orders</div>
<div class="stat-value">{{ todayOrders }}</div>
<div class="stat-desc">↗︎ {{ percentChange }}% from yesterday</div>
</div>
<div class="stat">
<div class="stat-title">Revenue</div>
<div class="stat-value">${{ revenue }}</div>
<div class="stat-desc">{{ orderCount }} orders</div>
</div>
</div>
</div>// Script
const { createApp } = Vue;
const { createClient, useQuery } = Villus;
// GraphQL client setup
const client = createClient({
url: window.APP_CONTEXT.graphqlURL,
context: () => ({
headers: {
Authorization: `Bearer ${window.APP_CONTEXT.apiToken}`
}
})
});
// Create Vue app
createApp({
setup() {
const today = new Date().toISOString().split('T')[0];
const { data, isFetching } = useQuery({
query: `
query DashboardStats($date: Date!) {
orders(filter: { createdAt: { greaterThanOrEqual: $date } }) {
nodes {
id
totalAmount
status
}
}
}
`,
variables: { date: today }
});
return {
todayOrders: Vue.computed(() => data.value?.orders?.nodes?.length || 0),
revenue: Vue.computed(() => {
const orders = data.value?.orders?.nodes || [];
return orders.reduce((sum, order) => sum + order.totalAmount, 0).toFixed(2);
}),
orderCount: Vue.computed(() => data.value?.orders?.nodes?.length || 0),
percentChange: Vue.computed(() => {
// Calculate percentage change logic
return 12.5; // Example value
})
};
}
}).use(client).mount('#sales-widget');Building Full Applications
When to Graduate from Simple Apps
Simple Apps are powerful, but as your requirements grow, you'll want the benefits of a full development environment:
- Type Safety: TypeScript catches errors before runtime
- Component Reusability: Build once, use everywhere
- Version Control: Git integration for team collaboration
- Testing: Unit and integration tests for reliability
- Build Optimization: Smaller bundles, faster load times
- Developer Tools: Hot reload, debugging, linting
Using create-cococo-app
The create-cococo-app tool scaffolds a complete development environment in seconds, configured with best practices and ready for immediate development. It's inspired by tools like Create React App but tailored specifically for the CoCore platform:
# Install the CLI
npm install -g @cococo/create-cococo-app
# Create a new app
create-cococo-app my-custom-app
# Choose your framework
? Select a framework:
❯ Vue 3 (Recommended)
React
Svelte
Vanilla JavaScript
# Development
cd my-custom-app
npm run dev
# Build for production
npm run buildProject Structure Explained
Understanding the project structure helps you organize code effectively as your app grows. Each directory has a specific purpose designed to keep your codebase maintainable:
my-custom-app/
├── src/
│ ├── main.js # App entry point
│ ├── App.vue # Root component
│ ├── components/ # Reusable components
│ ├── views/ # Page components
│ ├── stores/ # State management (Pinia)
│ ├── graphql/ # GraphQL queries/mutations
│ └── utils/ # Helper functions
├── public/
│ └── assets/ # Static assets
├── package.json
├── vite.config.js # Build configuration
└── cococo.config.js # Custom App configurationDevelopment Workflow
The development workflow is designed to feel familiar to modern web developers while providing CoCore-specific conveniences:
1. Local Development with Hot Reload
Start your development server with instant feedback on changes:
npm run dev
# Runs on http://localhost:3000
# Hot module replacement enabled
# Source maps for debugging
# Automatic error overlayYour app runs locally but can still access the production GraphQL API, giving you real data to work with during development.
2. Configure Dev Mode in CoCore
Tell CoCore to load your app from your local development server instead of the production bundle:
{
"config": {
"__typename": "CustomAppExternalType",
"devMode": true,
"devUrl": "http://localhost:3000",
// Dev mode only works for your user account
// Other users continue to see the production version
}
}This brilliant feature means you can test changes in the actual production environment without affecting other users. See exactly how your app will behave with real data, real permissions, and real UI context.
3. Build and Deploy
When you're ready to ship, create an optimized production bundle:
npm run build
# Generates dist/bundle.js
# Minified and tree-shaken
# Source maps removed
# Console logs stripped
# Upload bundle to Custom App configuration
# Users immediately see the new version
# Previous version saved for instant rollback if neededThe entire deployment process takes under a minute, with zero downtime.
API Integration
GraphQL Client Setup
Custom Apps have full access to the CoCore GraphQL API:
import { createClient, defaultPlugins } from 'villus';
const client = createClient({
url: window.APP_CONTEXT.graphqlURL,
use: [
...defaultPlugins(),
// Add authentication
{
onOperation({ opContext }) {
opContext.headers = {
...opContext.headers,
Authorization: `Bearer ${window.APP_CONTEXT.apiToken}`
};
}
}
]
});Common Queries
Fetch Jobs
query GetJobs($status: JobStateEnum) {
jobs(filter: { state: { eq: $status } }) {
nodes {
id
name
dueDate
state
customer {
name
}
operations {
nodes {
name
state
completedAt
}
}
}
}
}Update Order Status
mutation UpdateOrderStatus($id: ID!, $status: OrderStateEnum!) {
updateOrder(id: $id, input: { state: $status }) {
result {
id
state
updatedAt
}
}
}Subscribe to Changes
subscription JobUpdates($jobId: ID!) {
jobUpdated(id: $jobId) {
id
state
operations {
nodes {
id
state
progress
}
}
}
}Context-Aware Apps
Accessing Context Data
Apps automatically receive context about their environment:
// Available in window.APP_CONTEXT
{
graphqlURL: "https://your-instance.cococo.com/graphql",
resource: {
type: "job", // Current resource type
id: "job_123" // Current resource ID
},
embedded: true, // Whether app is embedded
appInfo: {
id: "app_456",
handle: "job-analyzer",
name: "Job Analyzer"
}
}Resource-Specific Apps
Build apps that adapt to their context:
// Job-specific app
const JobAnalyzer = {
setup() {
const jobId = window.APP_CONTEXT.resource?.id;
if (!jobId) {
return { error: "No job context available" };
}
const { data } = useQuery({
query: JOB_DETAILS_QUERY,
variables: { id: jobId }
});
// Job-specific logic here
return {
job: data,
// ... computed properties
};
}
};UI Components and Styling
Using DaisyUI
All Custom Apps have access to DaisyUI components:
<!-- Button examples -->
<button class="btn btn-primary">Print Job</button>
<button class="btn btn-ghost btn-sm">
<svg><!-- icon --></svg>
Settings
</button>
<!-- Card component -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">Job #12345</h2>
<p>Ready for production</p>
<div class="card-actions justify-end">
<button class="btn btn-primary">Start Production</button>
</div>
</div>
</div>
<!-- Stats display -->
<div class="stats stats-vertical lg:stats-horizontal shadow">
<div class="stat">
<div class="stat-title">Jobs Today</div>
<div class="stat-value">31</div>
<div class="stat-desc">Jan 1st - Feb 1st</div>
</div>
</div>Custom Styling
Apps can include custom styles while maintaining brand consistency:
/* Use CSS variables for theming */
.custom-widget {
background: oklch(var(--color-base-200));
color: oklch(var(--color-base-content));
border-radius: var(--rounded-box);
}
/* Responsive design */
@media (max-width: 768px) {
.custom-widget {
padding: 1rem;
}
}State Management
Local State with Vue Composition API
import { ref, computed, watch } from 'vue';
export function useOrderManager() {
const orders = ref([]);
const filter = ref('all');
const filteredOrders = computed(() => {
if (filter.value === 'all') return orders.value;
return orders.value.filter(o => o.status === filter.value);
});
const loadOrders = async () => {
const result = await client.query({
query: ORDERS_QUERY,
variables: { filter: filter.value }
});
orders.value = result.data.orders.nodes;
};
watch(filter, loadOrders);
return {
orders: filteredOrders,
filter,
loadOrders
};
}Global State with Pinia
import { defineStore } from 'pinia';
export const useAppStore = defineStore('app', {
state: () => ({
user: null,
settings: {},
notifications: []
}),
getters: {
isAuthenticated: (state) => !!state.user,
unreadCount: (state) => state.notifications.filter(n => !n.read).length
},
actions: {
async loadUserSettings() {
const { data } = await client.query({
query: USER_SETTINGS_QUERY
});
this.settings = data.settings;
}
}
});Advanced Features
Real-time Updates
Implement WebSocket subscriptions for live data:
import { useSubscription } from 'villus';
export default {
setup() {
const { data } = useSubscription({
query: `
subscription JobUpdates {
jobStatusChanged {
id
state
updatedAt
}
}
`
});
watch(data, (newData) => {
// Handle real-time updates
console.log('Job updated:', newData.jobStatusChanged);
});
return { data };
}
};File Uploads
Handle file uploads for job assets:
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('type', 'job_asset');
const response = await fetch('/api/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${window.APP_CONTEXT.apiToken}`
},
body: formData
});
const result = await response.json();
return result.fileId;
}Print Preview Integration
Generate and display print previews:
const PreviewComponent = {
template: `
<div class="preview-container">
<iframe
v-if="previewUrl"
:src="previewUrl"
class="w-full h-full"
/>
<div v-else class="loading">
Generating preview...
</div>
</div>
`,
setup() {
const previewUrl = ref(null);
const generatePreview = async (jobId) => {
const { data } = await client.query({
query: GENERATE_PREVIEW_MUTATION,
variables: { jobId }
});
previewUrl.value = data.generatePreview.url;
};
return { previewUrl, generatePreview };
}
};Security Considerations
Sandboxed Execution
Custom Apps run in sandboxed iframes with controlled permissions:
// Iframe sandbox attributes
sandbox="allow-scripts allow-forms allow-modals allow-popups allow-same-origin"API Token Management
- Tokens are injected at runtime, never stored in code
- Tokens have scoped permissions based on user role
- Automatic token refresh handled by the platform
Content Security Policy
Apps must comply with CSP restrictions:
// Allowed
import { library } from 'approved-cdn';
// Blocked - inline event handlers
<button onclick="doSomething()"> // ❌ Not allowed
// Use event listeners instead
<button @click="doSomething"> // ✅ CorrectPerformance Optimization
Code Splitting
// Lazy load heavy components
const HeavyChart = () => import('./components/HeavyChart.vue');
export default {
components: {
HeavyChart: defineAsyncComponent(HeavyChart)
}
};Query Optimization
// Use fragments for reusable query parts
const JOB_FRAGMENT = `
fragment JobDetails on Job {
id
name
state
dueDate
}
`;
// Paginate large datasets
const PAGINATED_QUERY = `
query GetOrders($offset: Int!, $limit: Int!) {
orders(offset: $offset, limit: $limit) {
nodes { ...OrderDetails }
pageInfo {
hasNextPage
totalCount
}
}
}
`;Caching Strategies
const client = createClient({
url: window.APP_CONTEXT.graphqlURL,
cachePolicy: 'cache-and-network',
use: [
cache({
// Cache for 5 minutes
ttl: 5 * 60 * 1000,
// Custom cache key
getCacheKey: (query, variables) => {
return `${query.id}-${JSON.stringify(variables)}`;
}
})
]
});Testing
Unit Testing
import { mount } from '@vue/test-utils';
import OrderWidget from './OrderWidget.vue';
describe('OrderWidget', () => {
it('displays order count', async () => {
const wrapper = mount(OrderWidget, {
props: {
orders: [
{ id: 1, status: 'pending' },
{ id: 2, status: 'complete' }
]
}
});
expect(wrapper.find('.order-count').text()).toBe('2');
});
});Integration Testing
import { createTestClient } from 'villus';
const mockClient = createTestClient({
responses: {
GetOrders: {
data: {
orders: {
nodes: [/* mock data */]
}
}
}
}
});Deployment
Version Management
// cococo.config.js
export default {
name: "Advanced Order Manager",
handle: "order-manager",
version: 2, // Increment for updates
kind: "page",
changelog: {
2: "Added bulk operations and export features",
1: "Initial release"
}
};Bundle Optimization
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'villus'],
utils: ['lodash', 'date-fns']
}
}
},
minify: 'terser',
terserOptions: {
compress: {
drop_console: true
}
}
}
};Deployment Checklist
- [ ] Test in development mode
- [ ] Build production bundle
- [ ] Verify bundle size (<500KB recommended)
- [ ] Test all GraphQL queries/mutations
- [ ] Verify responsive design
- [ ] Check error handling
- [ ] Update version number
- [ ] Document changes in changelog
- [ ] Upload bundle to Custom App configuration
- [ ] Test in production environment
Common Use Cases
1. Custom Reports Dashboard
Build specialized reporting tools that aggregate data across jobs, orders, and production metrics.
2. Customer Portal
Create customer-facing interfaces for order tracking, reordering, and artwork approval.
3. Production Floor Display
Develop kiosk applications for shop floor workers showing current jobs, schedules, and priorities.
4. Workflow Automation Tools
Build interfaces for complex automation rules, triggers, and batch operations.
5. Integration Connectors
Create custom connectors to external systems like CRM, ERP, or accounting software.
6. Quality Control Checklists
Implement digital checklists and inspection forms with photo capture and annotations.
7. Pricing Calculators
Build sophisticated pricing tools with custom rules, matrices, and instant quotes.
8. Inventory Management
Extend inventory tracking with custom allocation rules, reorder points, and supplier integration.
Best Practices
- Start Simple: Begin with Simple Apps for prototypes, then migrate to External Apps as complexity grows
- Use TypeScript: Add type safety to catch errors early
- Handle Errors Gracefully: Always provide fallback UI for loading and error states
- Optimize Queries: Request only needed fields, use fragments for reusability
- Follow Vue Style Guide: Use single-file components, composition API for complex logic
- Version Carefully: Increment versions for breaking changes, maintain backwards compatibility
- Document Your Code: Include README, inline comments, and API documentation
- Test Thoroughly: Unit test components, integration test API calls
- Monitor Performance: Use browser DevTools, lighthouse audits
- Secure by Design: Never hardcode credentials, validate all inputs
Troubleshooting
Common Issues
App Not Loading
- Check browser console for errors
- Verify API token is valid
- Ensure GraphQL endpoint is accessible
- Check Content Security Policy violations
GraphQL Errors
- Use GraphQL playground to test queries
- Verify field names and types match schema
- Check permission requirements
Performance Issues
- Profile with browser DevTools
- Reduce bundle size
- Implement pagination for large datasets
- Add appropriate caching
Styling Conflicts
- Use scoped styles or CSS modules
- Prefix custom classes
- Leverage DaisyUI utilities
Resources
Documentation
Templates and Examples
Support
- Community Forum: community.cococo.com
- Developer Discord: discord.gg/cococo
- Support Email: developers@cococo.com
Conclusion
Custom Apps transform CoCore from a print management system into a complete business platform tailored to your unique needs. Whether you're building a simple widget or a complex application, the framework provides everything needed to create, deploy, and maintain custom functionality without infrastructure overhead.
Start with a simple dashboard widget, experiment with the GraphQL API, and gradually build more sophisticated applications as your needs evolve. The platform handles the complexity of hosting, security, and integration, letting you focus on creating value for your print business.