Back to Blog
5 min read

Dataverse for Teams: Building Data-Driven Apps Inside Microsoft Teams

Dataverse for Teams brings the power of a relational database directly into Microsoft Teams. Teams users can now build sophisticated data-driven applications without leaving their collaboration environment.

What is Dataverse for Teams?

Dataverse for Teams is a built-in, low-code data platform that comes with every Teams environment. It provides:

  • Relational data storage with business logic
  • Built-in security and compliance
  • Integration with Power Apps and Power Automate
  • No additional licensing for Teams users

Creating Your First Table

In Teams, go to Power Apps tab and create a table:

// Table definition (conceptual - done through UI)
{
  "name": "Project",
  "displayName": "Projects",
  "columns": [
    { "name": "Name", "type": "text", "required": true },
    { "name": "Status", "type": "choice", "options": ["Not Started", "In Progress", "Completed"] },
    { "name": "DueDate", "type": "dateOnly" },
    { "name": "Budget", "type": "currency" },
    { "name": "Owner", "type": "lookup", "relatedTable": "User" }
  ]
}

Building a Project Tracker App

Create a canvas app connected to your Dataverse table:

// App OnStart
Set(CurrentUser, User());
ClearCollect(
    MyProjects,
    Filter(
        Projects,
        Owner.Email = CurrentUser.Email
    )
);

// Gallery displaying projects
Sort(
    Filter(
        Projects,
        Status.Value <> "Completed" && DueDate <= Today() + 7
    ),
    DueDate,
    Ascending
)

// Form for creating new projects
SubmitForm(ProjectForm);
Notify("Project created successfully!", NotificationType.Success);
Navigate(HomeScreen, ScreenTransition.None);

Status indicator with conditional formatting:

// Status color based on due date
Switch(
    true,
    ThisItem.Status.Value = "Completed", Color.Green,
    ThisItem.DueDate < Today(), Color.Red,
    ThisItem.DueDate < Today() + 3, Color.Orange,
    Color.Blue
)

// Days remaining calculation
If(
    ThisItem.Status.Value = "Completed",
    "Done",
    Text(
        DateDiff(Today(), ThisItem.DueDate, Days),
        "[$-en-US]0"
    ) & " days left"
)

Creating Relationships

Define relationships between tables:

// Task table with relationship to Project
{
  "name": "Task",
  "columns": [
    { "name": "Title", "type": "text" },
    { "name": "Project", "type": "lookup", "relatedTable": "Projects" },
    { "name": "AssignedTo", "type": "lookup", "relatedTable": "User" },
    { "name": "Completed", "type": "boolean" }
  ]
}

// Displaying related tasks in project detail
Filter(
    Tasks,
    Project.Name = SelectedProject.Name
)

// Count of incomplete tasks
CountRows(
    Filter(
        Tasks,
        Project.Name = ThisItem.Name && !Completed
    )
)

Power Automate Integration

Create flows that respond to data changes:

# Flow definition (conceptual)
trigger:
  type: dataverse
  table: Projects
  event: create

actions:
  - type: teams
    action: postMessage
    channel: General
    message: |
      New project created: @{triggerBody()?['name']}
      Owner: @{triggerBody()?['owner/fullname']}
      Due: @{formatDateTime(triggerBody()?['duedate'], 'MMMM dd, yyyy')}

  - type: outlook
    action: sendEmail
    to: "@{triggerBody()?['owner/emailaddress']}"
    subject: "You've been assigned a new project"
    body: |
      Hi @{triggerBody()?['owner/firstname']},

      You've been assigned to the project "@{triggerBody()?['name']}".

      Due Date: @{formatDateTime(triggerBody()?['duedate'], 'MMMM dd, yyyy')}
      Budget: @{triggerBody()?['budget']}

      Access your projects in Teams.

Automated task assignment flow:

trigger:
  type: dataverse
  table: Tasks
  event: update
  condition: "@equals(triggerBody()?['completed'], true)"

