Use when creating, modifying, debugging, or verifying FOSSBilling themes, Twig templates, cart & checkout workflows, ticket and invoice systems, API forms, or strict-mode Twig runtime behavior. Excludes core FOSSBilling PHP backend development or server infrastructure management.
86%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
This guide establishes architecture, design rules, Twig conventions, API integration patterns, and verification protocols for engineering production-ready themes in FOSSBilling.
FOSSBilling themes operate on a multi-tier rendering architecture:
strict_variables => true in production).Box_App / Slim): Routes are registered on separate roles:
admin (/admin/...)client (/login, /support, /order, etc.)guest (/, /order, /support/contact-us, /kb, etc.)api (/api/guest/..., /api/client/..., /api/admin/...)themes/<theme_name>/html/.modules/<Module>/templates/client/ or themes/huraga/html/.themes/<theme_name>/assets/.Base layouts (layout_default.html.twig, layout_public.html.twig, layout_blank.html.twig) establish the structural contract for all module templates. If a layout fails to declare a block, Twig silently drops content that child templates place inside that block—without raising a compile-time or runtime error.
Every master layout template must provide:
<!DOCTYPE html>
<html lang="{{ guest.system_locale|default('en')|replace({'_': '-'}) }}">
<head>
<meta charset="utf-8">
<title>{% block meta_title %}{% endblock %} - {{ company.name|default('Client Portal') }}</title>
{# Essential for OpenGraph and social cards #}
{% block opengraph %}{% endblock %}
{# Essential for page-specific CSS or WYSIWYG assets #}
{% block head %}{% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}">
{# Main view content #}
{% block content_before %}{% endblock %}
{% block content %}{% endblock %}
{% block content_after %}{% endblock %}
{# Global scripts #}
{{ 'js/fossbilling.js'|public_asset_url|script_tag }}
<script src="{{ 'assets/js/theme.js'|asset_url }}"></script>
{# MANDATORY: Never omit this block. Child templates inject forms, modals, and API logic here. #}
{% block js %}{% endblock %}
{# Hook for FOSSBilling extension widgets #}
{{ render_widgets('client.theme.body.end') }}
</body>
</html>[!CAUTION] The Silent Script Drop Failure: If
layout_default.html.twigomits{% block js %}{% endblock %}, templates likemod_support_tickets,mod_order_manage, andmod_support_tickethave their form submit handlers silently discarded. This leads to native form submissions that result in fatal 404s.
FOSSBilling can execute with strict_variables => true. In this mode, accessing a missing key or attribute halts rendering immediately:
Key "X" for sequence/mapping does not exist in "<template>.html.twig" at line Y.register_sld, register_tld, period, but do not have description.config and pricing, but may lack stock_control.|default:
{# BAD - crashes if description is missing in strict mode #}
<p>{{ item.description }}</p>
{# GOOD #}
<p>{{ item.description|default('') }}</p>?? (Null Coalescing) from |default:
?? and |default safely guard against undefined keys in Twig strict mode without fatal errors.?? when preserving valid false booleans or 0 matters (e.g., product.stock_control ?? false, settings.show_sidebar ?? true). Because |default checks empty(), false|default(true) mistakenly evaluates to true!|default when empty strings or missing collections should trigger fallback (e.g., item.description|default(''), items|default([])).Key "..." does not exist) occurs when keys are accessed directly without either ?? or |default (e.g., {{ item.description }} or {% if product.stock_control %}).| Variable | Safe Twig Usage | Notes |
|---|---|---|
| Cart item description | item.description|default('') | Absent on domain items; replaces empty strings |
| Product stock control | product.stock_control ?? false | Preserves valid false boolean; |default(true) would invert false |
| Product addons | product.addons ?? [] | Absent if addons disabled |
| Currency format | item.total|currency(item.currency ?? guest.cart_get_currency.code) | Preserves defined currency code |
| Request parameters | request.param_name|default('') | Guards absent request parameters |
| Client logged check | client ? true : false | Check client object before client.email |
FOSSBilling controllers strictly separate HTTP methods:
modules/Support/Controller/Client.php) only register $app->get(...)./api/client/..., /api/guest/...).If a form is written as:
<!-- FATAL FLAW -->
<form id="ticket-submit" method="post">When JavaScript fails to intercept the submit (due to an error, missing block, or network glitch), the browser falls back to native submission:
POST /support
Because FOSSBilling has no post('/support') route, it halts with:
404 Page /support not found
Always define an explicit API endpoint in action:
<form id="ticket-submit" method="post" action="{{ 'support/ticket_create'|api_url }}">
<input type="hidden" name="CSRFToken" value="{{ CSRFToken }}">
<!-- Form fields -->
<button type="submit" class="theme-btn theme-btn-primary" id="ticket-submit-btn">
{{ 'Submit Ticket'|trans }}
</button>
</form>
{% block js %}
<script>
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById('ticket-submit');
const submitBtn = document.getElementById('ticket-submit-btn') || form?.querySelector('button[type="submit"]');
if (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span><span>{{ "Submitting..."|trans }}</span>';
}
FOSSBilling.api.client.post(
'support/ticket_create',
FOSSBilling.tools.serializeFormDataToObject(new FormData(form)),
function (res) {
window.location = "{{ 'support/ticket'|url }}/" + res;
},
function (err) {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = `{{ 'Submit Ticket'|trans }}`;
}
FOSSBilling.message(err.message, 'error');
}
);
});
}
});
</script>
{% endblock %}FOSSBilling includes an automated anti-spam honeypot on guest forms (such as mod_support_contact_us):
{{ mf.recaptcha }} renders a hidden #anti-spam field.document.getElementById('anti-spam')?.remove() must be executed on DOM load. If bot-protection JS is stripped, submissions fail validation.When saving profile changes, updating orders, or closing tickets, user actions cause page reloads or redirects. Themes must persist and display these state transitions.
theme.js)Ensure assets/js/theme.js implements globalThis.flashMessage:
// Flash message handler for cross-page notifications
globalThis.flashMessage = function (options) {
options = options || {};
var message = options.message || '';
var reload = options.reload || false;
var type = options.type || 'info';
var key = 'flash-message';
if (!message) {
try {
var stored = sessionStorage.getItem(key);
if (stored) {
if (window.FOSSBilling && typeof window.FOSSBilling.message === 'function') {
window.FOSSBilling.message(stored, type);
}
sessionStorage.removeItem(key);
}
} catch (e) {}
return;
}
try {
sessionStorage.setItem(key, message);
} catch (e) {}
if (reload === true) {
window.location.reload();
} else if (typeof reload === 'string' && reload) {
window.location.assign(reload);
}
};
// Always trigger on page load to consume any queued flash message
try {
globalThis.flashMessage({});
} catch (e) {}Never declare a template fix complete based solely on curl or static analysis.
python3 AGENTARDS/scripts/validate_theme.py (or local validator).{ result: ..., error: null }.54a73d7
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.