Migrating Wrapper Rules to Appian 25.3 a!queryRecordType()

Certified Senior Developer

Hi everyone,

With Appian 25.3, a new version of a!queryRecordType() was introduced, and the previous version is still available as a!queryRecordType_25r2(). This evolution brings more control and clarity over which fields are retrieved.

Key difference between the two versions

25.2 - a!queryRecordType_25r2()

  • When no fields are specified, all base record fields are returned automatically.
  • Even if only relationship fields were passed, the base record fields would still come back unless explicitly narrowed down.

25.3 - a!queryRecordType()

  • When no fields are specified, only the primary key (id) is returned.
  • To retrieve all fields, the new a!selectionFields() function should be used with its allFieldsFromRecordType parameter.

Why this matters for wrapper rules

In our applications, most queries are wrapped in a reusable rule that takes a single fields input. Callers of these wrappers don’t (and shouldn’t) need to worry about the function version or its internal implementation.

To align with the new function, the wrapper needs to:

  1. Identify base record fields in the fields input. If none are present, automatically include the base record in allFieldsFromRecordType.
  2. Identify relationships in the fields input. These should also be passed into allFieldsFromRecordType.
  3. Always pass the requested fields to the selectFields parameter.

This way:

  • If callers specify base fields, only those fields are retrieved.
  • If callers specify no base fields, all base fields are retrieved by default.
  • Relationships continue to work as expected.

Expressions we are testing

Detecting base record fields

  • ri!record: an empty instance of the base record containing no fields
  • ri!fields: the same list of fields passed to the wrapper rule

if(
  and(
    a!isNotNullOrEmpty(ri!fields),
    a!isNotNullOrEmpty(ri!record)
  ),
  rule!LGTCP_RejectNulls(
    input: a!forEach(
      items: a!keys(
        a!update(
          ri!record,
          ri!fields,
          a!forEach(ri!fields, null)
        )
      ),
      expression: if(
        typeof(fv!item) = 284, /* Record field */
        fv!item,
        null
      )
    )
  ),
  null
)

Detecting relationships

  • ri!input: the same list of fields passed to the wrapper rule

cast(
  /*Record Relationship*/
  a!listType(298),
  if(
    a!isNotNullOrEmpty(ri!input),
    rule!LGTCP_RejectNulls(
      input: a!forEach(
        items: ri!input,
        expression: if(typeof(fv!item) = 298, fv!item, null)
      )
    ),
    {}
  )
)

Implementation

a!queryRecordType(
  recordType: <RECORD_TYPE>,
  fields: a!selectionFields(
    allFieldsFromRecordType: {
      if(
        a!isNullOrEmpty(
          rule!ExtractBaseRecordFields(
            record: <RECORD_TYPE>(),
            fields: ri!fields
          )
        ),
        <RECORD_TYPE>,
        {}
      ),
      rule!ExtractRecordRelationships(input: ri!fields)
    },
    selectFields: ri!fields,
    includeRealTimeCustomFields: true
  ),

Open questions for the community

  • Has anyone else approached this migration in a different way?
  • Do you see a simpler or more robust method to distinguish base fields vs relationships in the fields parameter?
  • Any best practices for structuring wrapper rules around the new a!queryRecordType()?

  Discussion posts and replies are publicly visible

Parents
  • 0
    Certified Senior Developer

    Hi  , thanks for your reply.

    Just to clarify and avoid any confusion: our “wrapper rule” approach is also one rule per record type, similar to your pattern of having clear, dedicated expressions per record.

    Where our use case differs a bit is that we were relying on these rules not only to fetch all fields, but also for cases where we wanted to retrieve specific subsets of fields (e.g. only a few base fields, or a mix of base and relationship fields).

    Now, with the new a!queryRecordType(), our wrapper needs a reliable way to detect whether the fields input contains any base-record fields (as opposed to only relationships or relationship fields). If there are base-record fields present, we should pass those as the selection; if there are none, we want the wrapper to automatically include the base record in allFieldsFromRecordType (and include any relationships) so callers that expect the “all base fields when none specified” behavior don’t need to change.

    We also understand that we could simply preserve the old versioned function, like  suggested, and introduce the new one with a slightly different contract (for example, separate parameters for fields vs all-record fields). However, staying aligned with Appian’s latest version of such an important function is valuable for us. At the same time, we’d like to avoid always querying every field by default, since for the same reasons Appian introduced this new standard, it’s not best practice to always select everything.

    Our proposed solution works as intended, but we were just wondering if there is an official or better approach to achieve the same behavior.

Reply
  • 0
    Certified Senior Developer

    Hi  , thanks for your reply.

    Just to clarify and avoid any confusion: our “wrapper rule” approach is also one rule per record type, similar to your pattern of having clear, dedicated expressions per record.

    Where our use case differs a bit is that we were relying on these rules not only to fetch all fields, but also for cases where we wanted to retrieve specific subsets of fields (e.g. only a few base fields, or a mix of base and relationship fields).

    Now, with the new a!queryRecordType(), our wrapper needs a reliable way to detect whether the fields input contains any base-record fields (as opposed to only relationships or relationship fields). If there are base-record fields present, we should pass those as the selection; if there are none, we want the wrapper to automatically include the base record in allFieldsFromRecordType (and include any relationships) so callers that expect the “all base fields when none specified” behavior don’t need to change.

    We also understand that we could simply preserve the old versioned function, like  suggested, and introduce the new one with a slightly different contract (for example, separate parameters for fields vs all-record fields). However, staying aligned with Appian’s latest version of such an important function is valuable for us. At the same time, we’d like to avoid always querying every field by default, since for the same reasons Appian introduced this new standard, it’s not best practice to always select everything.

    Our proposed solution works as intended, but we were just wondering if there is an official or better approach to achieve the same behavior.

Children