Skip to main content

Portal Configuration

How a Flourish Core portal is written down, record by record and key by key.

Overview​

A portal is a set of Flourish Portal Config custom metadata records that share one portal key. Each record's Body holds JSON:

KindHow manyWhat its JSON says
PortalOne per portalIts name, where it is served, who it takes, how they get their roles, branding, navigation and search. See The portal record.
PermissionSetOne per portal permission setWhich objects, fields and records a role reaches, and what it can do there. See Permission sets.
PageOne per pageA page's type and the cards on it. See Pages and Cards.

Portals explains how these fit together and how to put a portal on a site. This page is the reference.

Pages are presentation; permission sets are access. A page can name any field, and the portal shows each person only the fields and records their permission sets grant. A roles list on a page, card or action hides it from everyone else, and grants nothing.

The JSON is read strictly. A key the portal does not know is an error, not something to skip, so a misspelled filters cannot quietly widen what a list shows. Every object and field name is checked against your org, and values are never pasted into a query.

A configuration error stops the portal for everyone who would see it. Pages are checked each time someone opens the portal, but only the pages, tabs and cards that person can see. So a mistake on a staff-only page leaves guests untouched and stops the portal for staff. Everyone sees This portal is not set up correctly. and staff also see what is wrong, for example a field name the org does not have. Check a change by opening the portal as each role it affects.

Name objects and fields by their API names as your org has them, namespace prefix included: Case.Status, toflourish__Service_Request__c.


The records​

Create the records in Setup, under Custom Metadata Types, with Manage Records beside Flourish Portal Config, or deploy them as metadata.

FieldHolds
Label, NameAnything. The name is what a page's key defaults to, and how a role names a permission set.
PortalThe portal's key, up to 40 characters, for example acme. The same on every record of one portal, and what the portal component is given to say which portal it draws.
KindPortal, PermissionSet or Page.
BodyThe JSON, up to 131,072 characters.

One record per page and per permission set keeps each one well under that limit, and keeps a change readable in a diff.


The portal record​

{
"label": "Acme Client Portal",
"sites": ["Acme Portal"],
"audiences": ["licensed", "internal"],
"homePage": "home",
"navigation": ["home", "cases", "accounts"],
"branding": { "primaryColor": "#7a1f5c", "logoUrl": "https://acme.org/logo.png" },
"roles": { "client": ["acme_client"], "staff": ["acme_staff"] },
"roleRules": [
{ "role": "client", "audience": "licensed" },
{ "role": "staff", "audience": "internal" }
],
"badges": { "Case.Status": { "New": "info", "Closed": "success" } },
"search": [ { "objectName": "Case", "columns": ["Subject", "CaseNumber", "Status"] } ]
}
KeyWhat it takes
labelThe portal's name, shown in its header and on the browser tab.
sitesThe Experience sites the portal answers on, by name or Id. A request from any other site is refused. Leave it out for a portal used only on Lightning pages.
audiencesWho the portal takes: any of licensed, internal, arbor and guest. See Portals.
arborNetworksFor arbor: the Ids of the Arbor networks whose members may come in. Ids only; a network's name is ignored.
guestRoleFor guest: the one role every guest gets. Without it, guests are turned away.
rolesEach role, and the permission sets it grants: { "client": ["acme_cases", "acme_files"] }. A permission set is named by its record's Name. Role and set names are not case-sensitive.
roleRulesHow everyone but a guest gets their roles. See Role rules.
homePageThe key of the page the portal opens on.
navigationThe keys of the pages in the header's navigation, in order. A page left out is still reachable by a link.
profilePageThe key of a record page showing a person their own record. See My profile.
loginUrlWhere someone the portal turned away can sign in: a path on the same site, such as /portal/login, or an https address. Anything else is ignored.
brandingSee Branding.
badgesSee Badges and tones.
searchWhat the header's search box looks in. See Search.
scopeRowBudgetHow many rows working out a person's reach may read, 20,000 by default. See Permission sets.

Role rules​

Each entry in roleRules gives roles to whoever it matches. A person gets every role whose rule matches them, and a person with no role is turned away.