actions:
  - type: dataverse
    action: listRecords
    table: Tasks
    filter: "project/id eq '@{triggerBody()?['project/id']}' and completed eq false"
    outputVariable: remainingTasks

  - type: condition
    expression: "@equals(length(outputs('remainingTasks')?['body/value']), 0)"
    true:
      - type: dataverse
        action: updateRecord
        table: Projects
        id: "@{triggerBody()?['project/id']}"
        data:
          status: "Completed"
      - type: teams
        action: postMessage
        message: "Project @{triggerBody()?['project/name']} completed!"

Business Rules

Implement validation and automation with business rules:

// Validation rule: Budget must be positive
If(
    ThisItem.Budget <= 0,
    Notify("Budget must be greater than zero", NotificationType.Error);
    false,
    true
)

// Auto-set status based on dates
If(
    ThisItem.DueDate < Today() && ThisItem.Status.Value <> "Completed",
    Patch(
        Projects,
        ThisItem,
        { Status: { Value: "Overdue" } }
    )
)

// Calculated column: Project health score
With(
    {
        taskCompletion: CountRows(Filter(Tasks, Project.Name = ThisItem.Name && Completed)) /
                       Max(CountRows(Filter(Tasks, Project.Name = ThisItem.Name)), 1),
        budgetHealth: If(ThisItem.Spent <= ThisItem.Budget, 1, 0.5),
        timeHealth: If(Today() <= ThisItem.DueDate, 1, 0.7)
    },
    Round((taskCompletion + budgetHealth + timeHealth) / 3 * 100, 0)
)

Security and Permissions

Dataverse for Teams inherits Teams security:

// Check user permissions
Set(
    IsOwner,
    LookUp(Projects, Name = SelectedProject.Name).Owner.Email = User().Email
);

Set(
    IsTeamOwner,
    // Team owners have elevated permissions
    User().Email in TeamOwners
);

// Conditional UI based on permissions
If(
    IsOwner || IsTeamOwner,
    DisplayMode.Edit,
    DisplayMode.View
)

// Filter data based on user role
If(
    IsTeamOwner,
    Projects,
    Filter(Projects, Owner.Email = User().Email)
)

Migrating to Full Dataverse

When you outgrow Teams, upgrade to full Dataverse:

# Export solution from Dataverse for Teams
$teamEnvId = "your-teams-environment-id"
$solution = Get-AdminPowerAppEnvironmentSolutionPackages `
    -EnvironmentName $teamEnvId `
    -SolutionName "ProjectTracker"

Export-AdminPowerAppEnvironmentSolutionPackage `
    -EnvironmentName $teamEnvId `
    -SolutionName "ProjectTracker" `
    -SolutionPath ".\ProjectTracker.zip"

# Import to full Dataverse environment
$prodEnvId = "your-production-environment-id"
Import-AdminPowerAppEnvironmentSolutionPackage `
    -EnvironmentName $prodEnvId `
    -SolutionPath ".\ProjectTracker.zip"

Best Practices

  1. Design for Teams context: Users interact in Teams, so keep the experience native
  2. Use relationships wisely: Normalize data but don’t over-engineer
  3. Leverage Team membership: Use existing Teams security model
  4. Plan for growth: Design tables that can migrate to full Dataverse
// Pattern: Centralized configuration
// Create a Settings table for app configuration
LookUp(Settings, Key = "MaxProjectsPerUser").Value

// Pattern: Audit trail
// Add CreatedBy, CreatedOn, ModifiedBy, ModifiedOn to all tables
Patch(
    Projects,
    ThisItem,
    {
        ModifiedBy: User(),
        ModifiedOn: Now()
    }
)

// Pattern: Soft delete instead of hard delete
Patch(
    Projects,
    ThisItem,
    {
        IsDeleted: true,
        DeletedBy: User(),
        DeletedOn: Now()
    }
)

Dataverse for Teams democratizes data platform capabilities. Teams users can now build real applications with proper data management without needing IT involvement for simple use cases.

Resources

Michael John Pena

Michael John Pena

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.