Skip to content

GoatFlow 0.8.0: The Platform Release

In 0.7.0 we built a plugin sandbox and told you it was safe. In 0.8.0 we built everything plugins actually need to build real applications.

GoatFlow Plugin ADmin Interface

This is the release where GoatFlow stops being a ticketing system with a plugin API and starts being an application platform. Plugins can now declare custom fields, render their own UIs, store encrypted secrets, scope data by organisation, and get listed in a marketplace. And we shipped the whole thing five months ahead of schedule.

  ┌─────────────────────────────────────────────┐
  │            GoatKit PaaS Core                │
  │                                             │
  │  Plugin                                     │
  │  ┌───────────┐  ┌──────────┐  ┌──────────┐  │
  │  │ Custom    │  │ Plugin   │  │ Secure   │  │
  │  │ Fields    │  │ UIs      │  │ Settings │  │
  │  └─────┬─────┘  └────┬─────┘  └────┬─────┘  │
  │        │             │             │        │
  │        ▼             ▼             ▼        │
  │  ┌─────────────────────────────────────┐    │
  │  │         Organisations               │    │
  │  │  per-org config · query scoping     │    │
  │  │  org switcher · membership          │    │
  │  └─────────────────────────────────────┘    │
  │        │             │             │        │
  │        ▼             ▼             ▼        │
  │  ┌───────────┐  ┌──────────┐  ┌──────────┐  │
  │  │ Entity    │  │ Self-    │  │ Market-  │  │
  │  │ Deletion  │  │ Service  │  │ place    │  │
  │  │ GDPR      │  │ Auth     │  │ gk CLI   │  │
  │  └───────────┘  └──────────┘  └──────────┘  │
  └─────────────────────────────────────────────┘

Custom Fields

The headline feature. Plugins declare custom fields on any entity — tickets, contacts, agents, queues, organisations — and the platform handles everything: storage, validation, UI rendering, and querying. No more extension tables.

Fifteen field types ship out of the box, including three GIS types for location-aware applications:

TypeWhat it stores
text, textarea, integer, decimal, booleanThe basics
date, datetimeTemporal data
select, multi_selectControlled vocabularies
url, email, phoneContact information
point (lat/lng)Map coordinates with bounding-box queries
polygon (GeoJSON)Geographic boundaries
addressStructured addresses with auto-geocode

Plugins register fields in their GKRegistration manifest. Names are auto-prefixed to prevent collisions. The platform creates them in the database at load time, validates values server-side (including regex with timeout protection against ReDoS), and renders them automatically on entity pages.

Admins can also create custom fields directly — no plugin required. The admin UI supports the same 15 types.

Legacy OTRS dynamic_field entries migrate automatically on startup. It’s a copy, not a move — downgrade safely if you need to.

Plugin UI System

Plugins can now declare independent UIs: customer-facing apps, agent tools, admin panels, public status pages, kiosks. Each UI gets its own routes, branding, navigation, and optional PWA manifest.

Three shell types control how much GoatFlow chrome appears:

  • Standard — full nav bar, theme support, same look as native pages
  • Minimal — slim header, optional bottom/top/side navigation, mobile-first
  • None — raw HTML, plugin controls the entire page

Routes are namespaced under /ui/{plugin}_{ui_id}/ — no wildcard conflicts with existing endpoints. (We learned that lesson the hard way with a /:entity_type/:id/custom-fields route that ate our ticket API. Lesson noted, pattern banned.)

PWA manifests generate automatically at /ui/{id}/manifest.json. Users can install plugin apps to their phone home screen.

Organisations & Multi-Tenancy

The gk_organisation entity provides hierarchical organisations with user membership (agents AND customers), per-org sysconfig overrides, and automatic HostAPI query scoping.

The key design decision: per-org settings extend the existing sysconfig system rather than introducing a JSON blob. ConfigGet("max_attachments") resolves through a four-tier cascade — user preference, org override, system override, system default — and plugins don’t need to know about any of it.

Query scoping is automatic. When a plugin calls DBQuery("SELECT * FROM ticket WHERE queue_id = ?"), the sandbox transparently appends AND org_id = ? for org-aware tables. Plugins get tenant isolation for free.

Secure Settings

AES-256-GCM encrypted key-value storage via HostAPI. Plugins store API keys, webhook secrets, and device PINs without touching cryptography. The platform manages the encryption key, storage, and masked admin display (last 4 characters only).

Secrets are org-scoped — different API keys for different organisations, with automatic fallback to global values.

