---
id: CAP-PFM-MTD-008
title: Manage Shared Forms and Record Layouts
feature: FEAT-PFM-MTD-03
product_area: platform
module: metadata
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-platform:docs/layouts.md#2. The purpose is the authorization'
  - 'services-platform:docs/layouts.md#4a. Two console forms (Andrew, 2026-10-11: create and edit forms)'
  - >-
    services-platform:docs/standards/custom-field-capability.md#4. What enabling custom fields requires
    (console and portal forms and layouts)
  - >-
    services-platform:docs/proposals/custom-objects/09-decisions.md#D-09 (custom object purposes share
    Platform keys)
purpose: >-
  Presentation definitions let a workspace organize how eligible records are read and edited. Shared storage
  and assignments keep those definitions manageable across applications.
outcomes:
  - Presentation definitions let a workspace organize how eligible records are read and edited.
  - Assigned definitions cannot be removed until their uses are changed.
actors:
  - ACT-PFM-007
  - ACT-PFM-009
  - ACT-PFM-011
surfaces:
  - console
  - api
preconditions:
  - The target supports the relevant presentation capability or is a live custom object.
postconditions: []
permissions:
  - key: platform.layouts.read
    where_checked: api
    scope: tenant
  - key: platform.layouts.create
    where_checked: api
    scope: tenant
  - key: platform.layouts.update
    where_checked: api
    scope: tenant
  - key: platform.layouts.delete
    where_checked: api
    scope: tenant
  - key: 'crm.contact-layouts.read, .create, .update, .delete and the other purpose owners'' administration keys'
    where_checked: service
    scope: tenant
  - key: >-
      crm.contacts.read, crm.organizations.read, committees.committees.read, platform.custom-records.read
      (consumption)
    where_checked: service
    scope: tenant
  - key: 'platform.custom-objects.read, .create, .update, .delete (administration of custom object purposes)'
    where_checked: service
    scope: tenant
rules:
  - id: BR-CAP-PFM-MTD-008-01
    proved_by: [ATS-PFM-MTD-008-01, TST-PFM-MTD-098]
    statement: >-
      A presentation definition stores a trimmed name of one to two hundred characters, optional description,
      fixed purpose and an opaque JSON object of at most 256 kibibytes that is returned without
      interpretation.
    kind: validation
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/integration/services/persistence/console-page-layouts.integration.spec.js#refuses
      a layout larger than the stated maximum, telling the person the limit and the size
    origin: none
  - id: BR-CAP-PFM-MTD-008-02
    proved_by: [ATS-PFM-MTD-008-02, TST-PFM-MTD-087, TST-PFM-MTD-098]
    statement: >-
      A replacement or patch cannot change the purpose and reports the stored and requested purposes when they
      differ.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/integration/services/persistence/console-page-layouts.integration.spec.js#refuses
      a replacement that names another purpose, naming both the stored one and the request’s
    origin: none
  - id: BR-CAP-PFM-MTD-008-03
    proved_by: [ATS-PFM-MTD-008-03, TST-PFM-MTD-087]
    statement: >-
      Reading accepts the purpose administration read key or record consumption key, while writes require the
      appropriate purpose administration key defined by the record owner.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/e2e/console-page-layouts-api.e2e.spec.js#answers 403 to a caller
      who may read contacts but not administer their layouts
    origin: 'services-platform:docs/layouts.md#2. The purpose is the authorization'
  - id: BR-CAP-PFM-MTD-008-04
    proved_by: [ATS-PFM-MTD-008-04, TST-PFM-MTD-087]
    statement: >-
      Unreachable definitions return not found for reads and writes, disappear from lists and produce an empty
      purpose-filtered list.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/e2e/console-page-layouts-api.e2e.spec.js#answers 404 for a
      definition of a purpose the caller may not read, rather than confirming it exists
    origin: 'services-platform:docs/layouts.md#What a refused caller is told'
  - id: BR-CAP-PFM-MTD-008-05
    proved_by: [ATS-PFM-MTD-008-05, TST-PFM-MTD-087]
    statement: >-
      Creation rejects an unregistered purpose before purpose permission evaluation, and a Platform staff
      wildcard cannot substitute for the purpose owner keys.
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/e2e/console-page-layouts-api.e2e.spec.js#answers 400 for a
      purpose this build does not register, naming it
    origin: none
  - id: BR-CAP-PFM-MTD-008-06
    proved_by: [ATS-PFM-MTD-008-06, ATS-PFM-MTD-008-12, TST-PFM-MTD-088]
    statement: >-
      Custom-object purposes use shared definition administration and record-read keys, require a draft or
      active object in the workspace and become unreadable when that object is archived.
    kind: permission
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/e2e/custom-object-forms.e2e.spec.js#answers 404 for a stored
      form once its custom object is archived
    origin: 'services-platform:docs/proposals/custom-objects/09-decisions.md#D-09'
  - id: BR-CAP-PFM-MTD-008-07
    proved_by: [ATS-PFM-MTD-008-07, TST-PFM-MTD-098]
    statement: >-
      Replacement and patch require the current version, update locks block modifications and delete locks
      block removal.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/integration/services/persistence/console-page-layouts.integration.spec.js#refuses
      the version the caller did not read, as a StaleObject
    origin: none
  - id: BR-CAP-PFM-MTD-008-08
    proved_by: [ATS-PFM-MTD-008-11, TST-PFM-MTD-087]
    statement: >-
      Definition lists default to fifty entries, cap at two hundred, order by name and use a cursor that is
      rejected when malformed.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/e2e/console-page-layouts-api.e2e.spec.js#answers 400 for a
      ?cursor= it did not issue
    origin: none
  - id: BR-CAP-PFM-MTD-008-09
    proved_by: [ATS-PFM-MTD-008-08, TST-PFM-MTD-093]
    statement: >-
      The owning save registers presentation use in five assignment slots, whose database references reject
      missing definitions or mismatched workspace, kind or purpose.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/integration/db/layout-assignments.table.integration.spec.js#refuses
      an assignment naming a definition of the wrong kind, because each slot’s column references one table
    origin: none
  - id: BR-CAP-PFM-MTD-008-10
    proved_by: [ATS-PFM-MTD-008-09, TST-PFM-MTD-093]
    statement: Create and edit assignments may select either two different console forms or the same console form.
    kind: policy
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/integration/db/layout-assignments.table.integration.spec.js#holds
      a different console form for creating a record and for editing one, each under its own column
    origin: 'services-platform:docs/layouts.md#4a. Two console forms (Andrew, 2026-10-11)'
  - id: BR-CAP-PFM-MTD-008-11
    proved_by: [ATS-PFM-MTD-008-10, TST-PFM-MTD-087, TST-PFM-MTD-093, TST-PFM-MTD-098]
    statement: >-
      An assigned definition cannot be deleted until its referring records or settings are changed, and
      concurrent assignment and deletion cannot leave an invalid reference.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/integration/services/persistence/console-page-layouts.integration.spec.js#names
      every referring row, in a stable order, when more than one assigns it
    origin: none
  - id: BR-CAP-PFM-MTD-008-12
    proved_by: []
    statement: >-
      The contact form designer persists console forms in Platform and reports unsupported content when
      opening a stored form rather than discarding it.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: observed
    source: 'react-console:lib/forms/storage/console-forms.ts:readStoredForm'
    origin: 'services-platform:docs/milestones/PLAT-M5-custom-records.md#PLAT-M5 D17 (FormEngine forms)'
  - id: BR-CAP-PFM-MTD-008-13
    proved_by: []
    statement: >-
      The contact page-layout designer stores drafts and published layouts in browser storage instead of
      Platform presentation storage.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: observed
    source: 'react-console:lib/layout-designer/persistence.ts:publishLayout'
    origin: none
