An operation button runs an operation the server registered — a custom API, a custom action, one of Dataverse's own messages, or code of your own — and shows only to the users it is registered for. The button asks the server once which operations the signed-in user may run, and checks each record it would send against what the user may do to it, so it doesn't offer what the server would refuse. The server still judges every request when it is sent.
Register an operation in the server's Program.cs: AddOperationAccess for a Dataverse operation, AddOperationHandler for your own code. Inside AddPortalRole, WithOperationAccess and WithOperationHandler grant one to the role's holders. A registration says who may run the operation, the parameters a request may carry, the records they must point to and the outputs that come back.
// Program.cs (the server)
// A custom API for the holders of the Billing role, on an invoice they may change
builder.Services.AddPortalRole("Billing", role => role
.WithOperationAccess("ppp_IssueRefund", o => o
.OnRecord("Target", TableSecurityPermission.Write)
.AcceptingParameters("Amount")
.ReturningOutputs("RefundId")));
// Your own code, for any signed-in user, on a quote they may read
builder.Services.AddOperationHandler<SendQuoteHandler>("SendQuote", o => o
.ForSignedInUsers()
.OnRecord("Quote", TableSecurityPermission.Read)
.ReturningOutputs("SentTo"));
public sealed class SendQuoteHandler(IQuoteMailer mailer) : IPortalOperationHandler
{
public async Task<IReadOnlyDictionary<string, ColumnValueBase?>?> ExecuteAsync(
PortalOperationContext context, CancellationToken cancellationToken)
{
var quote = context.GetParameter<LookupValue>("Quote")!;
var sentTo = await mailer.SendAsync(quote.Value!.Value, cancellationToken);
return new Dictionary<string, ColumnValueBase?> { ["SentTo"] = new StringValue { Value = sentTo } };
}
}
ForEveryone(), ForSignedInUsers(), ForContactsMatching(fetchXml) and ForWebRoles(...) say who may run it; a role's registration is for the role's holders. Under Dataverse-native security, contacts hold web roles rather than portal roles, and a custom API is offered only to users whose security roles grant its execute privilege.Put an OperationButton where the operation belongs, naming the operation and the parameter its record goes in.
<RecordContext table="invoice" queryParameterName="id">
<OperationButton
operation="ppp_IssueRefund"
recordParameter="Target"
label="Issue refund"
icon={<Money20Regular />}
confirmationMessage="Issue a refund for this invoice?"
getParameters={askForAmount}
onCompleted={({ outputs }) => showRefund(outputs.RefundId)}
/>
</RecordContext><RecordContext TableName="invoice" QueryParameterName="id">
<OperationButton Operation="ppp_IssueRefund"
RecordParameter="Target"
Label="Issue refund"
Icon="@(new Icons.Regular.Size20.Money())"
ConfirmationMessage="Issue a refund for this invoice?"
GetParametersAsync="AskForAmountAsync"
OnCompleted="ShowRefund" />
</RecordContext>WhenNotAllowed (whenNotAllowed) to disable it instead. The answer is asked for once and kept while the app is open; a request the server refuses asks for it again.RecordParameter: Record, any record by reference, or else the record of the RecordContext the button sits in, once it is saved. The user must be able to act on it as the registration requires: a form's record says what the user may do to it, and a record named by reference is read for it.Parameters are sent with every request, and GetParametersAsync (getParameters) gathers more when the button is clicked, from a dialog, say; returning nothing cancels the click. ConfirmationMessage asks before the operation runs, and OnCompleted hands back its outputs.Label or its content, and its icon, tooltip and appearance as on any other button.The demo registers a few lines of its own code as an operation for everyone. Type a name and click the button: the server answers, and the page words the greeting.
In a grid's toolbar, OperationGridButton runs the operation once for each selected row, sending the row in RecordParameter, and then refreshes the grid. It is enabled while every selected row is one the user may act on, which each row says itself. A Dataverse operation's requests are sent in one transaction, so they all run or none do; your own code runs on one row at a time and stops at the first that fails.
<MainGrid tableName="invoice">
<GridToolbarItems>
<OperationGridButton
operation="ppp_ApproveInvoice"
recordParameter="Target"
label="Approve"
whenNotAllowed="disable"
/>
</GridToolbarItems>
</MainGrid><MainGrid TableName="invoice">
<ToolbarItems>
<OperationGridButton Operation="ppp_ApproveInvoice"
RecordParameter="Target"
Label="Approve"
WhenNotAllowed="OperationButtonBehavior.Disable" />
</ToolbarItems>
</MainGrid>Select some accounts and click Check: the demo's own code reads each one and answers its name. Approve is granted only to a portal role no visitor holds, so it shows disabled.
Account Name
Account Name Type the company or business name. | Account Number Account Number Type an ID number or code for the account to quickly search and identify the account in system views. | Main Phone Main Phone Type the main phone number for this account. | Address 1: City Address 1: City Type the city for the primary address. | Annual Revenue Annual Revenue Type the annual revenue for the account, used as an indicator in financial performance analysis. | Primary Contact Primary Contact Choose the primary contact for the account to provide quick access to contact details. | |
|---|---|---|---|---|---|---|
| Abernathy - MacGyver | KTWSLQHY | (491) 600-8029 | Tampa | $15,959,059.84 | Bradford Kiehn | |
| Abshire - Marks | 4EBO9F42 | (432) 879-3107 | Boston | $14,635,260.82 | Archibald Bailey | |
| Adams - Torphy | K6VNNUCN | (864) 842-2566 | Annapolis | $4,567,299.55 | Merle Vandervort | |
| Altenwerth, D'Amore and Mante | F9MN5BAZ | (125) 048-0015 | Gulfport | $3,839,082.81 | Heidi Reichel | |
| Ankunding and Sons | 92P2G0SF | (603) 147-5429 | Fargo | $32,724,448.89 | Fatima White | |
| Balistreri, Heller and Murphy | 3Y0AF481 | (909) 808-9886 | New Haven | $7,097,756.35 | Angie Kreiger | |
| Balistreri, Steuber and Schultz | FF5RCGXG | (435) 761-0052 | Hattiesburg | $35,943,517.75 | Jake Bogisich | |
| Bartoletti LLC | JBQ0L2O8 | (292) 573-7498 | Santa Fe | $766,813.66 | Cecil Sipes | |
| Bartoletti, Ratke and Beatty | WZIDG7K2 | (267) 075-5978 | Portland | $32,338,459.84 | Tabitha Morissette | |
| Barton, Koepp and Cronin | TGAL5Y8F | (821) 885-8434 | Norman | $43,550,293.98 | Marc Dibbert | |
| Bashirian - Hamill | ZS1XZLDL | (792) 135-0145 | Baltimore | $15,916,182.51 | Alexie Dach | |
| Bashirian - Weber | OC5PYOJC | (492) 789-5504 | Cranston | $23,269,960.31 | Emmet Krajcik | |
| Baumbach and Sons | X8V8TZRX | (670) 937-4364 | Ann Arbor | $4,319,879.33 | Charity Sawayn | |
| Baumbach, Maggio and Turner | V24ZA492 | (509) 251-9848 | Orlando | $25,479,745.81 | Cali McClure | |
| Beahan Inc | B9XXSIJM | (262) 429-4179 | Sioux Falls | $45,854,197.84 | Tressa Graham | |
| Berge and Sons | WO8OIGR6 | (165) 108-8947 | Topeka | $40,560,550.52 | Delphine Jerde | |
| Bergnaum Group | T3KB03RE | (381) 148-7032 | Rapid City | $32,406,150.23 | Dakota Ward | |
| Bergstrom and Sons | G99XNRDY | (182) 902-0206 | Casper | $48,880,601.83 | Grant Funk | |
| Bergstrom, Bins and Flatley | J1TXPJ9I | (409) 160-0657 | Pittsburgh | $21,005,421.54 | Blanche Gulgowski | |
| Blanda - Terry | 4L8RSVL9 | (190) 468-0033 | Vancouver | $33,567,160.56 | Fausto Pagac | |
| Bode LLC | F5IR66HK | (386) 496-6921 | Detroit | $1,800,033.15 | Kane Pollich | |
| Bogisich, Mitchell and Balistreri | PCVDL8PS | (267) 567-6606 | Manchester | $10,336,491.66 | Esperanza Luettgen | |
| Borer - Mohr | LHI1SSVO | (070) 539-8404 | Providence | $36,092,236.14 | Emelia Murray | |
| Botsford - Baumbach | 8JZK18K6 | (389) 188-6100 | Cranston | $318,662.34 | Maria Runte | |
| Botsford, Schroeder and Kassulke | U39NNCOJ | (960) 101-3753 | Sacramento | $19,016,940.28 | Raphael Bergnaum | |
| Boyer - Schaefer | EFJQXML0 | (473) 115-3249 | Fort Wayne | $48,601,111.30 | Moses Metz | |
| Boyle, Zulauf and Lakin | XXRF6GXU | (028) 071-4090 | Helena | $31,856,728.91 | Federico Hamill | |
| Brekke LLC | UCLPTB19 | (795) 385-0349 | Norman | $25,296,518.98 | Noemie Stracke | |
| Brekke, Feeney and Johns | VMGNL1M7 | (203) 861-9656 | Charleston | $13,632,447.58 | Kristin Koch | |
| Brown, Rohan and Mayer | EMOSYGOI | (479) 235-6497 | Fairbanks | $49,780,735.02 | Phoebe Legros | |
| Cassin, Kassulke and Schaefer | 5KSSVNW7 | (852) 978-3789 | Flagstaff | $29,260,487.45 | Dedrick Thiel | |
| Christiansen - Metz | GSZJ1ODJ | (183) 163-4723 | Reno | $25,227,451.85 | Hattie Schuster | |
| Cole, Tillman and Langworth | Y1JNB47Q | (289) 893-2488 | Dover | $13,838,171.56 | Jody Rohan | |
| Connelly, Cassin and Ledner | 69QI6R89 | (507) 196-5103 | Wilmington | $28,880,982.86 | Berneice Stracke | |
| Considine Inc | 1ZGMEMAV | (013) 094-8065 | Anchorage | $6,796,633.66 | Guido Hagenes | |
| Considine, Wilkinson and Spencer | HK18ZLOO | (772) 222-6147 | Jersey City | $8,490,647.93 | Douglas Prosacco | |
| Cormier - Hettinger | JFLKC0CK | (508) 849-8368 | Tucson | $14,771,052.63 | Nola Hand | |
| Cremin - Hodkiewicz | 4D30N08W | (757) 968-3487 | Fort Wayne | $27,239,848.21 | America Harber | |
| Cruickshank - Pollich | P3JT5LVY | (048) 698-7364 | Santa Fe | $32,195,480.36 | Joey Reilly | |
| Cruickshank, Auer and Schoen | TV06VSEA | (516) 585-5831 | Sioux Falls | $3,560,781.82 | Tiana Kiehn | |
| Cummerata Group | AJYD1N4I | (429) 577-7392 | Minneapolis | $20,579,317.84 | Gustave Zboncak | |
| Daniel LLC | ADF0WMN9 | (371) 118-7804 | Madison | $49,497,674.02 | Francisco Smitham | |
| Denesik, Skiles and Beier | 2ZMSATDW | (716) 332-8853 | Louisville | $25,958,417.48 | Dock Aufderhar | |
| Dibbert LLC | 4WT0EDV0 | (343) 448-8435 | Huntington | $24,379,660.89 | Elroy Leuschke | |
| Dickinson Inc | C7SI8E20 | (429) 201-5173 | Dover | $44,819,510.87 | Milton Ledner | |
| Dietrich and Sons | LAEDD52L | (189) 560-2423 | Hartford | $3,522,175.29 | Heath Kunde | |
| Donnelly - Sanford | 8SW26RNC | (738) 522-0182 | Fort Wayne | $37,819,037.93 | Betsy King | |
| Dooley - Nitzsche | NUCMFF7S | (574) 958-0441 | Charleston | $11,318,528.04 | Roman Dietrich | |
| Durgan and Sons | CKOSAHML | (332) 942-8325 | Saint Paul | $5,901,769.33 | Connor Marks | |
| Ebert - Wyman | HJBQWXIR | (753) 557-3136 | Los Angeles | $15,782,605.89 | Zetta Nolan |
The buttons ask GET /api/permissions/operations, which answers for the signed-in user alone: every operation they may run, or just the ones you name, with what a request for each may carry and the records it must point to. Your own UI can ask the same through IOperationAccessCache or IPowerPortalsProService.GetOperationAccessForCurrentUserAsync on Blazor, and useOperationAccess or ppp.retrieveOperationAccessAsync on React.