Entity Deletion

Two deletion patterns, unified across all entity types:

Soft delete moves entities to a recycle bin, anonymises PII fields with [DELETED] (irreversible — this is GDPR-grade), and notifies plugins via cascade handlers. Configurable retention periods auto-purge expired entries.

Hard delete physically removes entities and all linked data. Requires the entity.hard_delete RBAC permission (admin only). Tombstone logging records that a deletion happened without recording what was deleted.

The recycle bin admin UI lets admins browse deleted items, restore them, or purge permanently. Plugin cascade handlers (CascadeSpec in GKRegistration) get called on both soft and hard delete — so plugin data stays in sync.

Plugin Marketplace

gk install inventory — that’s the workflow. The marketplace uses GitHub Releases as the distribution backend with a curated JSON index. No infrastructure to maintain, zero hosting cost.

The gk CLI gained three commands: install, update, and search. Dependency resolution ensures plugins are loaded in the right order. Circular dependency detection catches problems at install time, not at 3am.

Theme-as-plugin support means community themes ship as plugin ZIPs — install them the same way you install any plugin.

For Kubernetes deployments, GOATFLOW_PLUGIN_ISOLATION=k8s generates Deployment + Service + NetworkPolicy YAML for each gRPC plugin, running them as isolated pods instead of local processes.

Self-Service Authentication

Customer-facing self-service: password recovery (email-based reset tokens with anti-enumeration), customer self-registration with admin approval workflow, email verification, and CAPTCHA integration (reCAPTCHA v3 and hCaptcha).

The registration flow: customer submits form, receives verification email, admin reviews and approves/rejects. No accounts appear until explicitly approved.

Reusable UI Components

Seven server-rendered components usable by any plugin:

ComponentWhat it does
gk-daily-queueTask list with priority indicators and action buttons
gk-week-calendarWeek grid with colour-coded events
gk-progress-barAnimated counter with configurable colour
gk-stat-cardDashboard metric with trend indicator
gk-quick-actionMobile-friendly tap targets
gk-file-dropzoneDrag-and-drop upload with progress
gk-presence-indicatorReal-time “who’s viewing this” via SSE

All components use CSS variables for theming and have WCAG 2.1 AA accessibility (ARIA attributes, keyboard navigation, screen reader announcements).

Security Fixes

Two vulnerabilities fixed in this release:

Stored XSS in ticket notes (gotrs-io/gotrs-ce#176): HTML content rendered via |safe in templates allowed <script> injection. Fixed with defence-in-depth — server-side bluemonday sanitisation on both write and read paths.

Null byte injection in file uploads: Filenames like shell.php\x00.jpg passed extension validation but the OS truncated at the null byte. Now rejected outright.

Security headers added: CSP with frame-ancestors 'none' (anti-clickjacking), base-uri 'self', form-action 'self', X-Content-Type-Options: nosniff.

By the Numbers

  • 9 major feature areas shipped
  • 6 database migrations (000009–000014)
  • 6 design specifications written
  • 15 field types for custom fields
  • 7 reusable UI components
  • 15 languages with native translations
  • 5 months ahead of the original September target
  • 2 days of implementation (26–28 March 2026)

What’s Next

0.8.1 (June 2026) brings the Statistics plugin’s Chart.js dashboard widgets, mobile optimisation, WebAuthn/FIDO2 hardware keys, plugin UI offline/service worker support, and performance benchmarks.

0.9.0 (August 2026) ships the first-party open source plugins: FAQ/Knowledge Base, Calendar & Appointments, and Process Management with visual workflow designer.

1.0.0 (November 2026) is the production release: security audit, load testing, comprehensive documentation, and migration tooling from OTRS 6.x.

Bonus Track: What We Learned

The biggest surprise was how much of the work was routing. Not the features — the features were straightforward once designed. But getting routes right took more iteration than any single feature. A wildcard route /:entity_type/:id/custom-fields silently ate our ticket API. A CSP nonce that worked in curl broke every page because Alpine.js needs unsafe-eval internally. The 2FA login path generated JWTs with role=user instead of role=Admin because it was a copy-pasted code path that nobody had tested with admin accounts.

The lesson: design specs save time, tests that verify functionality (not just code paths) catch real bugs, and DRY isn’t just about reducing lines of code — it’s about having one code path for “generate a login token” instead of three that drift apart.


Questions? Feedback? Open a GitHub Discussion and let us know what you think!

Back to Blog