App Structure
Apps extend O2VEND functionality by injecting code at specific hook points. This guide covers the app structure and organization.
App Overview
Apps allow you to:
- Add custom functionality to themes
- Create payment gateways
- Integrate third-party services
- Hook into theme rendering at specific points
App Location
Apps are stored in the tenant repository:
{tenant-repository}/apps/{app-id}/
For local development, apps can be placed in:
apps/{app-id}/
App Structure
An app consists of the following files:
{app-id}/
app.json # App metadata (required)
snippets/ # Liquid snippets for hooks (required)
{hook-name}.liquid
assets/ # Optional CSS/JS assets
app.css
app.js
gateway.js # Payment gateway implementation (for payment apps)
App Metadata (app.json)
The app.json file defines app metadata and configuration:
{
"id": "analytics-app",
"name": "Google Analytics",
"version": "1.0.0",
"description": "Adds Google Analytics tracking to the store",
"author": "Your Name",
"hooks": ["theme_head", "theme_body_end"],
"assets": ["app.css", "app.js"],
"priority": 100,
"settings": {
"tracking_id": {
"type": "text",
"label": "GA4 Measurement ID",
"default": ""
}
}
}
Required Fields
id(string): Unique app identifier (e.g., "analytics-app")name(string): Display name (e.g., "Google Analytics")version(string): Semantic version (e.g., "1.0.0")
Optional Fields
description(string): App descriptionauthor(string): Author or organization namehooks(array): Array of hook names this app usesassets(array): Array of asset filenames (CSS, JS)priority(number): Execution priority (lower = higher priority, default: 100)settings(object): App settings schemapayment_gateway(object): Payment gateway configuration (for payment apps)
Hook Snippets
Each hook the app uses requires a corresponding Liquid snippet file:
snippets/
theme_head.liquid
theme_body_end.liquid
product_price_after.liquid
Hook Snippet Example
{% comment %}
Hook: theme_head
App: analytics-app
{% endcomment %}
{% if settings.tracking_id %}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ settings.tracking_id }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ settings.tracking_id }}');
</script>
{% endif %}
App Assets
Assets (CSS, JavaScript) are stored in the assets/ directory:
assets/
app.css
app.js
images/
logo.png
Asset URLs
Assets are served via:
/apps/{tenant-id}/{app-id}/assets/{asset-name}
Example:
/apps/tenant123/analytics-app/assets/app.js
Referencing Assets
In hook snippets, reference assets using the app asset URL:
<link rel="stylesheet" href="/apps/{{ tenant.id }}/{{ app.id }}/assets/app.css">
<script src="/apps/{{ tenant.id }}/{{ app.id }}/assets/app.js"></script>
Payment Gateway Apps
Payment gateway plugins require a gateway.js file that exports a gateway implementation:
// gateway.js
module.exports = {
/**
* Initialize the payment gateway
* @param {Object} config - Gateway configuration
* @returns {Object} Gateway instance
*/
initialize(config) {
return {
name: 'Razorpay',
processPayment: async (paymentData) => {
// Payment processing logic
},
verifyPayment: async (paymentId) => {
// Payment verification logic
}
};
}
};
App Discovery
Apps are discovered via:
- API Endpoint (primary):
GET /apps/enabled- Returns array of enabled app IDs - Tenant Repository Scan (fallback): Scans tenant repository apps directory
An app is considered enabled if:
- Listed in API response, OR
- Present in tenant repository apps directory
App Context
Apps have access to the full Liquid context, including:
shop- Store informationproduct- Current product (on product pages)cart- Shopping cart datapage- Current page informationtenant- Tenant configurationsettings- App-specific settings- All other theme variables
Accessing App Settings
{% if settings.tracking_id %}
<!-- Use setting -->
{% endif %}
App Priority
The priority field controls execution order when multiple apps use the same hook:
- Lower priority = executes first
- Default priority = 100
- Higher priority = executes last
Example:
{
"id": "critical-app",
"priority": 10, // Executes first
"hooks": ["theme_head"]
}
Example App
Complete Analytics App
app.json:
{
"id": "google-analytics",
"name": "Google Analytics",
"version": "1.0.0",
"description": "Adds Google Analytics 4 tracking",
"hooks": ["theme_head", "theme_body_end"],
"assets": [],
"priority": 50,
"settings": {
"tracking_id": {
"type": "text",
"label": "GA4 Measurement ID",
"default": ""
},
"anonymize_ip": {
"type": "checkbox",
"label": "Anonymize IP",
"default": true
}
}
}
snippets/theme_head.liquid:
{% if settings.tracking_id %}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ settings.tracking_id }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ settings.tracking_id }}', {
{% if settings.anonymize_ip %}anonymize_ip: true{% endif %}
});
</script>
{% endif %}
snippets/theme_body_end.liquid:
{% if settings.tracking_id %}
<script>
gtag('event', 'page_view', {
'page_title': '{{ page.title | default: shop.name }}',
'page_location': window.location.href
});
</script>
{% endif %}
Local Development
For local development:
- Place app in
/apps/{app-id}/directory - Use the same structure as tenant repository apps
- System will fallback to local apps if tenant repository is unavailable
Best Practices
- Unique IDs: Use descriptive, unique app IDs
- Versioning: Follow semantic versioning (major.minor.patch)
- Error Handling: Handle errors gracefully - app errors shouldn't break the theme
- Performance: Keep app snippets lightweight
- Documentation: Document app settings and usage
- Testing: Test apps with different themes and configurations
- Priority: Use priority to control execution order
- Settings: Provide sensible defaults for all settings
Next Steps
- Hooks - Learn about available hooks
- Payment Gateways - Create payment apps
- Assets - Manage app assets