json-object-editor
v0.10.671
Published
JOE the Json Object Editor | Platform Edition
Readme
Json Object Editor
JOE is software that allows you to manage data models via JSON objects. There are two flavors, the client-side version and nodejs server platform.
What's new in 0.10.663 (brief)
- Plugin Inventory debug page:
- New
_www/plugins-test.htmlpage (linked from the MCP nav) shows all active plugins, their async/top-level methods, and which JOE apps reference them. It’s auth-protected and backed by theplugin-utilsplugin, and is especially useful for AI agents and developers to confirm that methods likechatgpt.autofillandchatgpt.widgetStartare present on a given instance.
- New
What's new in 0.10.662 (brief)
- React Form Integration with JSON Definitions:
- JSON includes:
includeschema now supportsfiletype: 'json'for storing JSON form definitions (served at/_include/{id}with proper content-type). - Form-page linking: Added
formreference field topageschema to link pages to JOE forms. - Form definition API: New
/API/plugin/formBuilder/definitionendpoint (merged fromformDefinitionplugin) serves JSON form definitions. Automatically finds JSON includes from form metadata or page includes. - React form renderer: New
joe-react-form.jsclient library renders multi-step React forms from JSON definitions with conditional visibility, validation, and submission to JOE's form submission system. - Page rendering fix: Enhanced template variable processing to preserve newlines in page content, fixing JavaScript comment issues in
codeandmodulecontent types.
- JSON includes:
What’s new in 0.10.660 (brief)
- MCP everywhere (prompts, autofill, widget):
- Prompts (
ai_prompt): new MCP config block (mcp_enabled,mcp_toolset,mcp_selected_tools,mcp_instructions_mode) lets you turn MCP tools on per‑prompt, pick a toolset (read-only,minimal,all, orcustom), and auto‑generate short tool instructions. - Autofill fields: the same MCP keys are now supported under a field’s
aiconfig so autofill runs can optionally call MCP tools with the same toolset/playlist model. - Audit:
ai_responserecords MCP config and actual tool calls (mcp_tools_used[]) plusused_openai_file_ids[]so you can see which tools and files were used for any run.
- Prompts (
- Uploader file roles + AI‑aware attachments:
- Uploader fields can define
file_roles(e.g.{ value:'transcript', label:'Intake Transcript', default:true }) and JOE renders a per‑file role<select>that saves tofile_roleon each file object. executeJOEAiPromptnow sends a compactuploaded_files[]header (includingitemtype,field,filename,file_role, andopenai_file_id) alongside Responses input so prompts can reason about “transcript vs summary” sources while the OpenAI Files integration still handles raw content.- Responses+tools (
runWithTools) now attaches files on both the initial tool‑planning call and the final answer call, so MCP runs see the same attachments end‑to‑end.
- Uploader fields can define
- History safety:
- Hardened
JOE.Storage.savehistory diffing to avoid acraydent-objectedge case where comparingnull/undefinedvalues could throw on.toString(). This only affects_history.changes, not what is saved.
- Hardened
- Object chat unification:
- The
objectChatfield now launches a floating<joe-ai-widget>shell with<joe-ai-assistant-picker>, reusing the same AIHub chat stack for all schemas. ai_widget_conversationgainedscope_itemtype/scope_idso each chat can be scoped to a specific object, plusattached_openai_file_idsandattached_files_metato track which uploader files were attached.- On the first turn of an object-scoped chat, the server preloads a slimmed
understandObjectsnapshot for the scoped object and merges object files + assistant files intoopenai_file_ids, so the assistant immediately knows “which record this is” and can reason over its files.
- The
What’s new in 0.10.654 (brief)
- OpenAI Files mirrored on S3 upload; uploader tiles show the
openai_file_id. Retry upload is available per file. - Responses integration improvements:
- Per‑prompt
attachments_modeonai_prompt(directvsfile_search). Direct sendsinput_fileparts; file search auto‑creates a vector store and attaches it. - Safe retry if a model rejects
temperature/top_p(we strip and retry once). - Select Prompt lists prompts by active status where either
datasets[]orcontent_items[].itemtypematches the current object. ai_responsenow showsused_openai_file_idsand correctly recordsreferenced_objectsfor Select Prompt runs.
- Per‑prompt
- UX: “Run AI Prompt” and “Run Thought Agent” buttons disable and pulse while running to avoid double‑submits.
Architecture & Mental Model (Server)
- Global JOE
- Created in
server/init.js; exposes subsystems likeJOE.Utils,JOE.Schemas,JOE.Storage,JOE.Mongo/JOE.MySQL,JOE.Cache,JOE.Apps,JOE.Server(Express),JOE.Sites,JOE.io(Socket), andJOE.auth.
- Created in
- Init pattern
- Modules can optionally export
init();init.jsloads/watches modules and callsinit()afterJOE.Serveris ready. This enables hot-reload and keepsServer.jsthin.
- Modules can optionally export
- Schemas & events
JOE.Schemasloads fromserver/schemas/and app schema dir intoJOE.Schemas.schemawith names inJOE.Schemas.schemaList. Raw copies live inJOE.Schemas.raw_schemasfor event hooks.JOE.Storage.save()triggers schema events (create,save,status,delete) viaJOE.Schemas.events()and writes history documents to_history.
- Storage & cache
JOE.Storage.load(collection, query, cb)chooses backend per schemastorage.type(mongo,mysql,file,api), with Mongo/file fallback.JOE.Storage.save(item, collection, cb, { user, history })emitsitem_updatedto sockets, records history, and fires events.JOE.Cache.update(cb, collections)populatesJOE.Data, flattens alist, and buildslookup. UseJOE.Cache.findByID(collection, idOrCsv)for fast lookups;JOE.Cache.search(query)for in-memory filtering.
- Auth
JOE.authmiddleware checks cookie token or Basic Auth; many API routes (and/mcp) are protected.
- Shorthand
$J- Server and client convenience:
$J.get(_id),$J.search(query),$J.schema(name). - On server, provided by
server/modules/UniversalShorthand.jsand assigned toglobal.$Jininit.js.
- Server and client convenience:
- MCP overview
- Manifest:
/.well-known/mcp/manifest.json; JSON-RPC:POST /mcp(auth-protected). Tools map to real JOE APIs (Schemas,Storage,Cache) and sanitize sensitive fields.
- Manifest:
MCP routes and local testing
Endpoints
- Manifest (public):
GET /.well-known/mcp/manifest.json - JSON-RPC (auth):
POST /mcp - Privacy (public):
/privacy(uses SettingPRIVACY_CONTACTfor contact email) - Terms (public):
/terms
- Manifest (public):
Auth
- If users exist,
POST /mcprequires cookie or Basic Auth (same as other APIs). If no users configured, it is effectively open.
- If users exist,
Test pages
- JOE ships several debug/test pages in
/_www/:mcp-test.html- Simple MCP tool testermcp-schemas.html- Schema health and summary viewermcp-prompt.html- MCP prompt/instructions viewermatrix.html- Interactive schema relationship visualization with D3.js force-directed graph
- Access via JOE path:
http://localhost:<PORT>/JsonObjectEditor/_www/<page>.html - If your host app serves its own
_www, pages can also be available at the root (fallback) if running with the updated server that mounts JOE’s_wwwas a secondary static directory. Then:http://localhost:<PORT>/<page>.html
- JOE ships several debug/test pages in
Tools
listSchemas(name?),getSchema(name)getObject(_id, itemtype?)(supports optionalflattenanddepth)search(exact): unified tool for cache and storage- Params:
{ itemtype?, query?, ids?, source?: 'cache'|'storage', limit?, flatten?, depth? } - Defaults to cache across all collections; add
itemtypeto filter; setsource:"storage"to query a specific collection in the DB. Runtime accepts legacy aliasschema(maps toitemtype). UsefuzzySearchfor typo-tolerant free text.
- Params:
fuzzySearch(typo-tolerant free text across weighted fields)- Params:
{ itemtype?, q, filters?, fields?, threshold?, limit?, offset?, highlight?, minQueryLength? } - Defaults:
fieldsresolved from schemasearchables(plural) if present; otherwise weightsname:0.6, info:0.3, description:0.1.threshold:0.5,limit:50,minQueryLength:2. - Returns:
{ items, count }. Each item may include_score(0..1) and_matcheswhenhighlightis true.
- Params:
findObjectsByTag { tags, itemtype?, limit?, offset?, source?, slim?, withCount?, countOnly?, tagThreshold? }- Find objects that have ALL specified tags (AND logic). Tags can be provided as IDs (CUIDs) or names (strings) - names are resolved via fuzzy search.
- Returns:
{ items, tags, count?, error? }wheretagscontains the resolved tag objects used in the search. - Use
countOnly: trueto get just the count and matched tags without fetching items. - If tags cannot be resolved, returns
{ items: [], tags: [...resolved ones...], error: "message" }instead of throwing.
findObjectsByStatus { status, itemtype?, limit?, offset?, source?, slim?, withCount?, countOnly?, statusThreshold? }- Find objects by status. Status can be provided as ID (CUID) or name (string) - name is resolved via fuzzy search.
- Returns:
{ items, status, count?, error? }wherestatusis the resolved status object used in the search. - Use
countOnly: trueto get just the count and matched status without fetching items. - If status cannot be resolved, returns
{ items: [], status: null, error: "message" }instead of throwing.
saveObject({ object })saveObjects({ objects, stopOnError?, concurrency? })- Batch save with per-item history/events. Defaults:
stopOnError=false,concurrency=5. - Each object must include
itemtype;_idandjoeUpdatedare set when omitted.createdis set on first save when missing. - Return shape:
{ results, errors, saved, failed }.
- Batch save with per-item history/events. Defaults:
Quick tests (PowerShell)
- Prepare headers if using Basic Auth:
$pair = "user:pass"; $b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair)) $h = @{ Authorization = "Basic $b64"; "Content-Type" = "application/json" } $base = "http://localhost:<PORT>" - Manifest:
Invoke-RestMethod "$base/.well-known/mcp/manifest.json" - listSchemas:
$body = @{ jsonrpc="2.0"; id="1"; method="listSchemas"; params=@{} } | ConvertTo-Json -Depth 6 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body - getSchema:
$body = @{ jsonrpc="2.0"; id="2"; method="getSchema"; params=@{ name="<schemaName>" } } | ConvertTo-Json -Depth 6 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body - search (cache default):
$body = @{ jsonrpc="2.0"; id="3"; method="search"; params=@{ query=@{ itemtype="<schemaName>" }; limit=10 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body - fuzzySearch (cache):
$body = @{ jsonrpc="2.0"; id="6"; method="fuzzySearch"; params=@{ itemtype="<schemaName>"; q="st paal"; threshold=0.35; limit=10 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body - search (storage):
$body = @{ jsonrpc="2.0"; id="4"; method="search"; params=@{ itemtype="<schemaName>"; source="storage"; query=@{ }; limit=10 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body - search (ids + flatten):
$body = @{ jsonrpc="2.0"; id="5"; method="search"; params=@{ itemtype="<schemaName>"; ids=@("<id1>","<id2>"); flatten=$true; depth=2 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body - saveObject:
$object = @{ itemtype="<schemaName>"; name="Test via MCP" } $body = @{ jsonrpc="2.0"; id="4"; method="saveObject"; params=@{ object=$object } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body - saveObjects (batch):
$objs = @( @{ itemtype="<schemaName>"; name="Batch A" }, @{ itemtype="<schemaName>"; name="Batch B" } ) $body = @{ jsonrpc="2.0"; id="7"; method="saveObjects"; params=@{ objects=$objs; stopOnError=$false; concurrency=5 } } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
- Prepare headers if using Basic Auth:
Quick tests (curl)
- Manifest:
curl -s http://localhost:<PORT>/.well-known/mcp/manifest.json | jq - listSchemas:
- search (cache):
curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"3","method":"search","params":{"query":{"itemtype":"<schemaName>"},"limit":10}}' | jq - fuzzySearch:
curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"6","method":"fuzzySearch","params":{"itemtype":"<schemaName>","q":"st paal","threshold":0.35,"limit":10}}' | jq - search (storage):
curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"4","method":"search","params":{"itemtype":"<schemaName>","source":"storage","query":{},"limit":10}}' | jqcurl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"1","method":"listSchemas","params":{}}' | jq - saveObjects (batch):
curl -s -X POST http://localhost:<PORT>/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"7","method":"saveObjects","params":{"objects":[{"itemtype":"<schemaName>","name":"Batch A"}],"stopOnError":false,"concurrency":5}}' | jq
- Manifest:
Troubleshooting
- If you see a payload like
{ originalURL: "/...", site: "no site found" }, the request hit the Sites catch-all. Ensure MCP routes are initialized before Sites (handled by default inserver/init.jsviaMCP.init()), and use the correct URL:/.well-known/mcp/manifest.jsonor/mcp. - To update contact email on /privacy and /terms, set Setting
PRIVACY_CONTACT.
- If you see a payload like
Thoughts & Thought Pipeline
thoughtschema stores AI/human hypotheses, syntheses, and link-thoughts withabout[](what it concerns),because[](receipts),status, and AI lineage fields likesource_ai_response/created_by.ThoughtPipelinemodule compiles a deterministic context from ascope_object(the item you’re thinking about), schema summaries, and accepted Thoughts, and therunThoughtAgentMCP tool runs an OpenAI Responses call to propose new Thoughts.- Each Thought run persists an
ai_responsewithresponse_type:'thought_generation',referenced_objects:[scope_id], andgenerated_thoughts[]containing the ids of created Thought records. - In any schema UI you can include core fields
proposeThoughtandai_responsesto (a) trigger a Thought run for the current object and (b) list all relatedai_responserecords for audit and reuse.
File uploads (S3 + OpenAI Files)
Uploader field options:
allowmultiple: true|false— allow selecting multiple files.url_field: 'image_url'— on success, sets this property to the remote URL and rerenders that field.ACL: 'public-read'— optional per-field ACL. When omitted, server currently defaults topublic-read(temporary during migration).
Flow:
- Client posts
{ Key, base64, contentType, ACL? }to/API/plugin/awsConnect. - Server uploads to S3 (AWS SDK v3) and, if
OPENAI_API_KEYis configured, also uploads the same bytes to OpenAI Files (purpose=assistants). - Response shape:
{ url, Key, bucket, etag, openai_file_id?, openai_purpose?, openai_error? }. - Client:
- Sets the
urlon the file object; ifurl_fieldis set on the schema field, it assigns that property and rerenders. - Persists OpenAI metadata on the file object:
openai_file_id,openai_purpose,openai_status,openai_error. - Renders the OpenAI file id under the filename on each uploader tile. The “OpenAI: OK” banner has been removed.
- Shows a per‑file “Upload to OpenAI” / “Retry OpenAI” action when no id is present or when an error occurred. This calls
POST /API/plugin/chatgpt/filesRetryFromUrlwith{ url, filename, contentType }and updates the file metadata.
- Sets the
- Client posts
Errors:
- If bucket or region config is missing, server returns 400 with a clear message.
- If the bucket has ACLs disabled, server returns 400: “Bucket has ACLs disabled… remove ACL or switch to presigned/proxy access.”
- If OpenAI upload fails, the uploader shows
OpenAI error: <message>inline; you can retry from the file row.
Using OpenAI file ids:
- File ids are private; there is no public URL to view them.
- Use the OpenAI Files API (with your API key) to retrieve metadata or download content:
- Metadata:
GET /v1/files/{file_id} - Content:
GET /v1/files/{file_id}/content
- Metadata:
- Node example:
const OpenAI = require('openai'); const fs = require('fs'); const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const meta = await client.files.retrieve('file_abc123'); const stream = await client.files.content('file_abc123'); const buf = Buffer.from(await stream.arrayBuffer()); fs.writeFileSync('downloaded.bin', buf);
File roles on uploader fields
- Schema configuration:
- Any uploader field can declare
file_rolesas an array of{ value, label?, default? }objects, for example:{ value:'transcript', label:'Intake Transcript', default:true }{ value:'summary', label:'Intake Summary' }
labelis optional; it falls back tovalue. At most one role should havedefault:true.
- Any uploader field can declare
- Runtime behavior:
- JOE renders a role
<select>next to each uploaded file with:- A blank option, and one option per configured role.
- The select updates the file object’s
file_roleproperty in the parent object (e.g.client.files[].file_role). - Existing uploads show the role selector on first render as long as
file_rolesis configured on the field.
- When OpenAI Files are enabled, uploader files still receive
openai_file_id,openai_purpose,openai_status, andopenai_erroras before;file_roleis an additional, JOE‑level label.
- JOE renders a role
- AI integration:
- When running an AI prompt via
executeJOEAiPrompt, JOE inspects referenced objects for uploader fields and builds anuploaded_files[]header:- Each entry includes
{ itemtype, field, name, role, openai_file_id }. - This header is merged into the user input so prompts can explicitly reason about which files are “transcripts”, “summaries”, etc., while the actual file bytes are attached via Responses
input_fileparts /file_searchtool resources.
- Each entry includes
- When running an AI prompt via
Related endpoints (server/plugins)
POST /API/plugin/awsConnect– S3 upload (and OpenAI mirror when configured)- Input:
{ Key, base64, contentType, ACL? } - Output:
{ url, Key, bucket, etag, openai_file_id?, openai_purpose?, openai_error? }
- Input:
POST /API/plugin/chatgpt/filesRetryFromUrl– (Re)upload an existing S3 file to OpenAI- Input:
{ url, filename?, contentType? } - Output:
{ success, openai_file_id?, openai_purpose?, error? }
- Input:
SERVER/PLATFORM mode
check port 2099
/JOE/
/JsonObjectEditor/docs.html
*Should auto-open on startJson Object Editor (Universal-esque software) (requires connection to a mongo server for full functionality)
JOE server instantiation (add to entry point js file)
var joe = require('json-object-editor');
or here's a custom example
var joe = require('json-object-editor')({
name:'name_your_joe' (JOE),
joedb:'local_custom_database_name' // {{mongoURL}}' if using mongolab or remote mongodb,
port:'4099', (2099)
socketPort:'4098', (2098)
sitesPort:'4100' (2100),
clusters:1,
hostname:'server name if not localhost'(localhost)
});$J (universal) Shorthand JOE
$J to access helper funtions on client and server. (callbacks are optional)
$J.get(itemId,[callback])
$J.schema(schemaname,[callback])Client-Side (front end only)
js client only instantiation
var specs = {
fields:{
species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema},
gender:{type:'select', values:['male','female']},
legs:{label:'# of Legs',type:'int', onblur:logit},
weight:{label:' Weight (lbs)',type:'number', onblur:logit},
name:{label:' pet Name', onkeyup:logValue},
//id:{label:'ID',type:'text', locked:true},
id:{label:'ID',type:'guid'},
//example of select that takes function (function is passed item)
animalLink:{label:'Link to other animal',type:'select', values:getAnimals},
hiddenizer:{hidden:true}
},
schemas:{
animal:animalschema,
thing:thingschema
},
container:string ('body'),
compact:false,
useBackButton:true,
autosave:2000,
listSubMenu:{filters:{}},
useHashlink:true,
title:'${itemtype} | ${display}'
}
var JOE = new JsonObjectEditor(specs);
JOE.init();##JOE CONFIG ##specs
useBackButton:[false] if true, back button moves through joe panels when joe has history to go to (is open).
useHashlink:[false], true or a template for hashlinks. default template is '${schema_name}_${_id}' default server tempalte is '${schema_name}/${_id}' ##SCHEMA CONFIG ###fields Properties for all Fields
label / display: what the field should display as *If the field type is boolean, label controls checkbox/boolean labelvalue: default value if not one in objectdefault: default value for field || function(object)type: what type of field should JOE showhidden: boolean / function, value will be added (but unsees by user)locked: booleancondition: booleanwidth: used for layout control.- can use pixels or percentages (as string)
comment: a commentthat shows up at the beginning of the fieldtooltip: hover/clickable tooltip that shows up next to name
field types:
rendering: for css html and jstext: default single line text.- autocomplete: boolean // obj of specs (template, idprop)
- values:array of possibilities -maxlength:string
- autocomplete: boolean // obj of specs (template, idprop)
int: integer fieldnumber: number (float) fieldselect: select list.- multiple(bool)
- values(array of objects, [{value:"",name/display:""]), can be a function
- disabled:boolean(func acceptable)
- idprop: string of prop name
geo: shows a map- takes a string array "[lat,lon]"
- center:[lat,lon], center of map
- zoom: zoom level (higher zooms in more)
- returns "[lat,lon]"
-
image: shows an image and HxW as th image url is typed in.
multisorter: allows arrays of objects to be selected and sorted in right bin.- values(array of objects, [{value:"",name/display:""]), can be a function
content: show content on in the editor- run: function to be run(current_object,field_properties)
- template: html template for fillTemplate(template,current_object);
objectlist: a table of objects with editable properties- properties: array of objects|strings for the object property names -name: value in object -display: header in objectList
- max: integer, number or items that can be added. use zero for infinite.
- hideHeadings: don't show table headings
objectReference: a list of object ids- template
- autocomplete_template
- idprop
- values
- max(0 unlimited)
- sortable(true)
code:- language
json:- edit/store JSON subobjects as objects (not strings) using the code editor in JSON mode; pretty-prints on blur/save and treats whitespace-only reformatting as no-op changes.
boolean:- label:controls checkbox label
preview: -content: string or function(current joe object) to replace everything on page (template). -bodycontent: same as content, only replaces body content. -url: preview page if not the default one.- encoded: boolean, if pre uriencoded labels:
pass an object instead of a string to the fields array.
{label:'Name of the following properties section'}
##page sections {section_start: 'SectionName', section_label:'Section Name with Labels', condition:function(item){ return item.show;} }, {section_end: 'CreativeBrief'}
pass an object instead of a string to the fields array. these show up on the details view as anchors.
Object Properties
section_start: name/id of section'section_label:use instead of section_start for display namesection_end: name/id of section(str)- template: html template for fillTemplate(template,current_object);
- Note: for fields,
conditionremoves the field from the DOM whilehiddenonly toggles visibility; sections usecondition(and sidebar sections usehidden) differently than fields.
##page sidebar {sidebar_start: 'SectionName', sidebar_label:'Section Name with Labels', condition:function(item){ return item.show;} }, {sidebar_end: 'CreativeBrief'}
pass an object instead of a string to the fields array. these show up on the details view as anchors.
Object Properties
sidebar_start: name/id of sidebarsidebar_label:use instead of sidebar_start for display namesidebar_end: name/id of sidebar(str)- template: html template for fillTemplate(template,current_object);
###defaultProfile overwrites the default profile
#schemas
a list of schema objects that can configure the editor fields, these can be given properties that are delegated to all the corresponding fields.
var animalschema =
{
title:'Animal', *what shows as the panel header*
fields:['id','name','legs','species','weight','color','gender','animalLink'], *list of visible fields*
_listID:'id', *the id for finding the object*
_listTitle:'${name} ${species}', *how to display items in the list*
menu:[array of menu buttons],
listMenuTitle: (string) template forjoe window title in list view,
listmenu:[array of menu buttons] (multi-edit and select all always show),
/*callback:function(obj){
alert(obj.name);
},*/
onblur:logit,
hideNumbers:boolean *toggle list numbers*
multipleCallback:function to be called after a multi-edit. passed list of edited items.
onUpdate: callback for after update. passed single edited items.
onMultipleUpdate:callback for after multi update.passed list of edited items.
filters: array of objects
checkChanges:(bool) whether or not to check for changes via interval and on leave
}##Table View - add tableView object to a schema; -cols = [strings||objects] -string is the name and value -display/header is the column title -property/name = object property ###Pre-formating you can preformat at the joe call or schema level. The data item will be affected by the passed function (which should return the preformated item).
##menu## an array of menu buttons
//the default save button
//this is the dom object,
//use _joe.current.object for current object
condition:function(field,object) to call
self = Joe object
var __saveBtn__ = {name:'save',label:'Save', action:'_joe.updateObject(this);', css:'joe-save-button'};##itemMenu## as array of buttons for each item in list views - name - action (action string) - url (instead of js action) - condition
##itemExpander## template or run for content to be shown under the main list item block.
###Addition properties Changing the schema on the fly?
_joe.resetSchema(new schema name);css (included) options
- joe-left-button
- joe-right-button
##FIELDS
{extend:'name',specs:{display:'Request Title'}},//extends the field 'name' with the specs provided.##usage
a | adding a new object
_joe.show({},{schema:'animal',callback:addAnimal);
//or goJoe(object,specs)
...
function addAnimal(obj){
animals.push(obj);
}b | viewing a list of objects
goJoe([array of objects],specs:{schema,subsets,subset})
goJoe.show(animals,{schema:'animal',subsets:[{name:'Two-Legged',filter:{legs:2}}]});
//use the specs property subset to pre-select a subset by nameproperties
_listWindowTitle: the title of the window (can be passed in with the schema);
_listCount: added to the current object and can be used in the title.
_listTitle:'${name} ${species}', how to display items in the list
_icon: [str] template for a list item icon (standard min 50x50), 'http://www.icons.com/${itemname}', can be obj with width, height, url
listSubMenu:a function or object that represents the list submenu
stripeColor:string or function that returns valid css color descriptor.
bgColor:string or function that returns valid css color descriptor.
subsets: name:string, filter:object
subMenu:a function or object that represents the single item submenu
_listTemplate: html template that uses ${var} to write out the item properties for the list item.
- standard css class
joe-panel-content-option
- standard css class
Helper shortcuts: subsets and stripes
Use these helpers to quickly generate subset/filter options and add per-item stripe colors.
// In a schema definition
{
// 1) Subsets from statuses of the current schema (auto colors)
subsets: function () {
return _joe.Filter.Options.status({
group: 'status', // optional grouping label
collapsed: true, // start group collapsed
none: true // include a "no status" option
// color: 'stripecolor' // uncomment to render colored stripes in the menu
});
},
// 2) Subsets for all distinct values of a property
// Example: recommendation_domain on 'recommendation' items
// (pass values to control order/allowlist)
// subsets: () => _joe.Filter.Options.getDatasetPropertyValues(
// 'recommendation', 'recommendation_domain', { group: 'domain', values: ['product','dietary','activity'] }
// ),
// 3) Subsets from another dataset (reference values)
// subsets: () => _joe.Filter.Options.datasetProperty('user','members',{ group: 'assignees', collapsed: true }),
// 4) Row stripe color by status (string or { color, title })
stripeColor: function (item) {
if (!item.status) return '';
const s = _joe.getDataItem(item.status, 'status');
return s && { color: s.color, title: s.name };
}
}###c | Conditional select that changes the item schema
fields:{
species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema},
[field_id]:{
+label : STR
+type : STR
value : STR (default value)
+values : ARRAY/FUNC (for select)
//modifiers
+hidden:BOOL/STRING(name of field that toggles this) //don't show, but value is passed
+locked:BOOL // show, but uneditable
//events
+onchange : FUNC
+onblur : FUNC
+onkeypress : FUNC
+rerender : STRING // name of field to rerender
}
}
function adjustSchema(dom){
var species = $(dom).val();
if(species == "thing"){
JOE.resetSchema('thing')
}
else{
JOE.resetSchema('animal')
}
}###d | duplicating an item
//duplicates the currently active object (being edited)
_joe.duplicateObject(specs);specs
deletes:array of properties to clear for new item- note that you will need to delete guid/id fields or the id will be the same.
e | exporting an object in pretty format json (or minified)
JOE.exportJSON = function(object,objvarname,minify)
##Useful Functions _joe.reload(hideMessage,specs)
- use specs.overwreite object to extend reloaded object.
_joe.constructObjectFromFields()
AI / Chat Integrations (OpenAI Responses + Assistants)
JOE ships a few plugins and schemas to help you connect OpenAI assistants and the new Responses API to your data model.
chatgptplugin- Central entry point for OpenAI calls (uses the
openaiNode SDK and the Responses API). - Looks up the OpenAI API key from the
settingschema:OPENAI_API_KEY: your secret key (can be a service‑account key, e.g.sk-svcacct-...).
- Key methods:
autofill(POST /API/plugin/chatgpt/autofill):- Given
{ object_id, schema, fields, prompt?, assistant_id?, model? }, callsopenai.responses.create(...)and returns a JSON patch for the requested fields only. - Used by
_joe.Ai.populateField(...)for schema‑driven “AI fill” buttons.
- Given
executeJOEAiPrompt:- Drives the
ai_prompt→ai_responseworkflow using Responses API + optional helper functions.
- Drives the
- Widget‑focused methods:
widgetStart,widgetMessage,widgetHistoryfor the embeddable AI widget (see below).
- Central entry point for OpenAI calls (uses the
chatgpt-assistantsplugin (legacy Assistants API)- Manages the older
beta.assistants/beta.threadsAPI for JOE’s rich in‑app chat (<joe-ai-chatbox>). - Still used by the
ai_conversationflow; new integrations should prefer the Responses‑basedchatgptplugin.
- Manages the older
chatgpt-toolsplugin- Exposes JOE “AI tools” configured via the
ai_toolschema. - Useful when you want OpenAI tools that call back into JOE (e.g. search, profile, etc.).
- Exposes JOE “AI tools” configured via the
AI Assistant schemas
ai_assistantschema- Represents a JOE‑side “assistant config” that can be synced to an OpenAI assistant.
- Key fields:
ai_model: default model (e.g.gpt-4.1-mini,gpt-4o).instructions: system prompt for the assistant.assistant_id: OpenAI assistant ID (asst_...) set by the sync button.file_search_enabled,code_interpreter_enabled: toggle built‑in tools.tools: JSON definition array for custom function tools.assistant_thinking_text: what the UI shows while the assistant is “thinking”.
- UI highlights the default assistant:
- The assistant whose
_idmatches theDEFAULT_AI_ASSISTANTsetting is given a golden stripe in list view.
- The assistant whose
- Default assistant resolution:
- The client helper
Ai.getDefaultAssistant()injoe-ai.jslooks up_joe.Data.setting.where({ name: 'DEFAULT_AI_ASSISTANT' })[0]and caches it.
- The client helper
ai_widget_conversationschema- Lightweight conversation log used by the new embeddable widget (
<joe-ai-widget>). - Fields:
model: model name used for the conversation (or inherited fromai_assistant.ai_model).assistant: optional reference to the JOEai_assistantused.assistant_id: OpenAI assistant ID (asst_...) used by the Responses API.system: effective system prompt.messages: JSON array of{ role, content, created_at }for the widget chat.last_message_at,source,tags, standard system fields.
- This schema is what the widget endpoints in
chatgptread/write.
- Lightweight conversation log used by the new embeddable widget (
Embeddable AI Widget (<joe-ai-widget>)
JOE includes a small web component, defined in js/joe-ai.js, that can be dropped into any web page and talks to your JOE instance via the chatgpt plugin.
Custom element:
<joe-ai-widget>- Attributes:
endpoint(optional): Base URL to the JOE instance. Defaults to same origin.
Example:endpoint="https://my-joe.example.com".ai_assistant_id(optional):_idof anai_assistantdocument in JOE. If present, the server will load this assistant and use itsai_model,instructions, andassistant_idwhen starting the conversation.assistant_id(optional): Direct OpenAI assistant ID (asst_...). If provided, the widget uses Responses API withassistant_idinstead of a baremodel.model(optional): Model name to use when no assistant is supplied (e.g.gpt-4.1-mini).title(optional): Header title text shown in the widget panel.placeholder(optional): Input placeholder text.source(optional): Arbitrary string stored on the conversation (ai_widget_conversation.source).conversation_id(optional): Existingai_widget_conversation._idto resume a saved chat.
- Behavior:
- On first attach:
- If
conversation_idis set → callsGET /API/plugin/chatgpt/widgetHistory?conversation_id=...and renders messages. - Otherwise → calls
POST /API/plugin/chatgpt/widgetStartto create a newai_widget_conversationand stores the returnedconversation_id(and anyassistant_id/model) as attributes.
- If
- On send:
- Optimistically appends a
{ role:'user', content }message in the UI. - Calls
POST /API/plugin/chatgpt/widgetMessagewith{ conversation_id, content, role:'user', assistant_id?, model? }. - Renders the updated
messagesand assistant reply from the response.
- Optimistically appends a
- On first attach:
- Attributes:
Server endpoints (
chatgptplugin):POST /API/plugin/chatgpt/widgetStart→chatgpt.widgetStart(data)- Input:
{ model?, ai_assistant_id?, source? }. - Behavior:
- If
ai_assistant_idis provided, loads thatai_assistantand seedsmodel,assistant_id,systemfrom it. - Creates
ai_widget_conversationwith emptymessagesand timestamps. - Returns
{ success, conversation_id, model, assistant_id }.
- If
- Input:
GET /API/plugin/chatgpt/widgetHistory?conversation_id=...→chatgpt.widgetHistory(data)- Input:
{ conversation_id }. - Returns
{ success, conversation_id, model, assistant_id, messages }.
- Input:
POST /API/plugin/chatgpt/widgetMessage→chatgpt.widgetMessage(data)- Input:
{ conversation_id, content, role='user', assistant_id?, model? }. - Behavior:
- Loads
ai_widget_conversation, appends a user message. - Calls
openai.responses.create:- With
assistant_idif available (preferred), or - With
modelandinstructions(fromsystem) otherwise.
- With
- Appends an assistant message, updates
last_message_at, saves the conversation. - Returns
{ success, conversation_id, model, assistant_id, messages, last_message, usage }.
- Loads
- Input:
AI Widget Test Page
To help you develop and debug the widget + plugin in your instance, JOE exposes an auth‑protected test page, similar to the MCP test pages.
Routes (both require standard JOE
auth):/ai-widget-test.html${JOE.webconfig.joepath}ai-widget-test.html(e.g./JsonObjectEditor/ai-widget-test.html).
Behavior (
server/modules/Server.js):- The route handler:
- Reads
assistant_idorassistantfrom the query string. - If not present, tries to resolve the default assistant via:
JOE.Utils.Settings('DEFAULT_AI_ASSISTANT', { object: true })and uses its.valueasai_assistant_id.
- Renders a small HTML page that:
- Includes the MCP nav (
_www/mcp-nav.js) so you can jump between MCP and widget tests. - Mounts
<joe-ai-widget title="JOE AI Assistant" ai_assistant_id="...">in a centered card. - Loads
js/joe-ai.js, which defines both<joe-ai-chatbox>(for in‑app use) and<joe-ai-widget>(for embedding).
- Includes the MCP nav (
- Reads
- The route handler:
Shared nav link:
The shared dev nav (
_www/mcp-nav.js) includes an “AI Widget” link:<a href="/ai-widget-test.html" target="ai_widget_test_win" rel="noopener">AI Widget</a>This appears on MCP test/export/prompt pages and on the AI widget test page itself.
AI / Widget Changelog (current work – 0.10.632)
- Added a Responses‑based tool runner for
<joe-ai-widget>that wiresai_assistant.toolsinto MCP functions viachatgpt.runWithTools. - Enhanced widget UX: assistant/user bubble theming (using
assistant_colorand usercolor), inline “tools used this turn” meta messages, and markdown rendering for assistant replies. - Expanded the AI widget test page with an assistant picker, live tool JSON viewer, a clickable conversation history list (resume existing
ai_widget_conversationthreads), and safer user handling (widget conversations now store user id/name/color explicitly and OAuth token‑exchange errors from Google are surfaced clearly during login). - Added field-level AI autofill support: schemas can declare
aiconfig on a field (e.g.{ name:'ai_summary', type:'rendering', ai:{ prompt:'Summarize the project in a few sentences.' } }), which renders an inline “AI” button that calls_joe.Ai.populateField('ai_summary')and posts to/API/plugin/chatgpt/autofillto compute a JSONpatchand update the UI (with confirmation before overwriting non-empty values).
