# Successfully Implemented farmOS Field Module System with FieldKit PWA

**URL:** <https://farmos.discourse.group/t/successfully-implemented-farmos-field-module-system-with-fieldkit-pwa/2453>\
**Category:** Hosting farmOS\
**Created:** [January 4, 2026, 12:50am UTC](https://farmos.discourse.group/t/successfully-implemented-farmos-field-module-system-with-fieldkit-pwa/2453 "2026-01-04T00:50:13Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![MiddleWorld](https://avatars.discourse-cdn.com/v4/letter/m/ac8455/32.png) [@MiddleWorld](https://farmos.discourse.group/u/MiddleWorld)\
**Post date:** [January 4, 2026, 12:50am UTC](https://farmos.discourse.group/t/successfully-implemented-farmos-field-module-system-with-fieldkit-pwa/2453/1 "2026-01-04T00:50:13Z")

</div>

🎉 Successfully Implemented farmOS Field Module System with FieldKit PWA  
Just got the farmOS Field Module system fully operational with our FieldKit Progressive Web App! This enables dynamic loading of custom field modules from farmOS into a mobile-friendly interface. Here’s how we made it work:

🏗 Architecture Overview  
Components:

farmOS 3.x (Drupal 10) hosting Field Module JavaScript files  
FieldKit PWA (Vue.js) - mobile-first field data collection interface  
Custom REST endpoint in farmOS to list available modules  
Dynamic module loading via window.lib.mountFieldModule()  
Integration Flow:

FieldKit authenticates to farmOS via OAuth2  
Fetches module list from /api/fieldkit/modules REST endpoint  
Dynamically loads JavaScript from farmOS at runtime  
Modules register themselves and appear in FieldKit navigation  
🔑 Key Implementation Details

1. farmOS REST Resource Plugin  
Created custom REST resource at:

web/profiles/farm/modules/core/fieldkit/src/Plugin/rest/resource/FieldModuleListResource.php  
Critical: Return full URL in uri field (not relative path):

\<?php'uri' =\> $request-\>getSchemeAndHttpHost() . '/fieldkit/js/' . $module\_id . '/index.js', 2. Field Module JavaScript Structure Modules live at /fieldkit/js/{module-name}/index.js: (function (mountFieldModule) { const config = { name: 'QR Scanner', routes: [{ name: 'qr-scanner', path: '/qr-scanner', component: { /\* Vue component \*/ } }] }; mountFieldModule(config);})(window.lib?.mountFieldModule); 3. THE CRITICAL FIX: CORS Configuration 🚨 Problem: Browser blocked cross-origin script loading even though files were accessible (ERR\_FAILED with 200 status). Solution: Configure CORS headers on farmOS nginx for /fieldkit/js/ path. For Plesk setups, edit: /var/www/vhosts/system/farmos.yourdomain.com/conf/vhost\_nginx.conf Add: # CORS headers for FieldKit Field Module loadinglocation /fieldkit/js/ { add\_header 'Access-Control-Allow-Origin' 'https://fieldkit.yourdomain.com' always; add\_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; add\_header 'Access-Control-Allow-Headers' 'Content-Type' always; if ($request\_method = 'OPTIONS') { add\_header 'Access-Control-Allow-Origin' 'https://fieldkit.yourdomain.com' always; add\_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; add\_header 'Access-Control-Allow-Headers' 'Content-Type' always; add\_header 'Content-Length' 0; add\_header 'Content-Type' 'text/plain'; return 204; }} Then rebuild and reload: /usr/local/psa/admin/bin/httpdmng --reconfigure-domain farmos.yourdomain.comsystemctl reload nginx Verify CORS headers: curl -I -H "Origin: https://fieldkit.yourdomain.com" \ "https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js" | grep access-control 4. FieldKit Module Loading In FieldKit's module loader: // Use uri directly from API (full URL)const script = document.createElement('script');script.src = module.uri; // Already includes https://farmos.domain.com/...script.onload = () =\> resolve();script.onerror = () =\> reject(new Error(`Failed to load ${module.name}`));document.head.appendChild(script); 🐛 Common Pitfalls We Hit URL Doubling: Don't concatenate base URL if API already returns full URL Relative Paths: Returning /fieldkit/js/... instead of full https://... URL CORS Headers Missing: Most critical - browser blocks execution even though file downloads IIFE Syntax: Module code must be wrapped properly with mountFieldModule check ✅ Verification Steps Check API endpoint: curl -H "Authorization: Bearer YOUR\_TOKEN" \ https://farmos.yourdomain.com/api/fieldkit/modules Should return: [{ "id": "qr-scanner", "name": "QR Scanner", "description": "Scan QR codes...", "uri": "https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js"}] Verify file accessibility: curl https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js Check CORS headers (critical!): curl -I -H "Origin: https://fieldkit.yourdomain.com" \ https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js Must see: access-control-allow-origin: https://fieldkit.yourdomain.com Browser test: Open FieldKit, check console - should see "✓ Module loaded successfully!" 📱 Result Working Field Module system with: ✅ Dynamic module discovery via REST API ✅ Runtime JavaScript loading from farmOS ✅ CORS properly configured ✅ Modules appearing in FieldKit navigation ✅ Full OAuth2 authentication integration First module: QR Scanner for asset identification in the field. 🔗 Resources farmOS REST API docs: https://farmos.org/development/api/ Field Module concept inspired by farmOS Field Kit (React version) Our implementation: Vue 3 + Vite PWA architecture Happy to share more implementation details if anyone's building similar integrations! The CORS configuration was the final puzzle piece that took ages to debug. 😅

---

<div class="post-metadata">

**Author:** ![mstenta](https://yyz2.discourse-cdn.com/free1/user_avatar/farmos.discourse.group/mstenta/32/4_2.png) [@mstenta](https://farmos.discourse.group/u/mstenta)\
**Post date:** [January 5, 2026, 5:32pm UTC](https://farmos.discourse.group/t/successfully-implemented-farmos-field-module-system-with-fieldkit-pwa/2453/2 "2026-01-05T17:32:03Z")

</div>

Thanks for sharing this @MiddleWorld! Unfortunately, it looks like the formatting was messed up (maybe you pasted from another source)? It’s a bit hard to read…

---

<div class="post-metadata">

**Author:** ![MiddleWorld](https://avatars.discourse-cdn.com/v4/letter/m/ac8455/32.png) [@MiddleWorld](https://farmos.discourse.group/u/MiddleWorld)\
**Post date:** [January 8, 2026, 8:31pm UTC](https://farmos.discourse.group/t/successfully-implemented-farmos-field-module-system-with-fieldkit-pwa/2453/3 "2026-01-08T20:31:39Z")

</div>

Sorry about the formatting on the original, can’t delet it and have another go or edit it?

Just got the farmOS Field Module system fully operational with our **FieldKit Progressive Web App**!  
This enables dynamic loading of custom field modules from farmOS into a mobile-friendly interface.

Here’s how we made it work 👇

* * *

## 🏗 Architecture Overview

### Components

- **farmOS 3.x (Drupal 10)** hosting Field Module JavaScript files
- **FieldKit PWA (Vue.js)** – mobile-first field data collection interface
- **Custom REST endpoint** in farmOS to list available modules
- **Dynamic module loading** via `window.lib.mountFieldModule()`

### Integration Flow

1. FieldKit authenticates to farmOS via OAuth2
2. Fetches module list from `/api/fieldkit/modules` REST endpoint
3. Dynamically loads JavaScript from farmOS at runtime
4. Modules register themselves and appear in FieldKit navigation

* * *

## 🔑 Key Implementation Details

### 1. farmOS REST Resource Plugin

Created a custom REST resource at:

web/profiles/farm/modules/core/fieldkit/src/Plugin/rest/resource/FieldModuleListResource.php

powershell  
Copy code

**Critical:** return a **full URL** in the `uri` field (not a relative path):

```php
'uri' => $request->getSchemeAndHttpHost()
  . '/fieldkit/js/' . $module_id . '/index.js',
2. Field Module JavaScript Structure
Modules live at:

bash
Copy code
/fieldkit/js/{module-name}/index.js
Each module must self-register:

js
Copy code
(function (mountFieldModule) {
  const config = {
    name: 'QR Scanner',
    routes: [
      {
        name: 'qr-scanner',
        path: '/qr-scanner',
        component: {
          /* Vue component */
        }
      }
    ]
  };

  mountFieldModule(config);
})(window.lib?.mountFieldModule);
🚨 THE CRITICAL FIX: CORS Configuration
❌ Problem
Browser blocked cross-origin script loading even though files were accessible
(ERR_FAILED with HTTP 200).

✅ Solution
Configure CORS headers on farmOS nginx for the /fieldkit/js/ path.

Plesk setup
Edit:

swift
Copy code
/var/www/vhosts/system/farmos.yourdomain.com/conf/vhost_nginx.conf
Add:

nginx
Copy code
# CORS headers for FieldKit Field Module loading
location /fieldkit/js/ {
  add_header 'Access-Control-Allow-Origin' 'https://fieldkit.yourdomain.com' always;
  add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
  add_header 'Access-Control-Allow-Headers' 'Content-Type' always;

  if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' 'https://fieldkit.yourdomain.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Content-Type' always;
    add_header 'Content-Length' 0;
    add_header 'Content-Type' 'text/plain';
    return 204;
  }
}
Rebuild and reload:

bash
Copy code
/usr/local/psa/admin/bin/httpdmng --reconfigure-domain farmos.yourdomain.com
systemctl reload nginx
Verify headers:

bash
Copy code
curl -I -H "Origin: https://fieldkit.yourdomain.com" \
https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js | grep access-control
3. FieldKit Module Loading
In FieldKit’s module loader:

js
Copy code
// Use uri directly from API (already a full URL)
const script = document.createElement('script');
script.src = module.uri;
script.onload = () => resolve();
script.onerror = () =>
  reject(new Error(`Failed to load ${module.name}`));

document.head.appendChild(script);
🐛 Common Pitfalls We Hit
URL doubling – don’t concatenate base URL if API already returns full URL

Relative paths – /fieldkit/js/... will break cross-origin loading

Missing CORS headers – browser blocks execution even if file downloads

IIFE syntax – module must be wrapped and call mountFieldModule safely

✅ Verification Steps
Check API endpoint
bash
Copy code
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://farmos.yourdomain.com/api/fieldkit/modules
Expected response:

json
Copy code
[
  {
    "id": "qr-scanner",
    "name": "QR Scanner",
    "description": "Scan QR codes...",
    "uri": "https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js"
  }
]
Verify file accessibility
bash
Copy code
curl https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js
Check CORS headers (critical!)
bash
Copy code
curl -I -H "Origin: https://fieldkit.yourdomain.com" \
https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js
Must include:

arduino
Copy code
access-control-allow-origin: https://fieldkit.yourdomain.com
Browser test
Open FieldKit → DevTools console → should see:

lua
Copy code
✓ Module loaded successfully!
📱 Result
Working Field Module system with:

✅ Dynamic module discovery via REST API

✅ Runtime JavaScript loading from farmOS

✅ CORS properly configured

✅ Modules appearing in FieldKit navigation

✅ Full OAuth2 authentication integration

First module: QR Scanner for asset identification in the field.

🔗 Resources
farmOS REST API docs: https://farmos.org/development/api/

Field Module concept inspired by farmOS Field Kit (React version)

Our implementation: Vue 3 + Vite PWA

Happy to share more details if anyone’s building similar integrations —
the CORS config was the final puzzle piece that took ages to debug 😅
```

---

<div class="post-metadata">

**Author:** ![mstenta](https://yyz2.discourse-cdn.com/free1/user_avatar/farmos.discourse.group/mstenta/32/4_2.png) [@mstenta](https://farmos.discourse.group/u/mstenta)\
**Post date:** [January 9, 2026, 12:19pm UTC](https://farmos.discourse.group/t/successfully-implemented-farmos-field-module-system-with-fieldkit-pwa/2453/4 "2026-01-09T12:19:05Z")

</div>

@MiddleWorld I’m a bit confused… Can you help me understand what you’re doing and why? Does this add new functionality or fix an issue that you encountered?

This reads a bit like it was AI-generated, and it’s hard to tell if it’s real or not… (no offense intended by this observation - but in this new age we do need to be wary of this, unfortunately).

I hope that you do have some valuable lessons to contribute! But I’m inclined to ask you to delete this post and start over. Because it’s apparent that you didn’t review your second comment before posting it either. It is also a mess of formatting.

Apart from the formatting it is just hard to follow the reasoning, and it sounds like you’re doing things that farmOS already does - like providing field module discovery. If you ran into an issue with that, we would like to know that first, because that would be a bug and you shouldn’t have to re-implement that in a different way.

I apologize if your intentions are good, and we welcome your participation here! But it’s hard to understand what you’re trying to communicate here.
