Opero Docs
Opero APICustom Object Forms

Using Form Runtime Layouts

Resolve published custom object forms and save records through runtime layouts.

Use runtime layouts when an integration needs to render a custom object form or save records through the same form contract that Opero uses for configured layouts.

Before runtime use, the form must exist, be active, support the requested mode, and have a published valid owned View Layout.

Before You Start

You need:

  • api.view_layouts.read to resolve layouts and load runtime data;
  • api.custom_records.read to read existing record data and relation rows;
  • api.custom_records.write to create or update records.

The custom object API profile must also allow the requested record operation and field access.

The examples use:

  • module key: crm;
  • object key: ticket;
  • form ID: form_ticket_intake;
  • record ID: record_123.

Resolve A Create Layout

Resolve returns the published layout, hydrated block metadata, validation state, render context, runtime context, data requirements, and immediate render data.

GET /v1/view-layouts/resolve?surface=DYNAMIC_OBJECT&mode=CREATE&moduleKey=crm&objectKey=ticket&formId=form_ticket_intake
Authorization: Bearer ek_...

Use this response to decide which fields and blocks to render. Do not submit fields just because they exist on the object; runtime saves are checked against the resolved layout.

Resolve A View Or Edit Layout

Pass recordId when working with an existing record:

GET /v1/view-layouts/resolve?surface=DYNAMIC_OBJECT&mode=EDIT&moduleKey=crm&objectKey=ticket&formId=form_ticket_intake&recordId=record_123
Authorization: Bearer ek_...

For view-only rendering, use mode=VIEW.

If formId is omitted, the API tries to use the default form for the requested mode.

Load Lazy Runtime Data

Some blocks declare dataRequirements in the resolve response. Use runtime-data to load those values:

POST /v1/view-layouts/runtime-data?surface=DYNAMIC_OBJECT&mode=VIEW&moduleKey=crm&objectKey=ticket&formId=form_ticket_intake&recordId=record_123
Authorization: Bearer ek_...
Content-Type: application/json

{
  "requirements": [
    {
      "blockId": "relation_tasks",
      "type": "relation_table",
      "key": "tasks"
    }
  ]
}

The API returns data by block ID. Requirements that do not belong to the resolved layout are returned as unavailable instead of being trusted.

Create A Record

Use the runtime create endpoint for CREATE layouts:

POST /v1/view-layouts/runtime/dynamic-object/records?surface=DYNAMIC_OBJECT&mode=CREATE&moduleKey=crm&objectKey=ticket&formId=form_ticket_intake
Authorization: Bearer ek_...
Content-Type: application/json

{
  "clientMutationId": "ticket-create-001",
  "values": {
    "title": "Printer is offline",
    "notes": "The lobby printer stopped responding."
  }
}

clientMutationId is required for aggregate create idempotency. If one or more active workflows apply to the target object, include workflowId to start the selected workflow during create.

Update A Record

Use the runtime update endpoint for EDIT layouts:

PATCH /v1/view-layouts/runtime/dynamic-object/records/record_123?surface=DYNAMIC_OBJECT&mode=EDIT&moduleKey=crm&objectKey=ticket&formId=form_ticket_intake
Authorization: Bearer ek_...
Content-Type: application/json

{
  "clientMutationId": "ticket-update-001",
  "values": {
    "notes": "Technician dispatched."
  }
}

Workflow state can also affect edit access. If a record is in a read-only workflow stage or the current actor is not allowed to edit the current stage, the API rejects the edit even when the token has write permissions.

Relation Tables And Subordinate Objects

Runtime saves can include more than scalar fields:

  • scalar field values go under values;
  • relation-table mutations go under relationTables;
  • nested subordinate object mutations go under subordinateObjects.

Use the relation-table runtime endpoints when the UI needs table rows or target row layouts:

NeedEndpoint
Read selected relation-table rows through the resolved layoutPOST /v1/view-layouts/runtime/dynamic-object/relation-tables/table-layout
Resolve the target row layout for create or edit UIGET /v1/view-layouts/runtime/dynamic-object/relation-tables/:relationFieldKey/target-layout
Query relation rows for an existing parent recordPOST /v1/view-layouts/runtime/dynamic-object/records/:recordId/relation-tables/:relationFieldKey/query

Why A Runtime Save Can Fail

A submitted field or child mutation must pass every layer:

  • The field or relation exists on the custom object.
  • The field or relation is included in the resolved View Layout.
  • The field is writable in the requested mode.
  • The custom object API profile allows the token to use the operation and write the field.
  • The token has the required API permissions.
  • Workflow state allows the mutation when the record participates in a workflow.

If any layer blocks the field or mutation, the API rejects the save.

Runtime Endpoint Map

NeedEndpoint
Resolve published layoutGET /v1/view-layouts/resolve
Load lazy runtime dataPOST /v1/view-layouts/runtime-data
Create record through layoutPOST /v1/view-layouts/runtime/dynamic-object/records
Update record through layoutPATCH /v1/view-layouts/runtime/dynamic-object/records/:recordId
Read relation table layoutPOST /v1/view-layouts/runtime/dynamic-object/relation-tables/table-layout
Resolve target row layoutGET /v1/view-layouts/runtime/dynamic-object/relation-tables/:relationFieldKey/target-layout
Query relation table rowsPOST /v1/view-layouts/runtime/dynamic-object/records/:recordId/relation-tables/:relationFieldKey/query

Troubleshooting

Resolve Returns Not Found

Check that the form is active, belongs to the same custom object, and has a published valid layout for the requested mode. If you omitted formId, check that a default form is configured for that mode.

A Field Is Rejected

Resolve the layout and confirm the field block is present for the requested mode. Then check the custom object API profile and token permissions.

Public Form Uses An Old Layout

Publish the layout with advancePublicPinnedVersion: true so public create requests use the new published version.

On this page