RuleGives the role to
{ "role": "client", "audience": "licensed" }Everyone in that audience.
{ "role": "manager", "audience": "licensed", "field": "Title", "operator": "equals", "value": "Manager" }Anyone whose own record matches: the Contact behind a licensed user, or the record an Arbor member is tied to.
{ "audience": "arbor", "resolver": "toflourish.arbor_CoreBridge" }Whatever the named class answers. This one gives Arbor members their own roles; see Portals.
  • audience narrows any rule to one audience. A rule without one applies to everyone but guests.
  • field is read off the person's own record, and can be a path through lookups, such as Account.Type. It takes the operators listed under Filters, with value or values, and compares as text, ignoring case. Staff have no record of their own, so a field rule never matches them; give staff roles by audience.
  • A field a rule reads can never be changed through the portal, on the person's own record or on any record along the way. Otherwise a person could edit themselves into a role.
  • resolver names an Apex class that returns role names. Writing one is covered in the Portal Developer Guide.

Guests never go through roleRules: a guest has guestRole and nothing else.

Branding​

Every branding value is text, in quotes.

KeyWhat it does
primaryColorThe portal's color, as a hex value such as #7a1f5c. It colors title bars, the current tab, links and primary buttons. Everything else stays neutral. #0176d3 by default.
accentColorA second color, as a hex value. A darker shade of the primary color by default.
headerColorThe header's background, as a hex value. Its text turns light or dark to suit. Near-black by default.
logoUrlA public URL for the logo in the header.
logoWhite"true" turns dark artwork white, for a dark header.
fontFamilyA font, or a list of them, for the whole portal: "Inter, Arial, sans-serif". Letters, numbers, spaces, commas, quotes and hyphens only. A system font by default.
radiusThe corner radius of cards, in px, rem or em. 0.75rem by default.
controlRadiusThe corner radius of buttons and inputs. 0.375rem by default.
maxWidthThe widest the page column grows. 112.5rem (about 1800px) by default.
footerTextThe line in the footer, on a site. The year and the portal's name by default.
backdropribbon draws a quiet ribbon of fine lines in the portal's color behind the top of every page.
chromenone drops the header (logo, navigation, search and the person's name) and the home page's greeting, for a portal sitting inside an app with its own. The breadcrumbs stay, and lead back home.

A color, length or font that does not pass its check is replaced by the default, so the portal still draws.

Badges and tones​

badges turns a field into a colored pill wherever it appears: a list cell, a record's header, its details. Name the field as Object.Field, and give a tone for each value:

"badges": {
"Case.Status": { "New": "info", "Escalated": "danger", "Closed": "success" },
"Case.Priority": { "High": "danger", "Medium": "warning", "Low": "neutral" }
}

A value the map leaves out shows as a neutral pill. The tones are success, warning, danger, info, neutral and accent (the portal's own color). Configuration names tones, never colors, everywhere a tone is taken.

search lists the objects the header's search box looks in, in the order the results show:

"search": [
{ "objectName": "Case", "columns": ["Subject", "CaseNumber", "Status"] },
{ "objectName": "Contact", "label": "People", "columns": ["Name", "Email", "Title"] }
]
KeyWhat it takes
objectNameThe object.
columnsWhat a result shows, and what the search matches. At least one has to be a text field. The object's name field when left out.
labelThe heading over its results. The object's plural label when left out.

A search needs at least two characters. It matches only the columns a result shows that hold text, email addresses, phone numbers, URLs or picklist values, and only those the person can read, on the records they can reach. An object the person cannot reach is left out of their search altogether.

My profile​

profilePage names a record page that shows a person their own record: the Contact behind a licensed user, or the record an Arbor member is tied to. When a person has such a record, and the page is for its object, and they can read it, their name in the header opens it.

It grants nothing. A person reads and edits their own record only as far as a permission set lets them, typically with a scope rule on the record's own Id:

{ "kind": "path", "field": "Id", "principal": "recordId", "access": "edit" }

Guests and staff have no record of their own, so they have no profile link.


Permission sets​

A portal permission set grants objects, fields and records, much as a Salesforce permission set does. Roles bundle them.

{
"objects": {
"Case": {
"permissions": ["read", "create", "edit"],
"fields": { "Subject": "edit", "Description": "edit", "Status": "read", "CaseNumber": "read" },
"scope": [
{ "kind": "path", "field": "AccountId", "principal": "accountId", "access": "edit",
"filters": [ { "field": "Status", "operator": "notEquals", "value": "Closed" } ] }
],
"createDefaults": { "AccountId": "principal.accountId", "ContactId": "principal.contactId", "Origin": "Web" }
},
"CaseComment": {
"permissions": ["read", "create"],
"fields": { "CommentBody": "edit", "ParentId": "edit", "CreatedDate": "read" },
"scope": [ { "kind": "inherited", "field": "ParentId" } ]
}
}
}

A permission set is a unit. The fields it grants apply only to the records its own scope reaches. When a person holds two sets, a record one of them reaches shows only that set's fields, even if the other set grants more fields on records of its own. That is stricter than Salesforce, where field access and record access are separate, and it is what lets a "public" set show every published case's subject without lending another set's internal notes to those cases.

Object grants​

KeyWhat it takes
permissionsAny of read, create, edit and delete. Edit and delete need read in the same set. Create stands alone, so a guest form can submit a record the guest cannot read back. Check the spelling: a word that is not one of the four is ignored rather than refused.
fieldsEach field, with read or edit. A field not listed never leaves the server.
scopeWhich records: a list of scope rules. A person reaches every record any of the rules reaches. Not needed for a create-only grant.
createDefaultsValues a new record is saved with: a literal, or principal. and a principal value, such as principal.contactId. A default always wins over anything the person entered, and an empty principal value leaves the field blank.
recordTypesThe developer names of active record types a write may set. With none listed, a record type cannot be set.
showPrivatetrue lets the set reach records Salesforce marks private to external users. See below.

Some fields can never be written, whatever a set says: the owner and the created and modified fields.

What Salesforce marks private to external users stays private to everyone but staff: an unpublished case comment, a task or event not visible in self-service, a private note or attachment, and a Chatter post or file link not visible to all users. showPrivate is the way past that, set by set.

A formula field can show more than the set reaches. A formula that pulls a value from a related record shows it whether or not the related record is in reach, so granting such a field grants that value too.

Scope rules​

Every rule has a kind, an access of read (the default) or edit, and optional filters, which narrow that one rule. A person reads every record any rule reaches; they edit and delete only what an edit rule reaches.

kindReachesKeys
allEvery record, narrowed by its filters.
pathRecords where a field, or a path through lookups up to five deep, equals one of the person's principal values.field, principal
junctionRecords linked to the person through another object: rows of junction whose match equals the principal value give junctionField, which must equal this record's targetField (the Id by default).junction, junctionField, match, principal, targetField, junctionFilters
inheritedRecords whose lookup field points at a record this same set reaches on the parent object.field, parentObject, parentAccess, parentScope
apexWhatever records an Apex class returns. See the Portal Developer Guide.className
"scope": [
{ "kind": "path", "field": "Account.ParentId", "principal": "accountId" },
{ "kind": "junction", "junction": "CaseContactRole", "junctionField": "CasesId",
"match": "ContactId", "principal": "contactId" },
{ "kind": "inherited", "field": "WhatId", "parentObject": "Case" }
]

Inherited rules take three extra keys:

  • parentObject names the parent when the lookup can point at several objects, as a task's Related To can. Write one rule per parent object.
  • parentAccess is the access the parent has to be reached at. By default it is the rule's own, so an edit rule edits only the children of parents the set can edit.
  • parentScope set to any uses every set's reach on the parent, rather than only this set's (set, the default).

A rule whose principal value is empty reaches nothing. A guest has no Account, so a rule on accountId gives a guest no records at all, rather than every record with no Account.

Principal values​

What a rule, a filter or a default can compare with:

ValueIs
userIdThe person's Salesforce user. Licensed and internal users only, since Arbor members and guests all reach a site as its one guest user.
contactIdA licensed user's Contact, or an Arbor member's when they are tied to a Contact.
accountIdThat Contact's Account, or the Account an Arbor member is tied to.
recordIdThe person's own record: a licensed user's Contact, or the record an Arbor member is tied to.
elementIdAn Arbor member's own Arbor Element.
networkIdAn Arbor member's network.

How much a scope may read​

Rules that need another query to resolve (junction, apex, and some inherited ones) read rows to do it, and those rows count against the request's query limits. The portal stops at scopeRowBudget rows, 20,000 by default, and refuses the request rather than show a partial list, which would hide records without saying so.

Filters​

Filters appear in scope rules, pages and cards, and take one shape everywhere:

{ "field": "Status", "operator": "notEquals", "value": "Closed" }
KeyWhat it takes
fieldA field on the object. In a scope rule's filters it can be a path through lookups, such as Account.Type. On pages and cards it has to be a field of the object itself. Either way it has to be a field Salesforce can filter on, so not long text.
operatorRequired. equals, notEquals, in, notIn, isBlank or notBlank.
valueOne value, always written as text: "Closed", "10", "true" or "false" for a checkbox, "2026-10-03" for a date, an ISO date and time for a date/time.
valuesA list, for in and notIn. Suits text, picklists, numbers and dates.
principalA principal value instead of a fixed one, with equals or notEquals, for "my cases": { "field": "ContactId", "operator": "equals", "principal": "contactId" }. An empty principal value matches nothing.

Values are fixed. There are no relative dates such as "this month"; for those, use a calendar or a report.

A page's or card's filter can only narrow what a person sees. It applies through the fields a person can read, and a card whose filter names a field they cannot read is left off their page, since it would otherwise show them something else.


Pages​

Every page has these keys:

KeyWhat it takes
keyThe page's name in links and in navigation. The record's Name when left out.
labelWhat the navigation, the breadcrumbs and the browser tab call it.
pageTypehome, app, list or record.
iconAn icon. On a record page, it sits beside the record's name and beside the object's records in search.
rolesShow the page only to these roles.

Home and app pages​

A home page and an app page are both a set of cards. The home page is the one the portal opens on and the one the breadcrumbs lead back to; an app page is any other page made of cards.

KeyWhat it takes
cardsThe cards, in order.
layoutOptional. Places the cards in regions; see Layouts. Without one, the cards flow as a grid.

List pages​

A list page is one object's records in a table, with search, sorting and New when the person may create the object.

KeyWhat it takes
objectNameThe object.
columnsThe fields to show. The first leads each row and opens the record.
filtersNarrow which records it lists.
orderBy, directionThe starting sort: a field, and asc or desc in lower case. The record's name, ascending, by default.
secondary, marker, views, actions, inlineEditAs on a list card.

It loads 50 rows at a time as the reader scrolls, up to 2,000. Past that, search is the way in.

Record pages​

A record page shows one record: a header, then Details and Related, then any tabs of its own.

KeyWhat it takes
objectNameThe object. A portal has one record page per object, and links to a record of that object open it.
subtitleFields shown under the record's name.
highlightsFields shown in the header. A field with badges shows as a pill beside the name; the rest run along the bottom of the header. The name itself is already the header's title, so it is left out here.
actionsButtons in the header. See Actions.
sectionsThe Details sections. See below.
relatedList cards of the record's children, each with a parentField.
tabsMore tabs after Details and Related, each { "label": "...", "roles": [], "cards": [] }.
cardsCards of the page's own, placed by layout: a path, counts, a timeline.
layoutSee Layouts. A record page is header-2-1 when it names none.
stackedtrue runs each Details section, the related lists and each tab's cards down the page as cards, instead of behind tabs.

A section is:

{ "label": "Case information", "columns": 2, "roles": [], "fields": ["Subject", "Status", "Priority"] }

columns is 2 by default; use 1 for long text. A section whose only field has the section's own name does not repeat it. A record page with no sections shows one Details section of every field the person can read.

Layouts​

layout places a page's own cards in regions, the way a Lightning page template does: a full-width header over columns.

LayoutColumns, left to right
header-2-1main (two thirds), side
header-1-2side, main (two thirds)
header-1-1main, side
header-1-2-1left, main (half), right
header-1-1-1left, main, right

Each card names its region, and a card that names none goes in main. On a record page the record itself (Details, Related and tabs) leads main. A column with no cards is left out and the others share its width. When the page is too narrow for its columns they stack under the header, main first.

Only a page's own cards take a region. A region the layout does not have, or a region on a page with no layout, is a configuration error.


Cards​

Every card takes these keys:

KeyWhat it takes
kindlist (the default), metrics, breakdown, board, path, calendar, text, component or timeline.
labelThe card's title.
iconAn icon in a tinted tile beside the title.
accentA tone for the bar beside the title, or for the icon's tile.
rolesShow the card only to these roles.
regionWhere it goes in the page's layout.
widthfull spans the whole row of a page laid out as a grid.

list​

Records of one object.

{ "label": "Open cases", "objectName": "Case",
"columns": ["Subject", "Status", "Priority"], "secondary": "CaseNumber", "marker": "Priority",
"filters": [ { "field": "Status", "operator": "notEquals", "value": "Closed" } ],
"orderBy": "CreatedDate", "direction": "desc", "rows": 5, "pageKey": "cases" }
KeyWhat it takes
objectNameThe object.
columnsThe fields to show. The record's name and the first few readable fields when left out.
parentFieldOn a record page, the lookup that limits the list to this record's children.
filters, orderBy, directionAs on a list page.
rowsHow many rows to show before View all: 5 by default, up to 20.
pageKeyThe list page View all opens.
secondaryA field shown small under the first column.
markerA field with badges, whose tone dots the start of each row.
viewsFilter tabs over the list, each { "label": "Open", "filters": [] }. Their filters add to the card's own, and the first tab starts selected.
searchtrue puts a search box on the card.
actionsUpdates people can run on a row, or on every checked row. See Actions on a list.
inlineEdittrue lets people who may edit the records change them in place, like a spreadsheet.
loadMoretrue keeps loading rows as the reader scrolls, as a list page does, instead of stopping at rows.
alternateA second form of the same card. See Two forms of one card.

metrics​

A row of counts.

{ "kind": "metrics", "items": [
{ "label": "Open cases", "objectName": "Case", "icon": "case",
"filters": [ { "field": "Status", "operator": "notEquals", "value": "Closed" } ], "pageKey": "cases" },
{ "label": "Resolved", "objectName": "Case", "tone": "success", "percentOf": [],
"filters": [ { "field": "Status", "operator": "equals", "value": "Closed" } ] }
] }

Each item counts the records of objectName the person can reach that match its filters, up to 2,000, and takes:

KeyWhat it takes
labelWhat the count is of.
objectName, filtersWhat to count.
parentFieldOn a record page, count only this record's children.
toneA tone for the number.
iconAn icon in the corner.
percentOfShow the count as a share of the records these filters select. An empty list means every record in reach.
pageKeyThe page the count opens. On a record page, a count with parentField opens the children it counted.

breakdown​

How one object's records divide over a picklist or checkbox: a bar split by value, with a count and share for each.

{ "kind": "breakdown", "label": "Cases by status", "objectName": "Case", "field": "Status" }

It takes objectName, field, filters and, on a record page, parentField. A picklist can have at most 10 active values. Values the picklist no longer lists are counted together as Other, and blank ones on their own.

board​

Records in a lane for each value of a picklist, moved between lanes by dragging or from each card's menu. A move saves the field, under the same checks as any other edit.

{ "kind": "board", "label": "Cases by status", "objectName": "Case", "field": "Status",
"lanes": ["New", "On Hold", "Escalated"], "columns": ["Subject", "Priority"], "rows": 6 }
KeyWhat it takes
objectName, fieldThe object and the picklist.
lanesThe values to show as lanes, which must be active, at most 8. Every active value when left out, followed by an Other lane for records holding a value the picklist no longer lists. A picklist with more than 8 values has to name its lanes.
columnsThe first is each card's title; the rest show under it.
rowsHow many cards each lane shows before Show more: 10 by default, up to 50.
filters, orderBy, direction, parentFieldAs on a list card.
rankFieldA number field that keeps cards where people drop them. See below.

Without rankField a lane is sorted, so a card moved into it lands at the top. With rankField, a lane is ordered by that field, lowest first, and a card can be dropped anywhere in a lane, including its own, and stays there. Cards with no rank follow the ranked ones in the board's own order until someone moves them.

path​

On a record page, the record's picklist as a row of steps. A click chooses a step, and the button beside the path moves the record there.

{ "kind": "path", "field": "Status", "region": "header", "steps": ["New", "On Hold", "Escalated", "Closed"] }

steps are the values to show, which must be active, at most 12; every active value when left out. A value the record holds that is not among the steps shows as a step of its own after them. A path goes only on a record page.

calendar​

Records on a month or a week, placed by a date or date/time field. Each opens its record.

{ "kind": "calendar", "label": "Visits", "objectName": "Event", "field": "StartDateTime",
"columns": ["Subject", "Location"], "period": "month" }
KeyWhat it takes
objectName, fieldThe object and the date or date/time to place records by.
periodmonth (the default) or week. People can switch between them.
columnsThe first is each record's title; the rest show in the week view.
markerA field with badges, whose tone colors each record.
filters, parentFieldAs on a list card.

A date/time places a record on the viewer's own day, at their own time. A month or a week shows at most 200 records, and says so when there are more.

text​

A heading and rich text. With a tone, it is a callout in that color.

{ "kind": "text", "label": "Need help?", "tone": "info",
"body": "<p>Call us on <b>555 0100</b>, or <a href=\"?p=cases\">open a case</a>.</p>" }

The HTML is cleaned before it is shown, so scripts and styling do not survive.

component​

A Lightning web component of your own, or one from another Flourish package.

{ "kind": "component", "component": "c/eligibilityCard", "properties": { "heading": "Your eligibility" } }
KeyWhat it takes
componentc/name for your own, or namespace/name for one in a package.
propertiesValues handed to the component's public properties.
providerThe server class the component's portal calls may reach.

What the component is handed, and how it reaches data on the portal's terms, is in the Portal Developer Guide.

timeline​

The activity timeline, on a record page.

{ "kind": "timeline", "region": "side", "properties": { "density": "compact" } }

properties takes three of the timeline's own properties, each as text: density ("comfortable" or "compact"), pageSize ("30" by default, up to "100") and customStyle, whose JSON is written into the string.

Two forms of one card​

A list card or a board can name an alternate: the same records shown as the other kind. A switch in the card's header moves between the two, and each person's browser remembers which they last chose.

{ "kind": "board", "label": "Open requests", "objectName": "Case", "field": "Status",
"columns": ["Subject", "Priority"],
"alternate": { "label": "Requests", "objectName": "Case", "columns": ["Subject", "Status", "Priority"],
"views": [ { "label": "Open", "filters": [] } ], "inlineEdit": true } }

An alternate takes its place on the page from the card that names it, and cannot have an alternate of its own.


Actions​

On a record page​

A record page's actions are buttons in its header. The first two show as buttons and the rest go in a menu.

"actions": [
{ "key": "close", "label": "Close case", "kind": "update", "values": { "Status": "Closed" }, "confirm": "Close this case?" },
{ "key": "priority", "label": "Change priority", "kind": "update", "fields": ["Priority"] },
{ "key": "comment", "label": "Add comment", "kind": "create",
"objectName": "CaseComment", "parentField": "ParentId", "fields": ["CommentBody"] },
{ "key": "followup", "label": "Add a follow-up task", "kind": "apex", "className": "CaseActions" }
]
kindDoesKeys
updateSaves values on the record, after asking for fields in a form when it names any.values, fields
createOpens a form for a new child record of objectName, linked through parentField, asking for fields and saving values with it.objectName, parentField, fields, values
apexRuns a class of your own. See the Portal Developer Guide.className, access

Every action takes key, label, roles and confirm, a question asked before anything happens.

An update or a create is an ordinary edit. Every field it sets or asks for has to be one the person may change on that record, and the save is checked again, so an action can be a shortcut but never a way past a permission set. An action the person could not complete is not shown to them. Anything that should do more than the person could by hand, such as closing a case they may not otherwise edit, is an apex action, where your own code decides.

An update with fixed values offers Undo afterwards, when the page shows every field it changes.

Actions on a list​

A list card or list page can offer updates on a row, from the row's menu, or on every checked row, from a bar over the table. Name the key of an update already on the object's record page and the list offers it the same way, so an action is defined once:

"actions": [ { "key": "close" }, { "key": "escalate", "label": "Escalate all" } ]

A list action can override label and confirm, or be a full update of its own. Only updates can go on a list. A row the person cannot edit has no checkbox, and an action whose values a row already holds is left off that row's menu.


Icons​

Pages, cards and counts take an icon. Use one of the portal's own line icons by name, or a Salesforce utility icon such as utility:case. The portal's own are:

asset, board, calendar, call, campaign, case, chart, chat, comments, contract, donation, email, file, fund, gift, grant, home, household, lead, note, opportunity, organization, people, person, pie, priority, product, receipt, recurring, search, success, task, tip, volunteer

They take the color of whatever they sit in, as a utility icon does. A count or record page that names no icon takes a fitting one for common standard objects, and a card without one keeps its plain accent bar. Standard and custom Salesforce icons (standard:, custom:) are refused, since they bring a colored square of their own. A name that is not an icon is a configuration error.