Types
What It Is
A type defines one object model in Jetstack. It is the top-level builder object that owns a property set, controls baseline object rendering, and acts as the main contract for how records of that kind behave across forms, detail pages, queries, permissions, canvases, and navigation.
Why It Matters
Once a type exists and other platform features start depending on it, several decisions become expensive to change:
- naming and system naming
- whether the type is treated as an entity, enumeration, or checklist
- which properties belong to it
- which module exposes it
- which form and show variants it uses
- whether ACL and caching are enabled
- whether custom canvases replace the standard runtime
In practice, the type is where the platform-level design of a business object starts.
How To Read This Chapter
This chapter follows the same form groups the Type builder uses:
- General
- Form
- Displaying
- Canvases
- Logging
For each option, this page explains:
- what the option controls
- how to configure it
- what values are available
- when it matters in runtime behavior
Use this chapter together with:
Mental Model
Keep these distinctions clear when configuring a type:
type_nameandtype_name_pluralare user-facing labels.system_nameis the stable implementation identifier.roletells Jetstack what kind of type this is conceptually.object_propertiesdefines the schema surface of the type.viewer, form variants, and canvas slots define how the type is presented and interacted with.acl_enabled,is_cached, andis_activeare runtime behavior flags, not cosmetic settings.
Configuration Groups
General
These fields define the identity, structure, and main platform behavior of the type.
| Option | What it controls | How to configure it | Notes |
|---|---|---|---|
id | Stable internal identifier of the type record. | You normally do not configure this manually. It is assigned by the platform. | Useful for troubleshooting, exports, sync, and internal references. |
role | Conceptual role of the type in the model. | Choose one of the built-in values: Entity, Enumeration, or Checklist. | Entity is the normal business-object role. Enumeration is for managed vocabularies. Checklist is for simpler controlled lists. |
system_name | Stable machine-facing identifier for the type. | Use only English letters, digits, and underscores. The validation pattern is [a-zA-Z0-9][a-zA-Z0-9_]*, so the name must start with a letter or digit and cannot contain spaces. | Treat this as a durable implementation contract. Prefer names like invoice, contract, or project_task. |
type_name | Main singular business-facing name of the type. | Enter the singular name users should recognize, such as Invoice, Contact, or Asset. | This field is required on create and edit. It also has an onchange helper that prefills type_name_plural with the same value during editing. |
type_name_plural | Plural user-facing name of the type. | Enter the plural name used in lists and navigation, such as Invoices, Contacts, or Assets. | This field is required. Review the auto-prefilled value because correct pluralization often needs manual adjustment. |
icon | Main icon associated with the type. | Choose an icon that is meaningful in navigation and object headers. | This is a semantic UI choice. Reuse icon conventions across similar domains. |
object_properties | The property collection owned by the type. | Add, remove, and order the properties that define the schema of this type. | This is one of the most important fields on the type. It defines the data contract and affects forms, queries, views, permissions, and automations. |
parent_module | Module under which the type appears in navigation and organization. | Select the module that should own this type in the implementation hierarchy. | Read Modules. This affects discoverability and tenant structure more than data semantics. |
description | Human-readable description of the type. | Enter a concise explanation of what the type represents and how it should be used. | Use this to document business purpose, not source-code detail. |
is_cached | Whether objects of this type should be cached locally until changed. | Enable only when the type's records are safe to reuse between reads without always reloading from the database. | Use with care. The field description explicitly says cached types are reused locally until changed. |
acl_enabled | Whether ACL rules are enforced for the type. | Enable when access to this type must be constrained by roles, permissions, or type-level rules. | Default is enabled. It is best to decide this early because turning ACL on late affects views, automations, APIs, and user expectations. |
is_active | Whether the type is active in the current implementation. | Leave enabled for types that should participate in the active tenant experience. Disable only when the type should exist in the model but not be actively used. | This defaults to an active state when creating a new type. |
General Option Details
role
The role is more than a label. It affects how implementers and users think about the type.
Entity: use for ordinary business objects with full lifecycle, such asInvoice,Opportunity, orTask.Enumeration: use when the records represent a managed value vocabulary, such asPriority,Department, orIndustry.Checklist: use for simple controlled item sets where records behave like ordered checklist entries.
Practical implications:
- enumeration and checklist types are especially good candidates for controlled lists and selector backing
- enumeration and checklist types expose an "order items" action in the type UI
- entity types usually justify richer permissions, views, and object actions
system_name
Treat this as the implementation-safe identifier for:
- exported type configuration
- synchronization payloads
- stable references across documentation and implementation logic
Good examples:
invoiceproject_taskservice_contract
Avoid:
- names with spaces
- names that depend on UI wording
- unstable naming that may change after other features already reference the type
object_properties
This field points to the full property set of the type. In runtime terms, it defines:
- what values the type stores
- which fields appear in forms
- which properties can be filtered, sorted, grouped, or shown in views
- which data can be exposed in APIs and automations
It is configured as a multi-value relation to Property, with uniqueness enforced and dynamic property creation enabled. In practical builder terms, that means:
- you can add properties directly while working on the type
- the type owns a unique set of property definitions
- the same type can hold many properties
Read Properties alongside this section.
Form
These options define the default create and edit form experience for objects of this type.
| Option | What it controls | How to configure it | Notes |
|---|---|---|---|
object_form_variant_add | Default form variant used when creating objects of this type. | Choose one of: Automatic one page, Custom one page, Tabbed, or Wizard. | This determines the baseline add experience unless a custom add canvas replaces it. |
object_form_variant_edit | Default form variant used when editing existing objects of this type. | Choose one of: Automatic one page, Custom one page, Tabbed, or Wizard. | This determines the baseline edit experience unless a custom edit canvas replaces it. |
Form Variant Details
Automatic one page
Use when:
- the type has a manageable number of fields
- the standard form layout is enough
- you want a straightforward form without custom composition
This is the most typical baseline.
Custom one page
Use when:
- you still want a one-page interaction model
- but you need more deliberate layout or grouping behavior
This is usually a better fit when a type has medium complexity and the default automatic layout is no longer expressive enough.
Tabbed
Use when:
- the type has many fields
- the fields naturally fall into clearly separated sections
- users benefit from navigating by topic rather than scrolling
This works especially well with thoughtfully designed form groups.
Wizard
Use when:
- the creation or editing flow is sequential
- later steps depend on earlier input
- the user should be guided through a process rather than shown the whole form at once
This is a process-oriented choice, not just a layout choice.
Displaying
These options define the main object-detail presentation and some high-level viewing behavior.
| Option | What it controls | How to configure it | Notes |
|---|---|---|---|
layout_file | Main layout template file used around the object rendering. | Select the layout file to be used. The default points to the standard @layout.latte template path relative to the project templates directory. | This is an advanced presentation setting. Change it only when you need a different layout shell for this type's rendering behavior. |
viewer | Baseline single-object viewer used for the type. | Choose one of the registered single viewers: Form, Post, or Public site. | The default is the first configured single viewer, which is currently Form. |
object_form_variant_show | Default show-mode property presentation style. | Choose one of: Simple list, Custom list, Groups on one page, or Groups as tabs. | This defines how properties are shown when using the standard detail rendering rather than a custom show canvas. |
comments | Whether comment support is enabled for this type. | Choose Yes or No. | Use comments only when the type genuinely benefits from conversational or review-style discussion. |
modal_prefers_xl | Whether the type prefers opening in a large modal. | Enable when forms or object details for this type usually need more space. | This is a UX preference, especially important for dense forms, wide layouts, or richer object detail views. |
Display Option Details
layout_file
This field controls the broader rendering shell around the type's presentation. The default value is the standard shared layout file. You should change it only when:
- the type needs a specialized layout wrapper
- the tenant uses deliberate type-specific layout behavior
- the type should render in a presentation frame different from the general platform default
This is not the first tool to reach for when you only need a different field layout. In most cases, form variants, show variants, form groups, or canvases are the better place to work.
viewer
The viewer determines the overall single-object presentation mode.
Available values:
FormPostPublic site
Use them like this:
Form: best for standard back-office object interaction and structured editing/showing.Post: useful when the object reads more like a content item than a dense data form.Public site: use when the object should be presented with a more public-facing rendering model.
This field and the show variant work together:
- the viewer decides the broad single-object experience
- the show variant decides how the type's properties are arranged inside that experience
object_form_variant_show
Available values:
Simple listCustom listGroups on one pageGroups as tabs
Use them like this:
Simple list: good for compact straightforward detail pages.Custom list: use when the standard list format is not enough but you still want a list-oriented presentation.Groups on one page: best when the type has deliberate group structure and users benefit from seeing all sections together.Groups as tabs: best when the type is large and users need section-based navigation on object detail.
comments
Enable comments when:
- records need discussion
- users review or annotate object changes
- collaboration belongs directly on the record
Leave comments off when:
- the type is purely technical
- records are simple enumerations or controlled vocabularies
- discussion would add noise rather than useful context
Canvases
These slots let the type replace standard add, edit, show, delete, or not-found behavior with custom canvases.
| Option | What it controls | How to configure it | Notes |
|---|---|---|---|
object_canvas_add | Custom canvas used when creating an object of this type. | Select a canvas when the standard add form should be replaced or heavily customized. | Use this only when form variants and form design are not enough. |
object_canvas_edit | Custom canvas used when editing an object of this type. | Select a canvas when edit behavior needs a custom interaction model or layout. | This is often useful for process-heavy edit screens. |
object_canvas_delete | Custom canvas used for delete handling. | Select a canvas when deletion needs a richer confirmation or contextual workflow. | Most types do not need this unless delete behavior is business-sensitive. |
object_canvas_show | Custom canvas used when showing object details. | Select a canvas when the standard show rendering is not expressive enough. | This is a common way to build highly tailored detail pages. |
object_canvas_not_found | Custom canvas used when the requested object is not found. | Select a canvas when missing-object handling should be customized. | This is a specialized UX tool and is usually only needed in advanced implementations. |
Canvas Slot Strategy
Use custom canvases when:
- the standard type viewer and form variants cannot express the required experience
- the type needs a workflow-like or dashboard-like interaction surface
- the object should be shown as a composed visual page rather than a field list
Do not use a custom canvas just because:
- the automatic form is slightly plain
- one section needs reordering
- a small amount of layout tuning would solve the problem
In those cases, form groups, show variants, and property-level configuration are usually better tools.
Read Canvases.
Logging
These options opt the type in to the Event Log. When a flag is enabled, the platform automatically records an entry into the ledger every time an object of this type is created, read, updated, deleted, or restored from the Trash — without any automation or code.
Lifecycle logging is off by default on every type. It is deliberately an opt-in per type and per operation, because the log is meant to be a curated stream of meaningful activity, not a blanket write-ahead trace.
| Option | What it controls | How to configure it | Notes |
|---|---|---|---|
log_create_enabled | Whether an Object Created event is written whenever an object of this type is successfully created. | Enable on types whose creation is governance-relevant. | Default off. |
log_create_message | Custom content stored on the creation event. | Enter free-form text. Shortcodes and expressions are evaluated against the newly created object. Leave empty to use a platform default. | Visible only when creation logging is enabled. Cross-link: Expressions. |
log_read_enabled | Whether an Object Accessed event is written whenever an object of this type is opened on an operator-visible surface. | Enable only when read traceability is explicitly required. | Default off. Fires on the object detail page, on a REST single-resource fetch, and on an MCP single-object retrieval — not on list views or on internal resolver calls. |
log_read_message | Custom content stored on the access event. | Shortcodes/expressions evaluate against the accessed object. Leave empty for the platform default. | Visible only when read logging is enabled. |
log_update_enabled | Whether an Object Updated event is written whenever a change to an object of this type is actually persisted. | Enable on types whose changes should be visible in the operator log. | Default off. The event fires only when at least one property value actually changed. |
log_update_message | Custom content stored on the update event. | Shortcodes/expressions evaluate against the updated object (post-change state). Leave empty for the platform default. | Visible only when update logging is enabled. |
log_delete_enabled | Whether an Object Deleted and Object Restored event is written whenever an object of this type is deleted or restored from the Trash. | Enable on types where deletions are sensitive. | Default off. Deletion and restoration are two halves of one lifecycle, so a single flag controls both. The object state is captured before deletion so expressions can still resolve, including under hard-delete. |
log_delete_message | Custom content stored on the deletion and restoration events. | Shortcodes/expressions evaluate against the object. Used for both deletion and restoration — phrase it neutrally, or leave empty for the platform default (which distinguishes "has been deleted" from "has been restored" automatically). | Visible only when the delete flag is enabled. |
Logging Option Details
How the default message looks
When the message field is empty, the platform writes a neutral default of the form:
Object of type
<type name>(type ID<N>) with ID<M>and system name "<system name>" has been created / accessed / updated / deleted.
This is designed to be readable in the operator surface without configuration. Use it when the identifier and operation name are enough. Provide a custom message when the event needs domain-specific phrasing, extra context from object properties, or a translation key for localization.
Custom messages and expressions
Custom message fields are passed through the same expression layer used elsewhere in the platform. This means property shortcodes resolve against the object in scope:
- on create, update, read — the object as it exists after the operation
- on delete — the object as captured immediately before deletion
Shortcode syntax and available functions are documented in Expressions. This is how a tenant implementer phrases a custom entry like "Invoice {$this.invoice_number} for {$this.customer.type_name} was archived." without writing any automation.
If the message begins with __, the log treats it as a translation key. See Event Log → Title And Content.
When reading is logged
Read logging fires only on operator-visible single-object retrievals. Specifically:
- the object detail page (the platform's "show" action across object presenters, system resource presenters, and the active-resource presenter), but only on the initial full-page or modal render — not on re-renders driven by snippet redraws, signal handlers, batch sub-requests, or background AJAX side-tasks
- a single-resource fetch on the REST API — either by id/uuid (
GET /<type>/<id>) or by property value (get-by) - an MCP tool call that retrieves a specific object (the
getoperation on a type's MCP toolset)
Read logging deliberately does not fire on:
- list views and collection endpoints (paginated browsing is not an "access" worth logging)
- snippet redraws and AJAX signal handlers on the detail page (clicking a refresh link, re-sorting a nested view, submitting an inline form — these re-render parts of the page but are not fresh "access" moments)
- batch sub-requests (a batch action processing many items fires internal sub-requests that must not each count as a deliberate access)
- background AJAX operations (
ajax-backgroundside-tasks such asrecreateTableorreadaptTable) - internal object resolution during permission checks, expression evaluation, form rendering, automation scope resolution, cache warm-up, or search indexing — these happen constantly and are not operator-initiated retrievals
This narrow scope keeps the ledger meaningful: one event per deliberate "show me this record" interaction, not one per framework-internal lookup or partial redraw.
When to enable read logging
Even with the narrow scope, read logging is still the noisier of the four lifecycle flags — a single user browsing through a handful of records can produce a visible stream of access events. Enable it when read traceability is itself a governance requirement:
- personally identifiable data
- financial records under audit
- regulatory data whose access must be provable
- records where "who looked at this and when" is a first-class question
Leave it off for everything else.
Update logging and meaningful change
Update events are only written when at least one property value actually changed as a result of the update. Calls that resolve to no-op updates do not enter the ledger, so operators are not shown empty "updated but nothing changed" noise.
Deletion and captured context
Before the delete takes effect, the platform captures the object instance so that expressions in the custom deletion message can still resolve against it. This holds even when the tenant is configured for hard delete (objectsRealDelete) and the row is physically removed — the event content is still populated correctly.
Restoration is bound to the same flag
There is no separate flag for restoration. log_delete_enabled controls both Object Deleted and Object Restored events, because the two are halves of one lifecycle: a tenant that cares about tracking deletions also cares about tracking restorations. Logging one without the other would leave the governance trail asymmetric.
Restore events fire when a user explicitly restores a soft-deleted record from the Trash surface. There is no automatic or internal "restore" in the platform. If the tenant is configured for hard delete, soft-deleted rows do not exist in the first place and restore events therefore cannot occur for that type.
The restore event is emitted after the is_deleted flag has been flipped back, after the search index is refreshed, and after the object cache is invalidated — so the object passed to the custom message reflects the now-live record, not a stale in-memory copy.
The custom message in log_delete_message is reused for both events. The platform's default message distinguishes "has been deleted." from "has been restored." automatically based on the event category, so leaving the message field empty yields correctly-worded defaults for both halves.
Recommended pattern
- Start with all four flags off.
- Turn on
log_create_enabledandlog_delete_enabledfirst on types where lifecycle is sensitive. The delete flag automatically covers restoration too, so the ledger narrates both halves of the soft-delete / restore flow out of the box. - Turn on
log_update_enabledonly on types where field-level changes are audit-relevant. - Turn on
log_read_enabledonly when read traceability is a formal requirement. The scope is narrow (operator-visible single-object retrievals, not internal resolution), but a browsing session can still produce a visible stream of access events. - Use custom messages for domain phrasing; rely on the default when the generic form is enough.
Read Event Log for the operator-facing surface, filtering model, and retention behavior of the resulting events.
Additional Type-Owned Structures
Some important type-level structures are not ordinary editable fields in the grouped type form, but they are still part of the type contract.
object_actions
This is the type-owned list of object actions. It defines which actions can be configured and later shown on objects of the type.
Use this when:
- the object needs domain-specific actions
- action visibility and behavior are part of the object contract
top_nav
This is the type-owned top navigation structure.
Use this when:
- objects of the type need meaningful top-level contextual navigation
- the default navigation is not enough
form_groups
These are the groups used to organize properties for form and show rendering.
Use this when:
- the type has many properties
- the object experience should be split by concern
- tabbed or grouped layouts need meaningful sections
These structures are edited through dedicated type-oriented editors such as:
- form designer
- actions editor
- top navigation editor
Runtime Consequences Of Key Decisions
acl_enabled
When this is enabled, access to type records should be designed together with:
- role permissions
- property visibility
- view availability
- API exposure
- automation assumptions
This is not a small toggle. It affects the security model of the type.
is_cached
When enabled, objects of the type are cached locally until changed. This can help when:
- objects are read often
- they change relatively infrequently
- the business flow tolerates cached retrieval between updates
It is a poor fit when:
- data changes frequently outside the current interaction path
- freshness is critical
- users expect immediate reflection of external or concurrent updates
role
The role influences how the type is expected to be used:
- entity types justify rich lifecycle features
- enumeration types support managed vocabularies
- checklist types support lightweight ordered option sets
Form And Show Variants
These choices shape how much structure the default runtime gives users before any custom canvas is introduced. Many implementations can go very far using only:
- good properties
- deliberate form groups
- suitable form/show variants
That is usually preferable to jumping straight to canvases.
Common Configuration Patterns
Standard Business Object
Use:
role = Entity- ACL enabled
- comments enabled only if collaboration matters
- add/edit form variants as
Automatic one pageorTabbed - show variant as
Groups on one pageorGroups as tabs
This is a good baseline for things like invoices, contracts, tickets, or projects.
Managed Vocabulary Type
Use:
role = Enumeration- a narrow property set
- comments usually off
- simpler show behavior
- careful module placement if users need to manage the vocabulary directly
This works well for status sets, categories, industries, and other controlled reference data.
Checklist-Oriented Controlled List
Use:
role = Checklist- minimal schema
- simple forms
- comments off in most cases
This is a good fit when the records behave like maintainable list entries rather than full lifecycle objects.
Custom Experience Type
Use:
- deliberate form/show variants first
- custom canvases only where the standard runtime stops being expressive enough
- modal XL preference when the custom interaction surface needs width
This is the right pattern for highly tailored record experiences.
Design Questions To Ask Before Finalizing A Type
- Is this truly a separate object type, or should it be modeled as a property or related vocabulary?
- Should this type be an
Entity,Enumeration, orChecklist? - Does the type need ACL from the start?
- Will users mostly interact through forms, detail views, or custom canvases?
- Does the type belong under an existing module or justify a distinct navigation area?
- Are comments really useful here, or would they create noise?
- Does the type need caching, or would cached reads create correctness problems?
Best Practices
- Choose a stable
system_nameearly and avoid renaming it later. - Decide the
rolebased on business meaning, not just on record count. - Keep enumeration and checklist types intentionally narrow.
- Use modules to make the information architecture clear, not just to "put things somewhere."
- Prefer good form groups and suitable variants before reaching for custom canvases.
- Enable ACL deliberately and early when the type carries sensitive or role-dependent data.
- Turn on caching only when you can explain why cached behavior is safe for that type.