Generate valid Fuuz platform data model packages (.fuuz files) that import without errors. Use when requests mention "fuuz package", "package file", ".fuuz file", "package generation", "package creator", "import package", "export package", "manifest.json", "definition.json", "package-data.json", "data model package", or when generating importable packages for the Fuuz Industrial Operations platform. Covers package file structure (manifest.json, definition.json, package-data.json), validation rules, field metadata shapes, module/sequence prerequisites, seed data patterns, definition selections, relationship triplets, lookup/enum patterns, triggers, import file generation, and pre-import validation. This skill is the authoritative reference for Fuuz package format — use it alongside fuuz-schema for data model design. Version 2.0.0 with 63 golden rules.
Instalación
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion.Ver en GitHub
name
fuuz-packages
description
Generate valid Fuuz platform data model packages (.fuuz files) that import without errors. Use when requests mention "fuuz package", "package file", ".fuuz file", "package generation", "package creator", "import package", "export package", "manifest.json", "definition.json", "package-data.json", "data model package", or when generating importable packages for the Fuuz Industrial Operations platform. Covers package file structure (manifest.json, definition.json, package-data.json), validation rules, field metadata shapes, module/sequence prerequisites, seed data patterns, definition selections, relationship triplets, lookup/enum patterns, triggers, import file generation, and pre-import validation. This skill is the authoritative reference for Fuuz package format — use it alongside fuuz-schema for data model design. Version 2.0.0 with 63 golden rules.
FUUZ Package Creator Skill
Version 2.1.0 | Last Updated: 2026-02-21
Purpose
Generate valid FUUZ platform data model packages (.fuuz files) that import without errors on the first attempt. This document is the single source of truth for FUUZ package structure, field metadata shapes, and validated patterns. It is subject-matter agnostic — the rules apply to any domain (batch processing, quality management, warehouse management, etc.).
Golden Rules
Every model is "reference" / "Reference" / "Object" — there are no other valid values.
Every field needs complete metadata — the shape differs between scalar fields and relationship fields.
The id field is never updatable — update.include must be false.
Indices must only reference fields that exist — use [] if uncertain.
Modules/groups must exist before models reference them — create them in the data array.
The data array is for seed records (Sequences, Modules, ModuleGroups) — not for enum value records.
package-data.json has exactly 7 top-level keys — no more, no fewer.
labelField must match a real field or be omitted — set it to the model's display field name (e.g., "name" for lookups, "batchNumber" for transactional), or omit the key entirely. Never set it to null.
Seed data payloads must only contain fields the model defines — extra fields cause "Field X is not defined by type YCreateInput".
dataModelTypeId must be "transactional" for high-volume operational models — models with auto-number sequences (Batch, Campaign, etc.) need this; all others use null.
Do NOT include externalId, integrationData, or customData fields unless explicitly requested — these are optional fields. Only add them when the user specifically asks for them.
Boolean values must always be lowercase — use true / false, never True / False or TRUE / FALSE. This applies everywhere: JSON field defaults, seed data payloads, and import files.
UoM fields must reference the Unit model — never use a plain String for units of measure. Instead, create an FK + relation pair (e.g., yieldUnitId: ID! + yieldUnit: Unit). Name the FK with a descriptive prefix (e.g., capacityUnitId, quantityUnitId, batchSizeUnitId). Mark as required (ID!) when the parent quantity field is required.
Every FK relationship must have an inverse list relation on the parent model — when ModelA has parentModelId (FK) + parentModel (relation), the ParentModel MUST also have a modelAs field of type [ModelA!]! with relation { from: "id", to: "parentModelId" }. This applies to ALL parent models: lookup/enum models, reference models, and cross-references within the same package. Inverse relations for models in external packages (e.g., Product, Unit from MES) are NOT added — those would need to be added to the external package.
NEVER hallucinate screens, dataFlows, dataMappings, documentDesigns, or savedTransforms — always leave these as empty arrays []. These are complex platform objects built through the FUUZ UI. Never generate fake screen definitions, flow node configurations, or data mappings. Only dataModels and data should contain content.
Modules and module groups must come from the developer — NEVER invent module or module group IDs. Either (a) ask the developer for the exact IDs of existing modules/groups they want to use, or (b) ask the developer to define the new module groups and modules that the package should create. Then include the corresponding records in the data array and selections in definition.json.
Sequence-backed fields must be String or Int type — when a field uses a sequence for auto-generated values, the field type MUST be String or Int (matching the sequence's type). Most sequences use type: "String". The field metadata includes a "sequence": { "id": "sequenceId" } property that links it to the sequence definition.
Ask the developer about lookup/enum seed values — when creating models for enumerated values (states, types, categories, severities), ask the developer if they want those values pre-populated via definition.jsonpackageDefinitionSelections. Offer to list proposed values for their review. Never silently assume a set of enum values.
Enum model IDs must be abbreviated — the id field value for enum/lookup seed records should be an abbreviated, uppercase version of the code or name for easy reference (e.g., "MAINT" not "MAINTENANCE", "SEMI_AUTO" not "SEMI_AUTOMATIC"). Keep them short but recognizable.
Enum models must have verbose descriptions — every enum/lookup seed record MUST include a meaningful, human-readable description field. Never leave descriptions empty or use just the name repeated. Descriptions should explain what the value means in context.
Enum models always use dataModelTypeId: "setup" — all lookup/enum models (status, type, category, severity, mode, etc.) use "setup" for dataModelTypeId, not null. Only high-volume operational models with auto-number sequences use "transactional".
Enum models should include color and default fields when appropriate — color (String) for values that appear in status badges, chips, or visual indicators. default (Boolean, defaultValue: false) for setting the pre-selected value in form field select inputs via predicate filters. Ask the developer which enum models need these fields.
Ask the developer about deletionReferenceBehavior preferences — the default is "prevent" (block parent deletion when children exist), but "cascade" (auto-delete children when parent is deleted) is preferred for master-data/transactional child references. For example: deleting a WorkOrder cascades to its process, BOM, and schedule records — no extra flow steps or screen development needed. Always ask the developer: "For [parent] → [child] relationships, should deleting the parent cascade-delete the children, or prevent deletion?"
usable is for enum models, active is for master/transactional models — never mix them. usable controls whether an enum value appears in selection lists. active indicates whether a record (equipment, recipe, batch) is operationally active. Never put both on the same model.
Module group icons must be FontAwesome names — FUUZ uses FontAwesome Pro. Use the icon name without the fa- prefix (e.g., "boxes", "flask", "industry"). Ask the developer which icons they prefer.
Do NOT pre-define custom fields in packages — _customFields are site/location-specific and should be created by developers through the FUUZ UI after import. Only include _customFields._externalId (String) as the standard external integration hook, and "customFields": { "exposed": true } in model metadata to enable the feature.
Model names are always PascalCase singular — e.g., BatchRecipe, EquipmentUnit, WorkOrder. Never plural. Domain prefixes are OK but often unnecessary since the module association provides context.
NEVER include system audit fields — createdAt, updatedAt, createdBy, updatedBy are auto-managed by FUUZ. Never define them in your model. DO include domain-specific timestamp/user fields that are part of the business transaction (e.g., completedAt, acknowledgedAt, approvedBy, approvedDate).
FK field naming: always prefix, never suffix — FK fields follow {camelCaseModelName}Id (e.g., batchRecipeId). When two FKs reference the same model, differentiate with a prefix: sourceBatchId / targetBatchId — never batchIdSource / batchIdTarget.
FK requiredness depends on model type — setup/enum references (statusId, typeId, stateId) are typically required (ID!). Other references (groupId, categoryId, cross-package FKs) may be optional (ID). Always verify with the developer during package creation.
Package versioning starts at 1.0.0 — all development happens in the FUUZ build environment (pre-production). Use 1.0.0 for the initial package and increment as needed. When updating a package, the new version MUST be greater than what is already installed. If the developer has made changes in the app since the last import, the new package version must also be greater than the current app version.
Data retention tiers by model type — always prompt the developer to confirm, but use these defaults: setup/lookup models = 120 days, transactional models = 3650 days (10 years), master data models = 5475 days (15 years). These values go in dataChangeCapture.retentionDays.
Indices are mostly automatic — only define specialized ones — FUUZ auto-indexes: all id fields, all FK fields, all date fields, and all relation fields. Do NOT manually define indices for these. Only define custom composite indices or specialized indices the developer specifically requests. Default to indices: [] for most models.
State machines are implemented in data flows, not model definitions — FUUZ has no built-in state machine. State transitions, validation rules, and workflow logic are all built through the FUUZ data flow designer. The package only defines the state/status field and its lookup model.
Document all package dependencies — when a package references models from other packages, the developer MUST provide the list of dependencies. Document these in the manifest (even though dependencies: {} is used for import), and note them clearly so the developer knows what must be installed first.
Prefer smaller, focused packages when iterating — while everything CAN go in one package, it's better to create smaller packages organized by concern (data models, screens, flows, documents, scripts). This way, updates only affect the specific package that changed. This is especially important when iterating with Claude.
Document screen candidates for the developer — even though we never generate screens, note which models are "list + detail screen" candidates (master data, transactional) vs "embedded table" candidates (child detail records shown inline on parent screens) vs "setup screen" candidates (lookup/enum models). This helps the developer plan their screen development.
ID generation depends on model type — setup/enum models: use camelCase of the code value as the id. Master data: FUUZ assigns CUIDs automatically. Transactional data: FUUZ assigns CUIDs, but composite IDs can be computed via create triggers (e.g., sequence + product + datetime). Import files may use human-readable IDs for setup data and CUIDs or meaningful codes for master/transactional data.
All import file IDs must match exactly — every FK value in an import CSV MUST match an existing record's id field 100% exactly (case-sensitive). Mismatches cause import errors.
Delete triggers are rarely used — prefer data flows — while "delete" triggers are technically supported, they are uncommon. If you need to react to a record deletion (e.g., cascading side effects, audit logging), use a data flow with a data change node instead. Data flows offer more control, visibility, and debuggability.
Consider flows with request-response nodes as APIs — instead of relying solely on direct API CRUD calls, consider building data flows with request-response nodes. These flows can be called from anywhere as an API, allow standardized transforms, and are easier to test and iterate on than raw triggers.
The model package is ALWAYS the foundation — install it first — in all situations, regardless of how many packages exist, the data model package must be installed first. It is the fundamental building block for all FUUZ applications. Screens, flows, documents, and scripts all depend on the models existing first.
dataTransform: "$" transforms ALL existing data — in definition.json selection metadata, "dataTransform": "$" means the transform applies to every record already in the table. This is critical when adding new required fields to an existing model — you need a migration for existing data. In the build environment, you can simply delete the package and start over (then re-import data). In production, deleting data is not an option — plan migrations carefully.
External model selections require the same metadata as new models — when referencing models from other installed packages (e.g., Product, Unit, Area from MES) in your definition.json DataModel selection, include ALL the same selection metadata fields (id, label, fields, metadata, viewFields, labelField, dataTransform) as you would for models you're creating. External models are not exempt from selection metadata.
Naming conventions are the #1 source of mistakes — improper field naming, model naming, and screen/flow naming are the most common errors developers make with FUUZ packages. Always follow: PascalCase singular for model names, camelCase for field names and FKs, prefix-based FK naming (batchRecipeId not recipeIdBatch). Consistent naming prevents import failures and confusion.
Roles and policies CAN be in packages, but implement natively first — while roles, policyGroups, and policies can be included in the package definition, it's simpler to: (1) install the model package from Claude first, (2) implement roles and policies natively within FUUZ using the UI, (3) then export the updated FUUZ package including the roles and policies, and (4) provide the exported package back to Claude for further training. This approach lets the developer use FUUZ's native role/policy editor, which is more intuitive than hand-coding JSON.
50+ data flow node types are available — FUUZ has comprehensive node types for flow design including: trigger nodes (schedule, dataChanges, request, webhook), data nodes (query, mutate), transform nodes (JSONata, JavaScript, savedTransform), control flow (when, unless, ifElse, switch, broadcast, loop), UI interaction (formDialog, snackbar, confirmDialog, navigateScreen), integration (integrateV2, awsLambda, openaiChat), notifications (sendNotification, emailSend), and gateway/device nodes. For full node configurations with JSON examples, refer to the fuuz-flows skill.
Screen field grouping is a screen concern, not a model concern — nothing in the data model definition impacts screen field grouping or sections. Field grouping, ordering, and layout sections are all configured during screen development in the FUUZ screen designer.
Exported packages have the same 3-file structure — when a developer exports a package from FUUZ (e.g., after adding roles/policies natively), the exported .fuuz file follows the exact same structure: manifest.json, definition.json, package-data.json. This means exported packages can be provided directly to Claude for learning.
Field order in the fields array does not matter — the order of fields in the model definition's fields array has no impact on import or runtime behavior. Convention is to put id first, but it's not required.
Use the GraphQL type name for queries — when building screens or flows, use the PascalCase model name as the GraphQL type (e.g., BatchRecipe, EquipmentUnit). Queries use the camelCase plural for list queries (e.g., batchRecipes { ... }) and camelCase singular for single-record queries (e.g., batchRecipe(id: "...") { ... }).
enterpriseId and environmentId are tenant-specific — packages will have different enterpriseId and environmentId depending on the target tenant. The developer MUST provide the correct values for their environment. Do not assume "mfgx" / "build" — always ask.
Packages can be rolled back — developers can uninstall/delete packages that were imported. This is particularly useful in the build environment when iterating. If a package import causes issues, the developer can roll it back and re-import a corrected version.
Flow schedules are configured natively in FUUZ — do NOT attempt to configure data flow schedules in packages. Suggest to the developer: "Configure flow schedules using the Data Flow Schedules screen in FUUZ." Schedule configuration is a runtime concern, not a package concern.
Keep skills split by concern — fuuz-schema handles data model design (what fields, relationships, constraints). fuuz-packages handles package generation (producing valid JSON that imports). Not every interaction requires package generation — sometimes the developer only needs model design guidance.
Exported FUUZ flow JSON files are training examples — flow JSON files exported from FUUZ (e.g., Production Functions 0.0.66.json, Dispatch Functions 0.0.54.json) MUST be used as training data for the flow builder skill. These are real, production-tested flow definitions.
FUUZ has a flow-based screen generator — the platform includes a flow-based screen generator that users can run from within the platform to generate screens. This is a capability developers should be aware of. Screen generation flows may need updates to output screens formatted to current standards.
Skills are deployed via the Claude admin console — to deploy a skill, go to the Claude admin console, select the .skill file, and import it. Each skill is loaded individually (one at a time), not all at once.
Each skill must be fully self-contained — skills are shared between employees, partners, and customers. They must be comprehensive and CANNOT rely on references to files outside the skills directory. All content from external documentation must be baked directly into the skill files. Never reference Documentation/ folder paths or other external file locations in skill content.
No known skill file size limits — there are no known practical limits on skill file size for FUUZ deployment targets. Claude's context window is the practical constraint (~200K tokens). Keep skills comprehensive but avoid unnecessary repetition.
Documentation folder is reference only — not distributed — the Documentation/ folder contains reference material but is NOT distributed to users. All useful content from documentation MUST be incorporated directly into the appropriate skill files. If information exists only in documentation, it effectively doesn't exist for skill users.
Skill files are the single distribution artifact — the .skill file (binary archive containing SKILL.md and references/ directory) is the only thing distributed. Everything a user needs must be inside the skill package.
Always update deployed skills when source skills change — when a skill's source content (like this file) is updated with new rules or patterns, the corresponding deployed skill in fuuz skills/ must be updated to match. Keep the deployed version in sync.
Documentation bake-in priority is screens, data models, flows, documents — when baking documentation content into skills, prioritize in this order: screen-related content first, then data model content, then flow content, then document/report content.
Screen generator flow updates are a separate task — while FUUZ has a flow-based screen generator, updating its output format to match current standards is a separate task from skill maintenance. Do not conflate the two.
Both fuuz-schema and fuuz-packages must have the same correct information — when model design rules are updated in one skill, the other must be updated to match. The schema skill focuses on design guidance; the packages skill focuses on valid JSON generation. Both need identical rules for naming, relationships, metadata, and validation.
Extract key flow patterns with examples from exported flow JSON files — when incorporating flow training data, extract the key patterns and include complete examples. Also include the raw exported JSON files in the references/ directory so additional patterns can be extracted later.
Claude must repackage all .skill files — .skill files are ZIP archives. When skill content is updated, Claude must create new ZIP archives containing the updated SKILL.md and all references/ files. Use Python's zipfile module to create the archives.
Customers and partners have their own Claude instances — skills are used by employees, partners, and customers who have their own Claude instances to build FUUZ apps. The skills save them time by providing validated patterns and examples. Skills must be comprehensive enough to work standalone.
Skill testing is prompts and testing, not automated validation — there is no automated validation pipeline for skills. Testing consists of loading the skill into Claude and testing it with prompts to verify it produces correct output.
Both schema and packages skills must use the latest updates across both — when syncing the two skills, always use the most recent version of any shared rule or pattern. If the schema skill has a more detailed version of a rule, use that version in both skills. If the packages skill has a newer correction, apply it to both.
1. Package File Structure
A .fuuz file is a gzipped tarball containing exactly 3 JSON files:
tar -czf PackageName@version.fuuz manifest.json definition.json package-data.json
Only include the 3 JSON files. No subdirectories, no extra files.
2. manifest.json
{"name":"PackageName","version":"1.0.0","applicationPublisherId":"fuuz","enterpriseId":"<ask developer>","environmentId":"<ask developer>","dependencies":{},"specVersion":"2.0.0","platformVersion":"<ask developer for current platform version>"}
Field
Value
Why
applicationPublisherId
"fuuz"
null causes validation errors
enterpriseId
Developer must provide
Varies by tenant — ask the developer for the correct value (e.g., "mfgx")
environmentId
Developer must provide
Varies by tenant — ask the developer for the correct value (e.g., "build")
dependencies
{}
Listing dependencies triggers a tenantId filter bug in the installer
platformVersion
Current platform version
Must match target environment
version
"1.0.0" initial, increment for updates
New version MUST be greater than what's installed
name
Directory name without @version
Used for package identity
Versioning rules:
Start at 1.0.0 — all development is in the FUUZ build environment (pre-production)
Increment version when updating: 1.0.0 → 1.1.0 → 1.2.0 etc.
CRITICAL: The package version MUST be greater than what is currently installed in the tenant. If the developer has made any changes in the app since the last import, the new package version must exceed the current app version.
The same version applies in manifest.json and definition.json (packageDefinitionVersion.version)
Dependencies:
Even though dependencies: {} is used in manifest (due to the installer bug), document all package dependencies clearly for the developer
Ask the developer: "What other packages must be installed before this one?" (e.g., MES@1.0.0 for Product, Unit, Area models)
Note dependencies in comments or documentation so the developer knows the install order
Package scope strategy:
When iterating on application development (typical when using Claude), prefer smaller, focused packages organized by concern:
Package type
Contains
When to use
Data model package
dataModels + data (modules, sequences) + lookup seed values
Core schema — changes infrequently
Screen package
screens only
UI development — changes frequently
Flow package
dataFlows only
Automation — changes independently
Document package
documentDesigns only
Report templates — changes independently
For a new application: Start with a single package containing all data models. Split into focused packages as the application matures and the developer needs to iterate on specific areas.
FATAL ERROR if you use any of these keys instead:moduleGroups, modules, sequences, roles. These cause Cannot read properties of undefined (reading 'filter').
CRITICAL — do NOT populate these arrays:
screens — always []. Screen definitions are complex UI objects built in the FUUZ screen designer. Never generate fake screen JSON.
dataFlows — always []. Data flows are visual automation pipelines built in the FUUZ flow designer. Never generate fake flow node configurations.
dataMappings — always []. Built through the UI.
documentDesigns — always []. Built through the UI.
savedTransforms — always []. Built through the UI.
Only dataModels and data should contain content in a package.
4. Data Model — Complete Template
Every model — whether it holds lookup values, master data, or transactional records — uses the same structure:
{"header":{"id":"myModel","name":"MyModel","description":"What this model represents","dataModelKindId":"reference","_customFields":{"_externalId":null}},"version":{"id":"myModel_v1","number":"1","dataModelId":"myModel","modelDefinition":{"name":"MyModel","metadata":{"mfgx":{"module":{"id":"targetModuleId","group":{"id":"targetModuleGroupId"}},"exposed":true,"mutable":true,"mutations":{"create":true,"update":true,"del":true},"dataChangeCapture":{"exposed":true,"retentionDays":120},"customFields":{"exposed":true},"labelField":"name",// ONLY for lookup models — omit for other models. See labelField Guidelines."editor":{"autosort":true},"indices":[]},"mongo":{"collection":"myModel"},"graphql":{"type":"Object"}},"kind":"Reference","description":"What this model represents"},"dataModelTypeId":null// Set to "transactional" for auto-number operational models}}
Immutable Values
These values are always the same — no exceptions:
Path
Value
Error if wrong
header.dataModelKindId
"reference"
"The field dataModelKindId contains an invalid reference id"
modelDefinition.kind
"Reference"
Import failure
metadata.graphql.type
"Object"
"One or more fields contains an invalid type"
metadata.mfgx.exposed
true
—
metadata.mfgx.mutable
true
—
Required mfgx Metadata
Every model MUST have ALL of these properties in metadata.mfgx. Missing any causes import errors:
Property
Value
Notes
module
{ id, group: { id } }
Both IDs must resolve to existing module/group in the tenant
exposed
true
—
mutable
true
—
mutations
{ create: true, update: true, del: true }
Model-level mutation config
dataChangeCapture
{ exposed: true, retentionDays: N }
See retention tiers below — always confirm with developer
customFields
{ exposed: true }
—
labelField
string or omit entirely
See guidelines below — do NOT set to null; either provide a value or omit the key
editor
{ autosort: true }
—
indices
[] or array of index objects
See Index Rules section — most indices are auto-created
Data Retention Tiers
Always prompt the developer to confirm retention periods. Use these defaults:
Model type
retentionDays
Duration
Examples
Setup/lookup models
120
~4 months
RecipeType, BatchState, EquipmentStatus
Transactional models
3650
10 years
Batch, Campaign, WorkOrder
Master data models
5475
15 years
EquipmentUnit, BatchRecipe, ProcessCell
Ask the developer: "Are these retention periods appropriate for your compliance requirements, or do any models need longer/shorter retention?"
System Fields — NEVER Define
FUUZ automatically manages these fields on every model. Never include them in your model definition:
createdAt — auto-set on record creation
updatedAt — auto-set on every update
createdBy — auto-set to the creating user
updatedBy — auto-set to the updating user
DO include domain-specific timestamp/user fields that are part of the business transaction itself:
completedAt, completedBy — when a batch or task was completed
approvedDate, approvedBy — when a recipe was approved
acknowledgedAt, acknowledgedBy — when a deviation was acknowledged
effectiveDate, expirationDate — date range for recipe validity
These business fields use standard types (DateTime, String, ID) and follow Shape 2 metadata.
Naming Conventions ⚠️ (#1 Source of Package Mistakes)
Element
Convention
Example
header.id
camelCase, singular
"workOrder", "equipmentStatus"
header.name
PascalCase, singular
"WorkOrder", "EquipmentStatus"
mongo.collection
camelCase (matches header.id)
"workOrder"
version.id
{header.id}_v1
"workOrder_v1"
version.dataModelId
Same as header.id
"workOrder"
Field names
camelCase
"batchRecipeId", "estimatedDuration"
FK fields
{camelCaseModelName}Id — prefix, never suffix
"batchRecipeId", "sourceBatchId"
Model naming rules:
Always PascalCase singular: BatchRecipe, EquipmentUnit — never BatchRecipes or batch_recipe
Domain prefixes are acceptable but often unnecessary (the module association provides context)
When two FKs reference the same target model, differentiate with a prefix: sourceBatchId / targetBatchId — never a suffix like batchIdSource
Field Naming Standards
Timestamp fields — Use At suffix for all date/time fields:
If update.include is true, import fails with:"The update mutation of the 'id' field is marked true for data model 'X'. Please set it to false to continue."
Shape 2: Scalar Fields
All non-relationship, non-id fields. MUST have mutations, queries, where in metadata. Missing any causes: "Cannot read properties of undefined (reading 'relation')".
Computed code fields: When a model needs a composite unique identifier (e.g., product code + revision), the code field can be auto-populated via a create-trigger JSONata transform in the FUUZ flow designer. This keeps the UI simple — users don't manually type the code. Discuss with the developer if any models need computed codes.
Shape 3: Relationship Fields
Relationships come in triplets — a scalar FK field (Shape 2) + a forward relation object field + an inverse list relation field on the parent model.
The FK scalar field follows Shape 2 exactly:
{"name":"parentModelId","type":"ID","description":"FK to ParentModel","metadata":{"mfgx":{"mutations":{"create":{"include":true},"update":{"include":true}},"queries":{"include":true},"where":{"include":true}},"unique":false}}
The forward relation object field has relation plus full mutations, queries, and where:
MUST have relation.fields.from (local FK field name) and relation.fields.to (remote field, usually "id")
MUST have mutations, queries, and where — all set to { "include": true }
Forward relations MUST include "deletionReferenceBehavior" — either "prevent" or "cascade" (see guidelines below)
The relation type should match the FK nullability: required FK (ID!) → required relation (ParentModel!), optional FK (ID) → optional relation (ParentModel)
FK requiredness guidelines (confirm with developer):
Setup/enum references (statusId, typeId, stateId) → typically required (ID!) — every record should have a status/type
Other references (groupId, categoryId) → may be optional (ID) — depends on developer preference
Cross-package FKs (productId, workunitId, areaId) → often optional (ID) — may not be mapped yet
Parent-child FKs (batchRecipeId on BatchFormula) → typically required (ID!) — child must belong to a parent
deletionReferenceBehavior guidelines:
Always ask the developer: "For [parent] → [child] relationships, should deleting the parent cascade-delete the children, or prevent deletion?"
Value
Behavior
Use when
"prevent"
Blocks parent deletion if child records exist
Lookup/enum → any child: deleting a status/type value while records reference it should be blocked
"cascade"
Auto-deletes child records when parent is deleted
Master/transactional parent → owned children: e.g., deleting a WorkOrder cascades to its process, BOM, schedule records — simplifies UI (no extra flow steps or screen development needed)
General defaults (confirm with developer):
Lookup/enum model references → "prevent" (you shouldn't delete a RecipeType while recipes use it)
Parent → owned child detail records → "cascade" (deleting a BatchRecipe should delete its BatchFormula, BatchProcedure records)
Cross-references between peer models → "prevent" (deleting an EquipmentClass shouldn't cascade to unrelated models)
Inverse/list relationships (parent → child lookups). Every FK relationship MUST have a corresponding inverse list relation on the parent model:
When a parent model is referenced by multiple child models, it gets one inverse list relation for each:
// On EquipmentClass model — two different child models point to it{"name":"equipmentUnits","type":"[EquipmentUnit!]!","description":"","metadata":{"mfgx":{"relation":{"fields":{"from":"id","to":"equipmentClassId"}},"mutations":{"create":{"include":true},"update":{"include":true}},"queries":{"include":true},"where":{"include":true}},"unique":false}},{"name":"batchProcedures","type":"[BatchProcedure!]!","description":"","metadata":{"mfgx":{"relation":{"fields":{"from":"id","to":"equipmentClassId"}},"mutations":{"create":{"include":true},"update":{"include":true}},"queries":{"include":true},"where":{"include":true}},"unique":false}}
Cross-Package References
When a field's type references a model from another installed package (e.g., Product, WorkOrder):
The referenced type MUST already exist in the tenant as an installed data model
If it doesn't exist, import fails with: "One or more fields contains an invalid type on data model 'X'"
Include those external model IDs in your definition.json DataModel selection
6. Lookup/Enum Models
Models that hold a fixed set of selectable values (status codes, type categories, etc.) are structurally identical to all other models. They use the same "reference" / "Reference" / "Object" values.
Standard lookup field set:
Field
Type
Unique
Notes
id
ID!
yes
update.include: false — use abbreviated uppercase IDs (e.g., "MAINT" not "MAINTENANCE")
code
String!
yes
Short identifier — uppercase with underscores (e.g., "IN_PROGRESS", "SEMI_AUTO")
name
String!
no
Human-readable display label (e.g., "In Progress", "Semi-Automatic")
description
String
no
Always verbose — explain what the value means in context, never leave empty or repeat the name
usable
Boolean!
no
defaultValue: true — controls whether the value appears in selection lists
color
String
no
Include when appropriate — MUST be hex color codes only (e.g., "#4CAF50" for success, "#F44336" for error). Never use named colors. Ask the developer which models need colors.
default
Boolean
no
Include when appropriate — defaultValue: false. Set ONE value to true to pre-select it in form field select inputs via predicate filter. Ask the developer which models need a default.
Lookup model metadata:
labelField: "name"
editor: { autosort: true }
indices: [] (lookup tables are small; no performance benefit from indices)
dataModelTypeId: "setup" — ALL lookup/enum models use "setup", never null
Enum ID Convention:
Seed record id values should be abbreviated uppercase for easy reference
Keep them short but recognizable: "MAINT" not "MAINTENANCE", "QC_PASS" not "QUALITY_CONTROL_PASSED"
Use underscores for multi-word: "IN_PROGRESS", "SEMI_AUTO"
The code field can be more verbose than id but typically matches the id value
Enum Description Convention:
Every enum value MUST have a meaningful description — never leave blank or just repeat the name
Descriptions should explain what the value means in context and when to use it
Example: For BatchState value RUNNING: description = "Batch is actively executing procedure phases on the equipment" (not just "Running" or "")
When to include color field:
Ask the developer: "Which enum models need color values for UI display (status badges, chips, indicators)?"
Typically yes: Status models (BatchStatus, EquipmentStatus, QualityStatus, CampaignStatus, RecipeStatus)
Typically yes: Severity models (DeviationSeverity)
Typically no: Type/category models (RecipeType, MaterialType, PhaseType) — unless the developer wants color-coded categories
When to include default field:
Ask the developer: "Which enum models should have a default value pre-selected in form inputs?"
Typically yes: Status models (e.g., DRAFT is default for new recipes, AVAILABLE is default for equipment)
Typically yes: Type models when there's a clear default (e.g., STANDARD phase type)
Typically no: Category models where no single value is a natural default
usable vs active — Know the Difference
These two Boolean fields serve different purposes and should NEVER be confused:
Field
Used on
Meaning
Controls
usable
Lookup/enum models only
Whether this enum value should appear in selection lists
Dropdown/select visibility — set false to hide a value from users without deleting it
active
Master data and transactional models
Whether the record itself is active/operational
Record lifecycle — false means the record is deactivated (soft delete)
Rules:
Lookup/enum models → use usable (Boolean!, defaultValue: true). Do NOT add active to enum models.
Master data models (Equipment, Recipes, Process Cells) → use active (Boolean!, defaultValue: true). Do NOT add usable to master data.
Transactional models (Batches, Campaigns) → use active (Boolean!, defaultValue: true) if lifecycle soft-delete is needed.
NEVER put both usable and active on the same model.
Lookup models MUST have inverse list relations:
When another model references a lookup via FK (e.g., BatchDeviation.deviationTypeId → DeviationType), the lookup model MUST have an inverse list relation field pointing back (e.g., DeviationType.batchDeviations of type [BatchDeviation!]!). This is required for every FK reference within the same package. See Shape 3 for the full field template.
Lookup/Enum Seed Values — Ask the Developer
When the package includes lookup/enum models (e.g., BatchState, RecipeType, DeviationSeverity), the developer may want to pre-populate them with values. Always ask the developer:
"Do you want these lookup values included in the package, or will you add them manually after import?"
If yes: "Here are the values I'd propose for [ModelName] — please review: DRAFT, ACTIVE, RETIRED..."
NEVER silently assume what lookup values should be
If the developer wants values included, add them as packageDefinitionSelections in definition.json:
Important: Lookup value records go in definition.jsonpackageDefinitionSelections — NOT in the package-data.jsondata array. The data array is only for Sequences, ModuleGroups, and Modules.
Where lookup models go:
YES — in package-data.jsondataModels array (they ARE data models)
YES — in definition.jsonDataModel selection
YES — their value records go in definition.jsonpackageDefinitionSelections (if developer approves)
NO — their value records do NOT go in the package-data.jsondata array
7. Index Rules
FUUZ Auto-Indexes These — Do NOT Manually Define:
FUUZ automatically creates indices for:
All id fields
All FK fields (any field ending in Id that is a relation)
All DateTime fields
All relation fields
Because of auto-indexing, most models should use indices: []. Only define custom indices for specialized scenarios the developer explicitly requests.
Default: Use empty indices []
Incorrect indices block import entirely. When in doubt, omit them. Indices can always be added later through the platform UI.
When to add custom indices:
Only for specialized scenarios NOT covered by auto-indexing:
When you are not 100% certain the field name exists in the model's field list
Inform the developer: "FUUZ auto-indexes all id, FK, date, and relation fields. You only need custom indices for composite lookups or unique constraints not already covered."
Index validation rule:
Every field name listed in an index's fields array MUST exist as a name in the model's fields array. If it doesn't, import fails silently or with an error.
8. The data Array
The data array in package-data.json holds seed records that get created during package import.
Every key in a seed data payload record MUST exist as a name in the target model's field definition. If the payload contains a field the model doesn't define, import fails with: Field "X" is not defined by type "YCreateInput". This commonly happens when lookup models don't have an active field but seed data includes "active": true.
Data entry format:
{"api":"application","modelName":"Sequence","payload":[/* array of records */],"metadata":{}}
Sequences — Complete Guide
Sequences generate auto-incrementing numbers for transactional models (e.g., batch numbers, work order numbers). They are ONLY needed when a model has an auto-number field. Standard FUUZ apps starting from scratch will NOT include sequences — they are only present when building on an application accelerator or when the developer explicitly requests auto-numbering.
When to create sequences:
The developer explicitly requests auto-generated number fields
The package extends an existing application that already uses sequences
NEVER assume a model needs a sequence — ask the developer
When a model field is backed by a sequence, add a "sequence" property to the field's mfgx metadata. The field's type MUST match the sequence's type (usually String!).
Example: code field on BatchDeviation linked to batchDeviationCode sequence:
The sequence.id MUST match a Sequence record id in the data array
The field type MUST be String! or Int! (matching the sequence's type)
The field is typically unique: true (auto-numbers should be unique)
The sequence MUST also be listed in the definition.json Sequence selection
The model using the sequence typically has dataModelTypeId: "transactional" and labelField set to the sequence-backed field name
ModuleGroup record:
{"id":"myModuleGroup","name":"My Module Group","icon":"boxes","description":"Description of this functional area","_customFields":{"_externalId":null}}// icon MUST be a FontAwesome icon name (Pro license available)// Examples: "boxes", "flask", "industry", "cogs", "clipboard-list", "chart-bar"// Reference: https://fontawesome.com/icons — use the icon name without the "fa-" prefix
Module record:
{"id":"myModule","name":"My Module","description":"Description of this module","moduleGroupId":"myModuleGroup","_customFields":{"_externalId":null}}
8a. Model Triggers (JSONata Transforms)
Triggers are JSONata expressions that execute automatically on create, update, or delete operations. They are defined in the model's metadata.mfgx.triggers property.
Trigger placement in model metadata:
"metadata":{"mfgx":{"module":{ ... },"exposed":true,"triggers":{"create":"JSONata expression for create","update":"JSONata expression for update"},"mutations":{ ... }}}
Trigger event types:
Event
Fires when
Access to
"create"
New record is created
$ (incoming payload), $after (new values)
"update"
Existing record is updated
$ (incoming payload), $before (old values), $after (new values)
Cross-model operations — triggers only affect the current record; use data flows for multi-model operations
Delete-event reactions — while "delete" triggers are technically supported, they are rarely used. Use a data flow with a data change node instead for cascading side effects, audit logging, or cleanup operations after deletion.
Alternative to triggers: Flows as APIs
Instead of relying on triggers for complex transformations, consider building data flows with request-response nodes. These flows:
Can be called from anywhere as an API (from screens, other flows, external systems)
Are easier to test and debug than inline JSONata trigger expressions
Support multi-model operations that triggers cannot handle
When to use flows as APIs instead of triggers:
The transform needs to read/write multiple models
The logic needs to be testable and debuggable step-by-step
The operation needs to be callable from multiple places (not just on create/update)
The transform is complex enough that trial-and-error in a trigger would be painful
Important notes:
Triggers are an advanced use case — discuss with the developer before adding them
Trigger testing is trial and error in the build environment — this works fine with simple CRUD operations, but consider flows for anything more complex
Always test trigger expressions carefully — a broken JSONata expression can block record creation
Setup/enum models commonly use Pattern B (camelCase ID from code) — this is the standard convention
8b. Screen Development Guidance
Even though packages NEVER include screen definitions (screens are built in the FUUZ screen designer), documenting screen candidates helps the developer plan their UI development.
Screen type recommendations by model type:
Model type
Recommended screen pattern
Notes
Setup/enum models
Setup screen — simple list with inline edit
Standard grid view with add/edit/delete. Group similar lookups together.
Master data models
List + Detail screen — grid list view with click-to-open detail form
Search/filter on the list, full form on detail. Include related child records as embedded tables.
Transactional models
List + Detail screen — grid list with detail form, possibly with workflow actions
Status badges, action buttons for state transitions. Show child records as tabs or embedded tables.
Child detail models
Embedded table — shown inline on the parent's detail screen
NOT standalone screens. Shown as a grid/table within the parent record's detail view.
When documenting models for the developer, note:
Which models need standalone list screens (master data, transactional)
Which models should be embedded tables on parent screens (child detail records)
Which lookup models can be grouped on a single setup screen
Which transactional models need workflow/action buttons for state transitions
3. ModuleGroup — if creating new module groups
4. Module — if creating new modules
Both follow the same selection pattern with their respective fields.
External model selections:
When your package references models from other installed packages (e.g., Product, Unit, Area from MES), those external models MUST be included in the DataModel selection with all the same metadata as new models. External models are not exempt from selection metadata — they need the same fields, metadata, viewFields, labelField, and dataTransform as any model you create.
What does NOT need a selection:
Individual lookup/enum model value lists — not required
Understanding dataTransform: "$":
The "dataTransform": "$" value in selection metadata means the transform applies to ALL existing data in the table during import. This is particularly important for:
Adding new required fields to an existing model — existing records will need a migration to populate the new field
In the build environment: You can delete the package and start over, then re-import data. This is the simplest approach.
In production: Deleting data is NOT an option. Plan data migrations carefully before upgrading packages with new required fields.
Best practice: When adding required fields to a model that already has data in production, include a dataTransform that provides default values for existing records, or make the new field optional initially and populate it through a data flow.
Key rule:
All packageDefinitionVersionId values MUST match packageDefinitionVersion.id.
10. Module Assignment
Every model's metadata.mfgx.module references a module and module group. Both MUST exist in the tenant at import time.
CRITICAL: NEVER invent module or module group IDs. Always get them from the developer. Module groups and modules organize the application's navigation and data model ownership — inventing IDs that don't match the developer's intent causes confusion and broken navigation.
Before generating any package, ask the developer:
"What module groups and modules should these models belong to?"
"Do these modules already exist in your tenant, or should the package create them?"
"What are the exact camelCase IDs you want to use?" (e.g., recipeManagement, batchExecution)
How to ensure they exist:
Option A (recommended for new packages): Include ModuleGroup and Module records in the data array AND their corresponding selections in definition.json. Use the IDs the developer provided.
Option B: Reference modules that already exist in the tenant. Confirm the exact IDs with the developer before generating the package.
ModuleGroup and Module records in the data array:
// ModuleGroup{"id":"batchProcessing","name":"Batch Processing","icon":"boxes","description":"ISA-88 batch control models","_customFields":{"_externalId":null}}// Module (must reference its parent group){"id":"recipeManagement","name":"Recipe Management","description":"Batch recipe definitions and procedures","moduleGroupId":"batchProcessing","_customFields":{"_externalId":null}}
Common error messages:
"Invalid Module on data model 'X'. The provided module 'Y' is invalid or cannot be found." — module ID doesn't exist
"Invalid Module Group on data model 'X'. The provided module group 'Y' is invalid or is not associated with the module 'Z'" — group doesn't exist or module isn't a member
ID format:
Both module and module group IDs use camelCase: "qualityManagement", "productionManagement"
Icon convention:
Module group icons MUST use FontAwesome icon names (FUUZ has the Pro license)
Use the icon name without the fa- prefix: "boxes", "flask", "industry", "cogs", "clipboard-list"
Ask the developer which icons they prefer for each module group
10a. Custom Fields (_customFields)
Every model automatically supports _customFields — these are developer-defined fields added through the FUUZ UI after package import. They are typically site-specific or location-specific.
Rules:
Every model definition MUST include "customFields": { "exposed": true } in its mfgx metadata — this enables the feature
Do NOT pre-define custom field schemas in the package — let developers create them in FUUZ directly
The only custom field to include in the model's field list is _customFields._externalId (type String) — this is the standard external system integration hook
Seed data records should include "_customFields": { "_externalId": null } as a placeholder
Import CSV files should include a _customFields column (leave values blank)
11. Import Error Reference
Error Message
Root Cause
Fix
Cannot read properties of undefined (reading 'filter')
Wrong top-level keys in package-data.json
Use exactly: dataModels, screens, dataFlows, dataMappings, documentDesigns, savedTransforms, data
The update mutation of the 'id' field is marked true
id field has update.include: true
Set update.include: false on every id field
Invalid Module on data model 'X'
Module ID doesn't exist in tenant
Create via data array or use existing ID
Invalid Module Group on data model 'X'
Module group ID doesn't exist or doesn't own the module
Create via data array or use existing ID
The field dataModelKindId contains an invalid reference id (enumeration)
Used "enumeration"
Always use "reference"
One or more fields contains an invalid type
Relationship type references a model not installed in tenant
Add target model to dataModels or confirm it's already installed
Cannot read properties of undefined (reading 'relation')
Scalar field missing mutations, queries, or where
Use complete Shape 2 metadata on all scalar fields
Index-related errors
Index references a field name not in the model's field list
Remove the index or fix the field name; default to []
tenantId filter not supported
dependencies lists a package name
Set dependencies: {}
Field "X" is not defined by type "YCreateInput"
Seed data payload contains a field not in the model's field definition
Remove the field from the seed data payload; every key in a payload record must exist as a field name in the model
12. Pre-Package Validation Checklist
Run through this before creating the .fuuz file:
manifest.json
applicationPublisherId is "fuuz"
enterpriseId is correct for the target tenant (developer-provided, NOT assumed)
environmentId is correct for the target tenant (developer-provided, NOT assumed)
screens, dataFlows, dataMappings, documentDesigns, savedTransforms are ALL empty [] — NEVER populated
ALL models are in dataModels (including lookup/enum models)
ALL models: dataModelKindId: "reference", kind: "Reference", graphql.type: "Object"
ALL models have complete mfgx metadata: module, exposed, mutable, mutations, dataChangeCapture, customFields, editor, indices
labelField is set to a valid field name for lookup models ("name") and transactional models (their number field), or omitted entirely for other models
version.dataModelTypeId is "setup" for ALL lookup/enum models, "transactional" for operational/auto-number models, null for master data and reference models
ALL id fields: update.include is false
ALL scalar fields: metadata has mutations.create.include, mutations.update.include, queries.include, where.include
ALL relationship fields: metadata has relation.fields.from, relation.fields.to, queries.include
ALL relationships come in triplets: FK scalar field + forward relation object field + inverse list relation on the parent model
ALL forward relation fields include mutations, queries, where, and deletionReferenceBehavior: "prevent"
ALL inverse list relation fields include mutations, queries, where with full { "include": true } shape
ALL parent models (including lookup/enum models) have inverse list relations for every FK that points to them from within the same package
No inverse list relations added for FKs pointing to external package models (e.g., Product, Unit)
ALL indices reference only fields that exist in the model
Lookup/enum models have indices: []
data array has Sequence records if needed — sequence type matches the linked field's type (String or Int)
ALL sequence-backed fields have "sequence": { "id": "sequenceId" } in their mfgx metadata
ALL sequence-backed fields are String! or Int! type with unique: true
data array has ModuleGroup and Module records if creating new ones — IDs confirmed with developer
No module/group IDs were invented — all came from developer input
No lookup/enum value records in data array (they go in definition.jsonpackageDefinitionSelections)
ALL seed data payload records contain ONLY fields that exist in the target model's field definition (no extra fields like active unless the model defines it)
ALL lookup/enum models have dataModelTypeId: "setup" (not null)
ALL lookup/enum models have usable (Boolean!, defaultValue: true) field
ALL lookup/enum seed values have verbose, meaningful description fields (never empty or name-repeated)
Lookup/enum models that need visual indicators have color (String) field — confirmed with developer
Lookup/enum models that need form defaults have default (Boolean, defaultValue: false) field — confirmed with developer
Enum record id values are abbreviated uppercase (e.g., "MAINT" not "MAINTENANCE")
deletionReferenceBehavior is set appropriately: "prevent" for lookup references, "cascade" for owned child records — confirmed with developer
Lookup/enum models use usable (Boolean!) — NOT active
Master data and transactional models use active (Boolean!) — NOT usable
No model has BOTH usable and active
Module group icons are valid FontAwesome icon names (no fa- prefix)
Color fields on enum models contain hex values only (e.g., "#4CAF50") — no named colors
_customFields._externalId included in field list; no other custom fields pre-defined in the package
Model names are PascalCase singular (never plural)
NO system audit fields defined (createdAt, updatedAt, createdBy, updatedBy) — FUUZ manages these
Domain-specific timestamps/users ARE included where needed (completedAt, approvedBy, etc.)
FK fields use prefix naming convention: {modelName}Id — never suffix (sourceBatchId not batchIdSource)
dataChangeCapture.retentionDays set per tier: setup=120, transactional=3650, master=5475 — confirmed with developer
Package version is 1.0.0 or greater, and greater than any currently installed version
No manually defined indices for fields that are auto-indexed (id, FK, date, relation fields)
No "delete" triggers used — prefer data flows with data change nodes for deletion reactions