Skip to content

View Inheritance

View inheritance allows modules to extend existing views without modifying the original files.

  1. Create a new view with inherited_view pointing to the parent
  2. Define operations to add, modify, or remove elements
  3. Use semantic targeting to find elements by their attributes

Extend an existing form view:

{
"data_type": "UiView",
"identifier": "contact_twitter_extension",
"type": "Form",
"model": "Contact",
"inherited_view": "contact_form_view",
"arch": {
"operations": [
{
"action": "add",
"target": {"field": "email"},
"position": "after",
"value": {
"type": "field",
"name": "twitter_handle",
"properties": {"widget": "TextInput"}
}
}
]
}
}

Two patterns to remember:

Use element-specific keys that match by the element’s natural identifier:

TargetElement TypeMatches by
{"field": "email"}fieldname attribute
{"tab": "Details"}tabtitle attribute
{"accordion": "Shipping"}accordiontitle attribute
{"fieldset": "Address"}fieldsettitle attribute
{"paper": "Customer Info"}papertitle attribute
{"button": "confirm"}actionButtonproperties.method
{"statusbar": "state"}statusbarname attribute
{"linkButton": "view_all"}linkButtonname attribute

Use type-based targeting when shortcuts aren’t available:

TargetDescription
{"type": "pageTabs"}First element of that type
{"type": "tab", "name": "crm"}Element matching type AND all other attributes
{"anchor": "my_section"}Any element with that anchor attribute

Target a field:

{"target": {"field": "email"}}

Target a tab:

{"target": {"tab": "Contact Info"}}

Target an accordion:

{"target": {"accordion": "Advanced Settings"}}

Target a fieldset:

{"target": {"fieldset": "Billing Address"}}

Target a paper section:

{"target": {"paper": "Customer Details"}}

Target by anchor (for elements without natural identifiers):

// In parent view
{
"type": "row",
"anchor": "customer_section",
"content": [...]
}
// In child view
{"target": {"anchor": "customer_section"}}

Target by type + attributes:

{"target": {"type": "tab", "name": "settings"}}

For convenience, you can use colon-separated strings:

"target": "field:email"
"target": "tab:Details"
"target": "accordion:Shipping"
"target": "fieldset:Address"
"target": "anchor:customer_section"

Insert a new element.

{
"action": "add",
"target": {"field": "email"},
"position": "after",
"value": {
"type": "field",
"name": "phone",
"properties": {"widget": "TextInput"}
}
}
PositionDescription
afterInsert after target (default)
beforeInsert before target
insideAppend to target’s content
firstInsert as first child of target

Update element properties.

{
"action": "modify",
"target": {"field": "description"},
"value": {
"properties": {
"widget": "RichTextEditor",
"required": true
}
}
}

Replace an element entirely.

{
"action": "replace",
"target": {"field": "old_field"},
"value": {
"type": "field",
"name": "new_field",
"properties": {"widget": "TextInput"}
}
}

Delete an element.

{
"action": "remove",
"target": {"field": "deprecated_field"}
}
{
"data_type": "UiView",
"identifier": "google_integration_form_view",
"type": "Form",
"model": "Integration",
"inherited_view": "integration_form_view",
"arch": {
"operations": [
{
"action": "add",
"target": {"field": "image"},
"position": "after",
"value": {
"type": "row",
"content": [
{
"type": "column",
"span": 6,
"content": [
{
"type": "field",
"name": "google_client_id",
"properties": {
"widget": "TextInput",
"label": "Google Client ID"
}
},
{
"type": "field",
"name": "google_client_secret",
"properties": {
"widget": "TextInput",
"label": "Google Client Secret"
}
}
]
}
]
}
}
]
}
}
{
"data_type": "UiView",
"identifier": "meeting_sync_extension",
"type": "Calendar",
"model": "Meeting",
"inherited_view": "meeting_calendar_view",
"arch": {
"operations": [
{
"action": "add",
"target": {"field": "name"},
"position": "before",
"value": {
"type": "actionButton",
"properties": {
"label": "Sync Calendar",
"icon": "RefreshCw",
"method": "sync_calendar"
}
}
}
]
}
}
{
"arch": {
"operations": [
{
"action": "add",
"target": {"tab": "Details"},
"position": "inside",
"value": {
"type": "field",
"name": "custom_notes",
"properties": {"widget": "TextArea"}
}
}
]
}
}

Use when semantic targeting isn’t possible (e.g., targeting a specific row or column):

{
"action": "add",
"target": "arch[1].content[0].content[5]",
"value": {...}
}

Trade-offs:

  • ✅ Can target any element, even without identifiers
  • ⚠️ Fragile - breaks if parent view structure changes
  • ⚠️ Harder to read and maintain

Tip: Prefer semantic targeting when possible. Use index paths for structural elements like row or column that lack natural identifiers, or add an anchor attribute to the parent view for stable targeting.

Apply multiple changes:

{
"arch": {
"operations": [
{
"action": "add",
"target": {"field": "email"},
"position": "after",
"value": {"type": "field", "name": "linkedin_url", ...}
},
{
"action": "add",
"target": {"field": "linkedin_url"},
"position": "after",
"value": {"type": "field", "name": "twitter_handle", ...}
},
{
"action": "modify",
"target": {"field": "phone"},
"value": {"properties": {"required": true}}
}
]
}
}
  1. Always use semantic targeting - Never use index-based paths for new code
  2. Add anchors to parent views - For stable targeting of complex sections
  3. Keep operations focused - Each inherited view should have a specific purpose
  4. Test after parent changes - Verify inherited views still work
  5. Use descriptive identifiers - contact_google_extension not contact_ext_1