[go: up one dir, main page]

CodeAppJS Docs

Support docs for the JavaScript library CodeApp.js | Current version: 1.0.2

Power Apps Code Apps — JavaScript

Power Apps empowers developers of all skill sets, including those building web apps in integrated developer environments (IDEs) like Visual Studio Code, to efficiently build and run business apps on a managed platform.

Code apps let developers bring Power Apps capabilities into custom web apps built in a code‑first IDE. You can develop locally and run the same app in Power Platform. Build with popular frameworks (React, Vue, and others) while keeping full control over your UI and logic.

Key features include:
  • Microsoft Entra authentication and authorization
  • Access to Power Platform data sources and 1,500+ connectors, callable directly from JavaScript
  • Easy publishing and hosting of line-of-business web apps in Power Platform
  • Adherence to your organization's Managed Platform policies (app sharing limits, Conditional Access, Data Loss Prevention, and so on)
  • Simplified deployment and application lifecycle management (ALM)

The managed platform accelerates safe, rapid innovation, and when ready, apps can be deployed to dedicated production environments.

Code Apps by default use React. CodeAppJs replaces React with vanilla JavaScript.

Code App running in Power Platform

Starter app files and power.config.json explained

codeapp.js
File Purpose
power.config.json Main app manifest for display name, environment, build output, connection references, and Dataverse sources.
dist/index.html Bootstraps the app shell, sets the import map for @microsoft/power-apps/data, and loads index.js.
dist/index.js Default JavaScript entry point where app startup logic lives.
dist/codeapp.js Shared runtime helper for environment variables, Dataverse CRUD, and connector plumbing.
dist/power-apps-data.js Bundled SDK runtime copied from the npm package for browser use inside the starter app.

src/generated and .power/ directories

Running pac code add-data-source generates typed connector files under src/generated/. The .power/ folder is created automatically by the PAC CLI and contains internal platform metadata. Both folders are part of the project structure and should be committed to source control.