scenarios:
  - id: SC-CAP-PFM-MTD-008-01
    given: A contact type and default settings both assign the same layout.
    when: Try deleting that layout before and after clearing its assignments.
    then: The initial refusal identifies both uses; deletion succeeds after their removal.
    provenance: documented
    source: 'services-platform:docs/layouts.md#5. Deleting a definition'
edge_cases:
  - id: SC-CAP-PFM-MTD-008-02
    given: A caller can read contacts but cannot administer their layouts.
    when: Read a layout and attempt to edit it.
    then: Reading succeeds while the edit receives a forbidden response.
    provenance: test
    source: >-
      services-platform:modules/layouts/tests/e2e/console-page-layouts-api.e2e.spec.js#answers 403 to a caller
      who may read the definition but not administer it
constraints: []
depends_on:
  - CAP-PFM-MTD-003
related:
  - CAP-PFM-MTD-002
  - CAP-PFM-MTD-009
journeys: []
open_questions:
  - OQ-PFM-MTD-002
concepts:
  - CON-PFM-MTD-003
evidence:
  - >-
    services-platform at ad3f6a6582843008ed0f5a17b131878d15829af1: modules/layouts/aws/api/*.routes.ts ,
    layout-access.ts, layout-operations.ts, purpose-permissions.ts, purposes.ts,
    services/persistence/layout-resources.ts and the four resource files, db/console-page-layouts.table.ts,
    portal-page-layouts.table.ts, console-forms.table.ts, portal-forms.table.ts, layout-assignments.table.ts,
    layout-resource-columns.ts; tests e2e/*-api.e2e.spec.js, e2e/custom-object-forms.e2e.spec.js,
    integration/db/*.table.integration.spec.js, integration/services/persistence/*.integration.spec.js,
    unit/purposes.spec.js, unit/layout-permission-keys.spec.ts; docs/layouts.md.
  - >-
    react-console at baf6dae382d6f41dce976c7f848667ccc37e076f:
    app/(console)/console/[tenantId]/configuration/{forms,recordLayouts}/**,
    configuration/[module]/{forms,recordLayouts}/**, components/forms/management, components/forms/designer,
    components/layout-designer, lib/forms/storage/console-forms.ts, lib/layout-designer/persistence.ts,
    lib/configuration/layout-definitions.ts.
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-003, TC-DAT-004, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-MTD-03-01, FAS-PFM-MTD-03-02, FAS-PFM-MTD-03-03, FAS-PFM-MTD-03-04, FAS-PFM-MTD-03-05]
suites: [TST-PFM-MTD-087, TST-PFM-MTD-088, TST-PFM-MTD-093, TST-PFM-MTD-098]
---

# Manage Shared Forms and Record Layouts

## Purpose

Presentation definitions let a workspace organize how eligible records are read and edited. Shared storage and assignments keep those definitions manageable across applications.

## How It Works

An administrator creates a named definition for a record purpose. Platform stores its document without interpreting the design. Purpose-specific access determines who can read or administer it. An owning application records where the definition is assigned. Changes use version checks and applicable locks. Assigned definitions cannot be removed until their uses are changed. The contact form designer uses shared storage, while the current contact page-layout designer publishes only in its browser.

## Relationships

Record eligibility determines which definitions may be created. Assigning applications retain responsibility for selecting and rendering them.