Path Purpose
src/generated/index.ts Barrel export that re-exports generated model namespaces and service classes for every added connector.
src/generated/models/*.ts Generated request/response types, enums, and metadata interfaces for each connector (Outlook, Users, Groups, Jira, Key Vault).
src/generated/services/*.ts Generated static service wrappers that call getClient(dataSourcesInfo) and expose typed connector operations.
.power/ PAC CLI internal metadata directory. Contains platform configuration and should be committed alongside the project.
Project folder structure in VS Code

Top-level keys in the starter config

Key What it does
appDisplayName The name shown for the app when it is deployed.
description Short app description for documentation and deployment metadata.
environmentId The target Power Platform environment id used when pushing the app.
buildPath The folder PAC packages and deploys. In the starter app this is ./dist.
buildEntryPoint The HTML file PAC treats as the app entry file, usually index.html.
logoPath Path or placeholder for the app icon used in deployment metadata.
localAppUrl The local URL used during development. Update it to match the server you actually use.
region The Power Platform region, shown here as prod in the starter config.
connectionReferences Connector bindings such as Dataverse, Outlook, SharePoint, Groups, Users, Teams, Jira, Key Vault, or SQL.
databaseReferences Dataverse table declarations used to generate service and model code.
  • id is the connector API path such as shared_office365 or shared_sharepointonline.
  • displayName is the label shown for the connector.
  • dataSources contains the connector names the runtime expects.
  • dataSets, authenticationType, and sharedConnectionId are available when needed by the connection.
  • default.cds is the Dataverse container used by the examples.
  • dataSources maps table aliases to entitySetName and logicalName.
  • isHidden controls whether a data source is hidden.
  • actions and databaseDetails appear when the config includes them.

The repo workflow is npm plus PAC CLI

npm install
npm start
npm run start:codeapp

`npm install` rebuilds local `power-apps-data.js` files from the published SDK package so samples stay aligned with npm.

pac auth create
pac auth list
pac env select --environment "environment-id"
pac connection list
pac connection create --name "connection-name" --application-id "app-id" --client-secret "secret" --tenant-id "tenant-id"
pac code add-data-source -a "connection-name" -c "connection-id"
pac code push --solutionName YourSolutionName

The readme makes PAC the deployment path, with auth, environment selection, connection creation, data source registration, and app push as the core flow.

What each command is for

Command Use
npm install Install dependencies and rebuild browser SDK files used by the starter app and examples.
npm start Serve the whole repo on port 4173 so samples can be opened directly.
npm run start:codeapp Open straight to the starter app under `codeApp/dist/`.
pac auth create Create a login profile against a Power Platform environment.
pac env select Choose the active environment profile for later PAC commands.
pac connection list Inspect the connector connections available in the selected environment.
pac code add-data-source Add connector or Dataverse source details used by the app.
pac code push Package and deploy the app build output into the target solution.

Debug code apps with browser dev tools

codeapp.js
  1. To support debugging, all API interactions can be shown in a debug panel.
  2. Enabling debugger will add a small button in the top right of the app.
  3. Clicking will open the panel with detailed information about API calls and responses.
  4. Each response can be copied to clipboard by clicking the copy icon
import { enableDebugger } from './codeapp.js';
enableDebugger();
Debug panel open with API call details

Read Dataverse environment variables through the generic codeapp.js Dataverse APIs

codeapp.js & Dataverse
  1. Create the environment variables in the maker portal under Solutions > Environment Variables.
  2. Use the real Dataverse schema names when wiring the app. Do not invent or guess them.
  3. Add both environment variable tables to `databaseReferences.default.cds.dataSources` in `power.config.json`.
  4. Register `environmentvariabledefinitions` and `environmentvariablevalues`, then query them with the same Dataverse helpers used for any other table.
  • `environmentvariabledefinition` stores the schema name, default value, and variable type metadata.
  • `environmentvariablevalue` stores the current environment-specific value and links back to the definition.
  • If a value row exists, use that first. If it does not, fall back to the definition row's `defaultvalue`.
  • The app reads the Dataverse tables at runtime. There is no separate `environmentVar.js` file in this repo anymore.

Add both environment variable tables to the Dataverse data source map

{
  "databaseReferences": {
    "default.cds": {
      "dataSources": {
        "environmentvariabledefinitions": {
          "entitySetName": "environmentvariabledefinitions",
          "logicalName": "environmentvariabledefinition",
          "isHidden": false
        },
        "environmentvariablevalues": {
          "entitySetName": "environmentvariablevalues",
          "logicalName": "environmentvariablevalue",
          "isHidden": false
        }
      }
    }
  }
}
import { registerTable, listItems } from './codeapp.js';

registerTable('environmentvariabledefinitions', 'environmentvariabledefinitionid');
registerTable('environmentvariablevalues', 'environmentvariablevalueid');

async function loadEnvironmentVariable(sSchemaName) {
  const { entities: aDefinitions } = await listItems(
    'environmentvariabledefinitions',
    'environmentvariabledefinitionid',
    {
      filter: "schemaname eq '" + sSchemaName + "'",
      select: ['environmentvariabledefinitionid', 'schemaname', 'defaultvalue', 'type'],
      top: 1
    }
  );

  const oDefinition = aDefinitions[0];
  if (!oDefinition) {
    throw new Error('Environment variable not found: ' + sSchemaName);
  }

  const { entities: aValues } = await listItems(
    'environmentvariablevalues',
    'environmentvariablevalueid',
    {
      filter: '_environmentvariabledefinitionid_value eq ' + oDefinition.environmentvariabledefinitionid,
      select: ['value'],
      top: 1
    }
  );

  return aValues[0]?.value || oDefinition.defaultvalue || '';
}

This mirrors the Dataverse table relationship directly: definition first, value second, then default fallback.

Generated connector sources plus runtime helper examples

cli & power-apps-data.js
pac code add-data-source -a "connection-name" -c "connection-id"

Run `pac code add-data-source` after creating the connector connection. PAC uses the selected connector metadata to generate typed source files under `src/generated` for connectors such as Outlook, Office 365 Users, and Office 365 Groups.

  • Generated connector files are the source of truth for operation names, parameter shapes, and return models for Microsoft 365 connectors.
  • Dataverse, SharePoint, Teams, Jira, and Azure Key Vault do not need these generated `src/generated` files in this repo because they already use handwritten helpers and configuration-based runtime support.
  • For generated connectors, keep the PAC output aligned with the connection metadata instead of hardcoding operation paths yourself.
File Purpose
`src/generated/index.ts` Barrel export that re-exports the generated model namespaces and service classes for every added connector.
`src/generated/models/Office365OutlookModel.ts` Generated Outlook request and response types, enums, and metadata interfaces used by the Outlook service methods.
`src/generated/services/Office365OutlookService.ts` Generated static service wrapper that calls `getClient(dataSourcesInfo)` and exposes typed Outlook connector operations through `executeAsync`.
`src/generated/models/Office365UsersModel.ts` Generated user profile, people, document, and photo types returned by the Office 365 Users connector.
`src/generated/services/Office365UsersService.ts` Generated service class with typed methods such as `MyProfile_V2`, `SearchUserV2`, and other Office 365 Users operations.
`src/generated/models/Office365GroupsModel.ts` Generated interfaces for group lists, membership payloads, calendar requests, and other Office 365 Groups connector shapes.
`src/generated/services/Office365GroupsService.ts` Generated service class with typed wrappers for group membership, events, and HTTP request operations exposed by the connector.
`src/generated/models/AzureKeyVaultModel.ts` Generated interfaces for Azure Key Vault secrets, keys, and certificates.
`src/generated/services/AzureKeyVaultService.ts` Generated service class with typed wrappers for Azure Key Vault operations.
`src/generated/models/JiraModel.ts` Generated interfaces for Jira issues, projects, and other Jira connector shapes.
`src/generated/services/JiraService.ts` Generated service class with typed wrappers for Jira operations.
"connectionReferences": {
  "dataverse": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps",
    "displayName": "Microsoft Dataverse",
    "dataSources": ["commondataserviceforapps"],
    "authenticationType": null,
    "sharedConnectionId": null,
    "dataSets": {}
  }
}
  • `dist/dataverse.js` registers tables before the first SDK call and reuses a shared client.
  • The skill file also requires matching `databaseReferences.default.cds.dataSources` entries for every table you touch.
  • Use entity set names such as `cr_contacts` in code, not display names.

All exported Dataverse and shared helpers

  • enableDebugger() - enableDebugger();
  • getEnvironmentVariable(schemaName) - const sSite = await getEnvironmentVariable('wd_sharepointsite');
  • registerTable(tableName, primaryKey) - registerTable('cr_contacts', 'cr_contactid');
  • createItem(tableName, primaryKey, record) - await createItem('cr_contacts', 'cr_contactid', { cr_name: 'Jane Doe' });
  • getItem(tableName, primaryKey, id, select) - await getItem('cr_contacts', 'cr_contactid', sContactId, ['cr_name', 'createdon']);
  • listItems(tableName, primaryKey, options) - await listItems('cr_contacts', 'cr_contactid', { filter: "cr_name eq 'Jane Doe'", select: ['cr_name'], top: 5 });
  • updateItem(tableName, primaryKey, id, changedFields) - await updateItem('cr_contacts', 'cr_contactid', sContactId, { cr_name: 'Jane Smith' });
  • deleteItem(tableName, primaryKey, id) - await deleteItem('cr_contacts', 'cr_contactid', sContactId);
  • callUnboundAction(tableName, primaryKey, actionName, params) - await callUnboundAction('', '', 'GrantAccess', oParams);
  • whoAmI() - const sUserId = await whoAmI();

The Dataverse helper does not define a fixed action map like the managed connectors. Instead it wraps generic CRUD plus custom unbound actions over the configured tables in databaseReferences.

"connectionReferences": {
  "sharepointonline": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
    "displayName": "SharePoint",
    "dataSources": ["sharepointonline"],
    "authenticationType": null,
    "sharedConnectionId": null,
    "dataSets": {}
  }
}
  • `dist/sharepoint.js` supports both list-id calls and list-name calls.
  • The skill file treats site URL plus list id, site URL plus list name, and environment variables as the main usage patterns.
  • Pass the plain site URL from your app code; the helper encodes it internally.

Every exported SharePoint helper

  • callSharePointOperation(operationName, parameters) - await callSharePointOperation('GetDataSetsMetadata', { siteUrl: encodeURIComponent('https://contoso.sharepoint.com/sites/demo') });
  • sendHttpRequest({ method, uri, headers, body }) - await sendHttpRequest({ method: 'GET', uri: 'https://contoso.sharepoint.com/sites/demo/_api/web/lists', headers: { Accept: 'application/json;odata=nometadata' } });
  • getItems(siteUrl, listId, options) - await getItems('https://contoso.sharepoint.com/sites/demo', '{12345678-abcd-1234-abcd-123456789012}', { top: 20, orderBy: 'Modified desc' });
  • getSpItem(siteUrl, listId, itemId) - await getSpItem('https://contoso.sharepoint.com/sites/demo', '{12345678-abcd-1234-abcd-123456789012}', 42);
  • createSpItem(siteUrl, listId, fields) - await createSpItem('https://contoso.sharepoint.com/sites/demo', '{12345678-abcd-1234-abcd-123456789012}', { Title: 'New task' });
  • updateSpItem(siteUrl, listId, itemId, changedFields) - await updateSpItem('https://contoso.sharepoint.com/sites/demo', '{12345678-abcd-1234-abcd-123456789012}', 42, { Status: 'Done' });
  • deleteSpItem(siteUrl, listId, itemId) - await deleteSpItem('https://contoso.sharepoint.com/sites/demo', '{12345678-abcd-1234-abcd-123456789012}', 42);
  • listTables(siteUrl) - await listTables('https://contoso.sharepoint.com/sites/demo');
  • listLibrary(siteUrl) - await listLibrary('https://contoso.sharepoint.com/sites/demo');
  • createFile(siteUrl, libraryName, fileName, fileContent) - await createFile('https://contoso.sharepoint.com/sites/demo', '/Shared Documents', 'notes.txt', 'Draft notes');
  • updateFile(siteUrl, fileId, fileContent) - await updateFile('https://contoso.sharepoint.com/sites/demo', '01ABCDEF234567...', 'Updated notes');
  • deleteFile(siteUrl, fileId) - await deleteFile('https://contoso.sharepoint.com/sites/demo', '01ABCDEF234567...');
  • moveFile(siteUrl, sourceFileId, destinationFolderPath, newFileName) - await moveFile('https://contoso.sharepoint.com/sites/demo', '01ABCDEF234567...', '/Shared Documents/Archive', 'notes-archive.txt');
  • getFileMetadata(siteUrl, fileId) - await getFileMetadata('https://contoso.sharepoint.com/sites/demo', '01ABCDEF234567...');

The sample wrapper does not export list-name helpers such as getItemByName. Resolve the list id first with listTables or call the raw connector action through callSharePointOperation(...).

All SharePoint connector actions wired by the wrapper

  • GetItems
  • GetItem
  • PostItem
  • PatchItem
  • DeleteItem
  • GetTables
  • GetDataSetsMetadata
  • CreateFile
  • UpdateFile
  • DeleteFile
  • MoveFile
  • GetFileMetadata
  • HttpRequest
"connectionReferences": {
  "Office365Outlook": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_office365",
    "displayName": "Office 365 Outlook",
    "dataSources": ["Office365Outlook"],
    "authenticationType": null,
    "sharedConnectionId": null,
    "dataSets": {}
  }
}
  • `dist/outlook.js` currently uses `DATA_SOURCE = 'Office365Outlook'` with handwritten helper wrappers.
  • The skill file anchors the connector id to `shared_office365` and recommends generated metadata as the source of truth for paths and parameters.
  • The helper covers both mail and calendar actions, not just inbox reads.

Every exported mail helper

  • callOutlookOperation(operationName, parameters) - await callOutlookOperation('ContactPatchItem_V2', { folder: 'Contacts', id: sContactId, item: { GivenName: 'Alex' } });
  • sendEmail(oOptions) - await sendEmail({ to: 'alex@contoso.com', subject: 'Weekly summary', body: '<p>Status attached.</p>' });
  • getEmail(messageId, oOptions) - await getEmail(sMessageId, { includeAttachments: true });
  • draftEmail(oOptions) - await draftEmail({ to: 'alex@contoso.com', subject: 'Draft status', body: 'Review this first.' });
  • updateDraftEmail(messageId, oOptions) - await updateDraftEmail(sDraftId, { subject: 'Updated subject', body: 'Revised content.' });
  • sendDraftEmail(messageId) - await sendDraftEmail(sDraftId);
  • forwardEmail(messageId, oOptions) - await forwardEmail(sMessageId, { to: 'team@contoso.com', comment: 'Please review.' });
  • replyToEmail(messageId, oOptions) - await replyToEmail(sMessageId, { comment: 'Received, thanks.', replyAll: true });
  • listEmails(oOptions) - await listEmails({ folderId: 'Inbox', top: 10, fetchOnlyUnread: true, searchQuery: 'from:alex' });
  • sendFromSharedMailbox(sharedMailbox, oOptions) - await sendFromSharedMailbox('support@contoso.com', { to: 'customer@contoso.com', subject: 'Case update', body: 'We have an update.' });
  • moveEmail(messageId, destinationFolderId, oOptions) - await moveEmail(sMessageId, 'Archive');
  • deleteEmail(messageId, oOptions) - await deleteEmail(sMessageId, { mailboxAddress: 'shared@contoso.com' });
  • markEmailAsRead(messageId, oOptions) - await markEmailAsRead(sMessageId, { isRead: true });
  • updateEmailFlag(messageId, oOptions) - await updateEmailFlag(sMessageId, { flagStatus: 'flagged' });
  • getEmailAttachment(messageId, attachmentId, oOptions) - await getEmailAttachment(sMessageId, sAttachmentId, { mailboxAddress: 'shared@contoso.com' });
  • listOutlookCategories() - await listOutlookCategories();
  • assignOutlookCategory(messageId, category) - await assignOutlookCategory(sMessageId, 'Blue category');
  • assignOutlookCategoryBulk(messageIds, categoryName) - await assignOutlookCategoryBulk([sMessageId1, sMessageId2], 'Follow up');
  • callOutlookHttpRequest({ uri, method, body, contentType, customHeaders }) - await callOutlookHttpRequest({ method: 'GET', uri: 'https://graph.microsoft.com/v1.0/me/messages?$top=5' });
  • manageOutlookEmails(queryRequest, sessionId) - await manageOutlookEmails({ prompt: 'Summarize unread email from today' }, 'session-1');

Events, calendars, rooms, and meeting suggestions

  • createEvent(oOptions) - await createEvent({ subject: 'Sprint review', start: '2026-03-14T16:00:00Z', end: '2026-03-14T17:00:00Z', attendees: ['alex@contoso.com'] });
  • listEvents(oOptions) - await listEvents({ calendarId: 'Calendar', top: 10, orderBy: 'start/dateTime asc' });
  • editEvent(eventId, changedFields, calendarId) - await editEvent(sEventId, { location: 'Room 301', body: 'Updated agenda' }, 'Calendar');
  • deleteEvent(eventId, calendarId, oOptions) - await deleteEvent(sEventId, 'Calendar');
  • listCalendars(oOptions) - await listCalendars({ top: 25, orderBy: 'name asc' });
  • getEvent(eventId, calendarId, oOptions) - await getEvent(sEventId, 'Calendar');
  • getCalendarView(oOptions) - await getCalendarView({ calendarId: 'Calendar', startDateTimeUtc: '2026-04-01T00:00:00Z', endDateTimeUtc: '2026-04-02T00:00:00Z' });
  • respondToEventInvite(eventId, response, oOptions) - await respondToEventInvite(sEventId, 'accept', { comment: 'See you there.' });
  • listRoomLists() - await listRoomLists();
  • listRooms() - await listRooms();
  • listRoomsInRoomList(roomList) - await listRoomsInRoomList('building1@contoso.com');
  • findMeetingTimes(oRequest) - await findMeetingTimes({ attendees: ['alex@contoso.com'], timeConstraint: { activityDomain: 'work' } });
  • setAutomaticReplies(oSettings) - await setAutomaticReplies({ automaticRepliesSetting: { status: 'alwaysEnabled', internalReplyMessage: 'On leave' } });
  • getMailTips(oRequest) - await getMailTips({ mailboxAddress: 'alex@contoso.com' });
  • manageOutlookMeetings(queryRequest, sessionId) - await manageOutlookMeetings({ prompt: 'Find a 30 minute slot tomorrow afternoon' }, 'session-1');

Contact folder CRUD, including ContactPatchItem_V2

  • listContactFolders() - await listContactFolders();
  • listContacts(folderId, oOptions) - await listContacts('Contacts', { top: 25, orderBy: 'displayName asc' });
  • getContact(folderId, contactId, oOptions) - await getContact('Contacts', sContactId);
  • createContact(folderId, contact) - await createContact('Contacts', { GivenName: 'Alex', Surname: 'Wilber', EmailAddresses: [{ Address: 'alex@contoso.com' }] });
  • updateContact(folderId, contactId, contact) - await updateContact('Contacts', sContactId, { GivenName: 'Alex', CompanyName: 'Contoso' });
  • deleteContact(folderId, contactId, oOptions) - await deleteContact('Contacts', sContactId);
  • manageOutlookContacts(queryRequest, sessionId) - await manageOutlookContacts({ prompt: 'Find contacts at Contoso' }, 'session-1');

All Outlook connector actions wired by the wrapper

  • GetEmailsV3
  • SendEmailV2
  • ForwardEmail
  • ReplyToV3
  • MoveV2
  • DeleteEmail
  • SharedMailboxSendEmailV2
  • V4CalendarGetItems
  • V4CalendarPostItem
  • V4CalendarPatchItem
  • CalendarDeleteItem
  • GetEmailsV2
  • GetEmail
  • GetEmailV2
  • SendEmail
  • DraftEmail
  • UpdateDraftEmail
  • SendDraftEmail
  • DeleteEmail_V2
  • ForwardEmail_V2
  • ReplyToV2
  • Flag
  • Flag_V2
  • MarkAsRead
  • MarkAsRead_V2
  • MarkAsRead_V3
  • GetAttachment
  • GetAttachment_V2
  • AssignCategory
  • AssignCategoryBulk
  • GetOutlookCategoryNames
  • SharedMailboxSendEmail
  • V3CalendarGetItem
  • GetEventsCalendarViewV3
  • RespondToEvent_V2
  • CalendarGetTables_V2
  • ContactGetTablesV2
  • ContactGetItems_V2
  • ContactGetItem_V2
  • ContactPostItem_V2
  • ContactPatchItem_V2
  • ContactDeleteItem_V2
  • GetRoomLists_V2
  • GetRooms_V2
  • GetRoomsInRoomList_V2
  • FindMeetingTimes_V2
  • SetAutomaticRepliesSetting
  • SetAutomaticRepliesSetting_V2
  • GetMailTips
  • GetMailTips_V2
  • HttpRequest
  • mcp_EmailsManagement
  • mcp_MeetingManagement
  • mcp_ContactsManagement
"connectionReferences": {
  "office365groups": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_office365groups",
    "displayName": "Office 365 Groups",
    "dataSources": ["office365groups"],
    "authenticationType": null,
    "sharedConnectionId": null,
    "dataSets": {}
  }
}
  • `dist/office365groups.js` defines explicit connector metadata for group listing, membership, and raw HTTP requests.
  • The shared connections skill calls out exact operation metadata as mandatory for managed connectors.
  • Keep the key and `DATA_SOURCE` aligned with the generated schema you sync into the app.

Every exported Office 365 Groups helper

  • callGroupsOperation(operationName, parameters) - await callGroupsOperation('ListOwnedGroups_V3', { extractSensitivityLabel: true });
  • openGroupsHttpRequest({ method, uri, headers, body, contentType, customHeaders }) - await openGroupsHttpRequest({ method: 'GET', uri: 'https://graph.microsoft.com/v1.0/me/memberOf' });
  • listMyGroups(oOptions) - await listMyGroups({ version: 3, extractSensitivityLabel: true });
  • listGroupMembers(groupId, oOptions) - await listGroupMembers('00000000-0000-0000-0000-000000000000', { top: 25 });
  • listOwnedGroups(oOptions) - await listOwnedGroups({ version: 2 });
  • listGroups(oOptions) - await listGroups({ filter: "startswith(displayName,'Team')", top: 25 });
  • onGroupMembershipChange(groupId, oOptions) - await onGroupMembershipChange('00000000-0000-0000-0000-000000000000', { select: ['id', 'displayName'] });
  • addMemberToGroup(userUpn, groupId) - await addMemberToGroup('alex@contoso.com', '00000000-0000-0000-0000-000000000000');
  • removeMemberFromGroup(userUpn, groupId) - await removeMemberFromGroup('alex@contoso.com', '00000000-0000-0000-0000-000000000000');
  • createGroupEvent(groupId, oOptions) - await createGroupEvent('00000000-0000-0000-0000-000000000000', { subject: 'Quarterly review', start: '2026-04-10T15:00:00Z', end: '2026-04-10T16:00:00Z' });
  • updateGroupEvent(eventId, oOptions, groupId) - await updateGroupEvent(sEventId, { groupId: '00000000-0000-0000-0000-000000000000', subject: 'Updated review' });
  • deleteGroupEvent(eventId, groupId) - await deleteGroupEvent(sEventId, '00000000-0000-0000-0000-000000000000');
  • onNewGroupEvent(groupId) - await onNewGroupEvent('00000000-0000-0000-0000-000000000000');
  • listDeletedGroups() - await listDeletedGroups();
  • restoreDeletedGroup(groupId) - await restoreDeletedGroup('00000000-0000-0000-0000-000000000000');
  • listDeletedGroupsByOwner(userId) - await listDeletedGroupsByOwner('alex@contoso.com');

All Office 365 Groups connector actions wired by the wrapper

  • ListGroupMembers
  • OnGroupMembershipChange
  • AddMemberToGroup
  • ListOwnedGroups
  • ListOwnedGroups_V2
  • ListOwnedGroups_V3
  • ListGroups
  • CreateCalendarEvent
  • CreateCalendarEventV2
  • CalendarDeleteItem_V2
  • UpdateCalendarEvent
  • RemoveMemberFromGroup
  • OnNewEvent
  • HttpRequestV2
  • HttpRequest
  • ListDeletedGroups
  • RestoreDeletedGroup
  • ListDeletedGroupsByOwner
"connectionReferences": {
  "office365users": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_office365users",
    "displayName": "Office 365 Users",
    "dataSources": ["office365users"],
    "authenticationType": null,
    "sharedConnectionId": null,
    "dataSets": {}
  }
}
  • `dist/office365users.js` exposes profile, org chart, photo, search, and raw HTTP request helpers.
  • The skill file confirms `UserPhoto_V2` returns JPEG content and uses `responseInfo` metadata.
  • No `databaseReferences` block is needed for this connector.

Every exported Office 365 Users helper

  • callUsersOperation(operationName, parameters) - await callUsersOperation('MyProfile_V2', {});
  • openUsersHttpRequest({ method, uri, headers, body, contentType, customHeaders }) - await openUsersHttpRequest({ method: 'GET', uri: 'https://graph.microsoft.com/v1.0/me' });
  • updateMyProfile(oProfile) - await updateMyProfile({ city: 'Sydney', jobTitle: 'Architect' });
  • getMyProfile(oOptions) - await getMyProfile({ select: ['displayName', 'mail', 'jobTitle'] });
  • getUserProfile(userId, oOptions) - await getUserProfile('alex@contoso.com', { select: ['displayName', 'department'] });
  • getManager(userId, oOptions) - await getManager('alex@contoso.com', { select: ['displayName', 'mail'] });
  • getDirectReports(userId, oOptions) - await getDirectReports('alex@contoso.com', { top: 10, select: ['displayName', 'mail'] });
  • getMyTrendingDocuments(oOptions) - await getMyTrendingDocuments({ filter: "resourceVisualization/title ne null" });
  • getRelevantPeople(userId) - await getRelevantPeople('alex@contoso.com');
  • updateMyPhoto(oBodyOrOptions, contentType) - await updateMyPhoto({ body: sBase64Jpeg, contentType: 'image/jpeg' });
  • getUserPhotoMetadata(userId) - await getUserPhotoMetadata('alex@contoso.com');
  • getUserPhoto(userId) - await getUserPhoto('alex@contoso.com');
  • getTrendingDocuments(userId, oOptions) - await getTrendingDocuments('alex@contoso.com', { filter: "lastModifiedBy ne null" });
  • searchForUsers(oOptions) - await searchForUsers({ searchTerm: 'Alex', top: 10, skip: 0 });

All Office 365 Users connector actions wired by the wrapper

  • UpdateMyProfile
  • MyProfile_V2
  • UpdateMyPhoto
  • MyTrendingDocuments
  • RelevantPeople
  • UserProfile_V2
  • UserPhotoMetadata
  • Manager_V2
  • DirectReports_V2
  • UserPhoto_V2
  • TrendingDocuments
  • SearchUserV2
  • SearchUser_V2
  • HttpRequest
"connectionReferences": {
  "teams": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_teams",
    "displayName": "Microsoft Teams",
    "dataSources": ["teams"],
    "authenticationType": null,
    "sharedConnectionId": null,
    "dataSets": {}
  }
}
  • `codeapp.js` resolves the Teams connector against `teams`, `Teams`, `microsoftteams`, and `MicrosoftTeams`.
  • The helper layer covers teams, channels, membership, chats, mention tokens, notifications, and raw Graph HTTP requests.
  • Use the wrapper helpers for common team and channel operations instead of hardcoding connector routes in app code.

Every exported Teams helper

  • callTeamsOperation(operationName, parameters) - await callTeamsOperation('GetAllTeams', {});
  • sendTeamsGraphHttpRequest({ method, uri, headers, body }) - await sendTeamsGraphHttpRequest({ method: 'GET', uri: 'https://graph.microsoft.com/v1.0/me/joinedTeams' });
  • listTeams() - await listTeams();
  • listChannels(teamId) - await listChannels('00000000-0000-0000-0000-000000000000');
  • getTeam(teamId) - await getTeam('00000000-0000-0000-0000-000000000000');
  • getChannelDetails(teamId, channelId) - await getChannelDetails('00000000-0000-0000-0000-000000000000', '19:demochannel@thread.tacv2');
  • addMemberToTeam(teamId, body) - await addMemberToTeam('00000000-0000-0000-0000-000000000000', { user: 'alex@contoso.com' });
  • addMemberToChannel(teamId, channelId, body) - await addMemberToChannel('00000000-0000-0000-0000-000000000000', '19:demochannel@thread.tacv2', { user: 'alex@contoso.com' });
  • getUserMentionToken(userId) - await getUserMentionToken('alex@contoso.com');
  • getTeamTagMentionToken(teamId, tagId) - await getTeamTagMentionToken('00000000-0000-0000-0000-000000000000', 'tag-id');
  • listChats({ top, skip }) - await listChats({ top: 10, skip: 0 });
  • listMembers(teamId, channelId) - await listMembers('00000000-0000-0000-0000-000000000000', '19:demochannel@thread.tacv2');
  • postFeedNotification({ groupId, body }) - await postFeedNotification({ groupId: '00000000-0000-0000-0000-000000000000', body: { previewText: 'Build complete' } });
  • postCardInChatOrChannel({ poster, location, body }) - await postCardInChatOrChannel({ location: 'Channel', body: { messageBody: 'Deployment finished' } });
  • postMessageInChatOrChannel({ poster, location, body }) - await postMessageInChatOrChannel({ location: 'Chat', body: { recipient: 'alex@contoso.com', messageBody: 'Hello from CodeAppJS' } });

All Teams connector actions wired by the wrapper

  • GetAllTeams
  • GetChannelsForGroup
  • GetTeam
  • GetChannel
  • AddMemberToTeam
  • AddMemberToChannel
  • AtMentionUser
  • AtMentionTag
  • ListChats
  • ListMembers
  • PostUserNotification
  • PostChannelNotification
  • PostCardToConversation
  • PostMessageToConversation
  • HttpRequest
"connectionReferences": {
  "jira": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_jira",
    "displayName": "Jira",
    "dataSources": ["jira"],
    "authenticationType": "APIToken",
    "sharedConnectionId": null,
    "dataSets": {}
  }
}
  • `codeapp.js` resolves Jira against `jira`, `Jira`, and `JIRA` data source names.
  • Issue, filter, task, and user helpers pass the Jira site through the `jiraInstance` argument, which maps to the required `X-Request-Jirainstance` header.
  • The wrapper set includes comments, issue create and edit, current-user lookup, project listing, filters, and async task helpers.

Every exported Jira issue helper

  • callJiraOperation(operationName, parameters) - await callJiraOperation('ListProjects_V2', {});
  • addJiraComment(issueKey, body, jiraInstance) - await addJiraComment('DEMO-42', 'Please review this issue.', 'https://contoso.atlassian.net');
  • createJiraIssue(oOptions) - await createJiraIssue({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO', summary: 'Connector docs update', issueTypeId: '10001' });
  • createJiraIssueV3(oOptions) - await createJiraIssueV3({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO', issueTypeIds: ['10001'], summary: 'Create via V3' });
  • editJiraIssue(issueIdOrKey, oOptions) - await editJiraIssue('DEMO-42', { jiraInstance: 'https://contoso.atlassian.net', fields: { summary: 'Updated summary' } });
  • editJiraIssueV2(issueIdOrKey, oOptions) - await editJiraIssueV2('DEMO-42', { jiraInstance: 'https://contoso.atlassian.net', notifyUsers: true, fields: { description: 'Updated body' } });
  • updateJiraIssue(issueKey, oOptions) - await updateJiraIssue('DEMO-42', { jiraInstance: 'https://contoso.atlassian.net', summary: 'Updated from helper' });
  • getJiraIssueByKey(issueKey, jiraInstance) - await getJiraIssueByKey('DEMO-42', 'https://contoso.atlassian.net');
  • listJiraIssues({ jiraInstance, jql, fields, expand }) - await listJiraIssues({ jiraInstance: 'https://contoso.atlassian.net', jql: 'project = DEMO ORDER BY updated DESC', fields: ['summary', 'status'] });
  • listJiraIssueTypes(oOptions) - await listJiraIssueTypes({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO' });
  • listJiraIssueTypeFields(oOptions) - await listJiraIssueTypeFields({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO', issueTypeIds: ['10001'] });
  • listJiraTransitions(issueIdOrKey, oOptions) - await listJiraTransitions('DEMO-42', { jiraInstance: 'https://contoso.atlassian.net' });
  • transitionJiraIssue(issueIdOrKey, oOptions) - await transitionJiraIssue('DEMO-42', { jiraInstance: 'https://contoso.atlassian.net', transition: { id: '31' } });

Projects, users, filters, priorities, and categories

  • listJiraProjects(oOptions) - await listJiraProjects({ jiraInstance: 'https://contoso.atlassian.net' });
  • createJiraProject(oOptions) - await createJiraProject({ jiraInstance: 'https://contoso.atlassian.net', key: 'DOCS', name: 'Docs Project' });
  • updateJiraProject(projectIdOrKey, oOptions) - await updateJiraProject('DOCS', { jiraInstance: 'https://contoso.atlassian.net', name: 'Docs Project Updated' });
  • deleteJiraProject(projectIdOrKey, oOptions) - await deleteJiraProject('DOCS', { jiraInstance: 'https://contoso.atlassian.net', enableUndo: true });
  • listJiraProjectCategories(oOptions) - await listJiraProjectCategories({ jiraInstance: 'https://contoso.atlassian.net' });
  • createJiraProjectCategory(oOptions) - await createJiraProjectCategory({ jiraInstance: 'https://contoso.atlassian.net', name: 'Internal Apps' });
  • removeJiraProjectCategory(id, oOptions) - await removeJiraProjectCategory(10000, { jiraInstance: 'https://contoso.atlassian.net' });
  • listJiraStatuses(oOptions) - await listJiraStatuses({ jiraInstance: 'https://contoso.atlassian.net', projectId: '10000', issueType: 'Bug' });
  • listJiraProjectUsers(oOptions) - await listJiraProjectUsers({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO' });
  • listJiraAssignableUsers(oOptions) - await listJiraAssignableUsers({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO' });
  • listJiraPriorityTypes(oOptions) - await listJiraPriorityTypes({ jiraInstance: 'https://contoso.atlassian.net' });
  • listJiraResources() - await listJiraResources();
  • getCurrentJiraUser({ jiraInstance, expand }) - await getCurrentJiraUser({ jiraInstance: 'https://contoso.atlassian.net', expand: ['groups'] });
  • getJiraUser(accountId, oOptions) - await getJiraUser('5b10a2844c20165700ede21g', { jiraInstance: 'https://contoso.atlassian.net', expand: ['groups'] });
  • listJiraFilters(jiraInstance) - await listJiraFilters('https://contoso.atlassian.net');

Async jobs, datacenter helpers, and event-style actions

  • getJiraTask(taskId, jiraInstance) - await getJiraTask('10001', 'https://contoso.atlassian.net');
  • cancelJiraTask(taskId, jiraInstance, token) - await cancelJiraTask('10001', 'https://contoso.atlassian.net', 'nocheck');
  • listJiraIssuesDatacenter(oOptions) - await listJiraIssuesDatacenter({ jiraInstance: 'https://contoso.atlassian.net' });
  • onNewJiraIssue(oOptions) - await onNewJiraIssue({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO' });
  • onClosedJiraIssue(oOptions) - await onClosedJiraIssue({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO' });
  • onUpdatedJiraIssue(oOptions) - await onUpdatedJiraIssue({ jiraInstance: 'https://contoso.atlassian.net', projectKey: 'DEMO' });
  • onNewJiraIssueFromJql(oOptions) - await onNewJiraIssueFromJql({ jiraInstance: 'https://contoso.atlassian.net', jql: 'project = DEMO AND status = Open' });
  • manageJiraIssues(queryRequest, sessionId) - await manageJiraIssues({ prompt: 'Create a sprint summary for DEMO issues' }, 'session-1');

All Jira connector actions wired by the wrapper

  • AddComment_V2
  • CancelTask_V2
  • CreateIssue_V3
  • EditIssue_V2
  • GetCurrentUser
  • GetIssue_V2
  • ListFilters_V2
  • ListIssues
  • ListProjects_V2
  • GetTask_V2
  • GetUser_V2
  • EditIssue
  • DeleteProject
  • UpdateProject
  • DeleteProject_V2
  • UpdateProject_V2
  • GetAllProjectCategories
  • CreateProjectCategory
  • GetAllProjectCategories_V2
  • CreateProjectCategory_V2
  • RemoveProjectCategory
  • RemoveProjectCategory_V2
  • GetTask
  • CancelTask
  • GetUser
  • CreateIssue
  • CreateIssueV2
  • GetIssue
  • UpdateIssue
  • UpdateIssue_V2
  • AddComment
  • ListIssueTypes
  • ListIssueTypes_V2
  • ListIssueTypesFields
  • ListIssueTypesFields_V2
  • ListProjects
  • CreateProject
  • CreateProject_V2
  • ListProjects_V3
  • ListStatuses
  • ListStatuses_V2
  • ListProjectUsers
  • ListProjectUsers_V2
  • ListAssignableUsers
  • ListAssignableUsers_V2
  • ListPriorityTypes
  • ListPriorityTypes_V2
  • ListFilters
  • ListResources
  • ListIssues_Datacenter
  • ListTransitions
  • UpdateTransition
  • OnNewIssue
  • OnNewIssue_V2
  • OnNewIssue_Datacenter
  • OnCloseIssue
  • OnCloseIssue_V2
  • OnCloseIssue_Datacenter
  • OnUpdateIssue
  • OnUpdateIssue_V2
  • OnUpdateIssue_Datacenter
  • OnNewIssueJQL
  • OnNewIssueJQL_V2
  • OnNewIssueJQL_Datacenter
  • mcp_JiraIssueManagement
"connectionReferences": {
  "keyvault": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_keyvault",
    "displayName": "Azure Key Vault",
    "dataSources": ["keyvault"],
    "authenticationType": "oauthDefault",
    "sharedConnectionId": null,
    "dataSets": {}
  }
}
  • `codeapp.js` checks common Azure Key Vault aliases including `keyvault`, `KeyVault`, `azurekeyvault`, `azureKeyVault`, and `AzureKeyVault`.
  • The sample wrapper exposes keys, secrets, and cryptography helpers as well as the generic action helper.
  • Pass the secret name only. The connected vault context comes from the configured connection reference.

Every exported Azure Key Vault helper

  • callKeyVaultOperation(operationName, parameters) - await callKeyVaultOperation('ListSecrets', {});
  • listKeys(oOptions) - await listKeys();
  • listKeyVersions(keyName) - await listKeyVersions('app-encryption-key');
  • getKeyMetadata(keyName) - await getKeyMetadata('app-encryption-key');
  • getKeyVersionMetadata(keyName, keyVersion) - await getKeyVersionMetadata('app-encryption-key', 'key-version-id');
  • encryptData(keyName, inputOrOptions) - await encryptData('app-encryption-key', { rawData: 'sensitive text' });
  • encryptDataWithVersion(keyName, keyVersion, inputOrOptions) - await encryptDataWithVersion('app-encryption-key', 'key-version-id', { rawData: 'sensitive text' });
  • decryptData(keyName, inputOrOptions) - await decryptData('app-encryption-key', { encryptedData: sCipherText });
  • decryptDataWithVersion(keyName, keyVersion, inputOrOptions) - await decryptDataWithVersion('app-encryption-key', 'key-version-id', { encryptedData: sCipherText });
  • getSecret(secretName) - await getSecret('codeapp-api-key');
  • listSecrets(oOptions) - await listSecrets();
  • listSecretVersions(secretName) - await listSecretVersions('codeapp-api-key');
  • getSecretMetadata(secretName) - await getSecretMetadata('codeapp-api-key');
  • getSecretVersionMetadata(secretName, secretVersion) - await getSecretVersionMetadata('codeapp-api-key', 'secret-version-id');
  • getSecretVersion(secretName, secretVersion) - await getSecretVersion('codeapp-api-key', 'secret-version-id');

All Azure Key Vault connector actions wired by the wrapper

  • ListKeys
  • ListKeyVersions
  • GetKeyMetadata
  • GetKeyVersionMetadata
  • EncryptData
  • EncryptDataWithVersion
  • DecryptData
  • DecryptDataWithVersion
  • GetSecret
  • ListSecrets
  • ListSecretVersions
  • GetSecretMetadata
  • GetSecretVersionMetadata
  • GetSecretVersion
"connectionReferences": {
  "sqlConnection": {
    "id": "/providers/Microsoft.PowerApps/apis/shared_sql",
    "displayName": "SQL Server",
    "dataSources": ["sql"],
    "dataSets": {}
  }
}
  • dist/connectors/sql.js retries the data-source names sql, Sql, and SQL.
  • The wrapper includes inline metadata for all SQL actions it exposes.
  • The Power Apps runtime special-cases shared_sql, so do not hand-roll SQL connector paths.
  • server and database default to "default" when not supplied.
Action Example
callSqlOperation(operationName, parameters) await callSqlOperation('GetTables_V2', { body: { server: 'default', database: 'default' } });
getSqlTables({ server, database }) await getSqlTables({ server: 'default', database: 'default' });
getSqlRows({ server, database, table, filter, orderBy, top, select }) await getSqlRows({ table: '[dbo].[Employees]', filter: "Department eq 'Engineering'", top: 50, orderBy: 'LastName asc' });
getSqlRow({ server, database, table, id }) await getSqlRow({ table: '[dbo].[Employees]', id: 42 });
insertSqlRow({ server, database, table, item }) await insertSqlRow({ table: '[dbo].[Employees]', item: { FirstName: 'Jane', LastName: 'Doe', Department: 'Engineering' } });
updateSqlRow({ server, database, table, id, item }) await updateSqlRow({ table: '[dbo].[Employees]', id: 42, item: { Department: 'Product' } });
deleteSqlRow({ server, database, table, id }) await deleteSqlRow({ table: '[dbo].[Employees]', id: 42 });
executeSqlQuery({ server, database, query }) await executeSqlQuery({ query: "SELECT TOP 10 * FROM [dbo].[Employees] WHERE Department = 'Engineering'" });
executeSqlStoredProcedure({ server, database, procedure, parameters }) await executeSqlStoredProcedure({ procedure: '[dbo].[usp_GetTeamMembers]', parameters: { TeamId: 5 } });

All SQL connector actions wired by the wrapper

  • GetTables_V2
  • GetItems_V2
  • GetItem_V2
  • PostItem_V2
  • PatchItem_V2
  • DeleteItem_V2
  • ExecutePassThroughNativeQuery_V2
  • ExecuteProcedure_V2
Screenshot: SQL Server connector in action

Five example apps from `examples`

Demo Connection Actions used What it shows
Dataverse Demo Dataverse `listItems`, `createItem` Simple CRUD starter using the `tasks` table with a small recent-items grid.
Groups Demo Office 365 Groups `listMyGroups` Lists the current user’s groups and renders basic metadata like name, mail, visibility, and description.
My Profile Office 365 Users `getMyProfile`, `getUserPhoto`, `getManager`, `getDirectReports` Builds a profile page with manager and direct reports in one call flow.
Outlook Demo Office 365 Outlook `listEmails` Shows the first 10 inbox emails and includes a fallback if the first response shape is awkward.
SharePoint Demo SharePoint `callSharePointOperation`, `listTables`, `getItems` Loads rows by list name first, then falls back to connector table lookup and list id resolution.
SharePoint Demo

Gotchas, tips, and things to know

PAC CLI is required

The docs should treat pac auth create, environment selection, and pac code push as first-class setup steps. Without PAC, you cannot deploy or add data sources.

Node.js is a prerequisite

Node.js is required for npm install, npm start, and rebuilding the browser SDK files. Install it before starting.

SharePoint env vars need Dataverse

SharePoint environment variables require a Dataverse connection reference — unlike with standard canvas apps where the SharePoint connector handles it natively.

Connector reference names must match

Names must stay aligned with the values used by the helper files: sharepointonline, Office365Outlook, office365users, office365groups, teams, jira, keyvault, sql.

Initialize one shared client

Call const client = getClient(ALL_DATA_SOURCES) once and reuse it across the app. Creating multiple clients wastes resources and can cause authentication issues.

Route calls through helpers

Use the provided helpers like execConnector(tableName, operationName, parameters) and unwrapResult to normalize executeAsync results instead of building raw calls.

Use Promise.allSettled for parallel loads

Load multiple resources with Promise.allSettled and surface partial failures via a unified status bar instead of Promise.all which fails fast.

Dataverse entity set names, not display names

Always use entity set names like cr_contacts in code, never display names. The databaseReferences block must have matching entries for every table you touch.

SQL server & database default to "default"

When using the SQL helpers, server and database default to "default". This works when only one SQL connection is configured.

Jira requires the instance URL

All Jira helpers need the jiraInstance parameter (e.g., https://contoso.atlassian.net). This maps to the X-Request-Jirainstance header.

Key Vault uses connected vault context

Pass the secret name only when calling Key Vault helpers. The vault URL comes from the connection reference, not from app code.

Generated files are source of truth

Generated connector files under src/generated/ contain the correct operation names, parameter shapes, and return models. Don't hardcode operation paths yourself.

Use the sample custom agent and skills in VS Code

The sample `codeapp.agent.md` file defines a custom Copilot agent persona focused on Power Apps code-first work: Dataverse CRUD, connector setup, environment variables, deployment workflow, and app scaffolding guidance.

In VS Code, custom agents are Markdown files with the `.agent.md` extension. Once the file is placed in a supported agent folder, it appears in the Copilot Chat agents picker and can be used like any other specialized agent.

  1. Place the sample agent file in `.github/agents/codeapp.agent.md` for workspace-wide discovery, or create a user agent through the Chat Customizations editor.
  2. Open Copilot Chat, use the agents dropdown or type `/agents`, and select `codeapp` after VS Code discovers the file.
  3. Place skill folders under `.github/skills/<skill-name>/SKILL.md` so VS Code can load them automatically or expose them as slash commands.
  4. Use `/skills` to inspect available skills, or invoke one directly with commands such as `/dataverse`, `/sharepoint`, or `/office365-outlook`.
.github/
  agents/
    codeapp.agent.md
  skills/
    connections/SKILL.md
    dataverse/SKILL.md
    environment-variables/SKILL.md
    frontend-design/SKILL.md
    jira/SKILL.md
    office365-groups/SKILL.md
    office365-outlook/SKILL.md
    office365-users/SKILL.md
    sharepoint/SKILL.md
    sql/SKILL.md
    teams/SKILL.md

The repo currently keeps these files under `samples/skills/` as templates. Move or copy the files into `.github/agents/` and `.github/skills/` if you want VS Code to discover them automatically in this workspace.

Sample VS Code name Use in chat
`codeapp.agent.md` `codeapp` Select the `codeapp` agent from the chat agents picker when you want Power Apps code-first behavior and connector-aware guidance.
`connections/` `connections` Use when wiring connector references, data source names, and shared connection setup across the app.
`dataverse/` `dataverse` Use for table registration, CRUD patterns, unbound actions, and Dataverse-backed environment variable reads.
`environment-variables/` `environment-variables` Use for schema-name driven runtime configuration and for keeping environment variable table wiring consistent.
`frontend-design/` `frontend-design` Use when you want stronger UI direction, layout refinement, and less generic-looking frontend output.
`jira/` `jira` Use for Jira issue, project, task, and Jira-instance aware helper flows.
`office365-groups/` `office365-groups` Use for Microsoft 365 group listings, members, and event-related helper flows.
`office365-outlook/` `office365-outlook` Use for mail, calendar, contacts, rooms, mailbox settings, and raw Outlook action guidance.
`office365-users/` `office365-users` Use for profile, manager, direct reports, photos, search, and user document helpers.
`sharepoint/` `sharepoint` Use for list, library, file, and environment-variable driven SharePoint setup.
`sql/` `sql` Use for SQL table discovery, row CRUD, native queries, and stored procedure execution.
`teams/` `teams` Use for teams, channels, chats, mentions, notifications, and message posting flows.

Skills load on demand

VS Code first reads the `name` and `description` from the skill frontmatter, then loads the full `SKILL.md` only when the task or slash command matches.

Slash command or automatic load

You can call a skill directly with `/skill-name`, or let Copilot load it automatically when the description matches your task.

Folder name must match `name:`

VS Code skill discovery expects the parent folder and the `name` field in `SKILL.md` to match. Review copied samples before relying on them.

`start/` needs cleanup

The `samples/skills/start/` folder currently contains `name: frontend-design`, so it should be corrected before being used as a standalone VS Code skill.

`keyvault/` is not loadable yet

The `samples/skills/keyvault/` folder exists, but there is no `SKILL.md` in that folder right now, so VS Code will not detect it as a skill.

Use diagnostics when discovery fails

If the agent or skills do not appear, open the Chat Customizations editor or the Chat diagnostics view to verify the folder location, frontmatter, and discovery settings.

agent in vs code

Articles, tutorials, and updates

Getting Started dev.to

Power Apps — A Cooler Way to Use Code Apps

An introduction to using CodeAppJS to build Power Apps code apps with vanilla JavaScript instead of React. Covers setup, configuration, and first deployment.

Read article →
Code App Plus

How to Create Your Own AI Coding Agent

YBuilding a bespoke AI coding agent for Power Platform Code Apps—using custom system prompts, skills, and a VS Code extension—to overcome the limitations of general LLMs in your highly niche vanilla‑JavaScript workflow and to consistently capture, refine, and reuse all your hard‑won learnings..

Read article →
Dataverse

Read and Write to Dataverse

Learn how to interact with Dataverse tables, perform CRUD operations, and leverage the Dataverse API for your Power Apps code apps.

Read article →
Coming Soon

Building a Outlook App

Step by step guide to creating an Outlook integration app using CodeAppJS, including reading, sending emails, and calendar overview

Coming soon
Coming Soon

Read and Write to SharePoint

Connect to SharePoint lists and libraries, perform CRUD operations, and manage documents from your code app.

Coming soon
Coming Soon

Using AI to Create CodeApp.js apps

Learn how to leverage AI to streamline the development of CodeApp.js applications, including VS Code setup, prompts, and debugging.

Coming soon

Version history and release notes

Version Changes
v0.0.0 Launch version.
v0.1.1 Consolidation of codeapp.js file, improved context handling.
v0.2.1 Improved Skill.md files, debugger added, fixed unbound action.
v0.2.2 Fixed missing Outlook actions. Added new Outlook demo.
v0.3.0 Added Teams, Jira, and Azure Key Vault connections. New SQL Server connector. Updated documentation site with dark mode, blog section, and comprehensive connector examples.
v0.3.1 Bug fix Outlook, added extra actions
v1.0.1 Broke up codeapp.js into multiple files for better maintainability. Update all actions for more robust performance. Extended actions for all connectors. Created agent and skill files for AI development
v1.0.2 Fix start and frontend-design skills