Home / PowerApps / Power Apps Pitfalls: 25 Mistakes That Break Apps, Burn Budgets, and Frustrate Users
PowerApps

Power Apps Pitfalls: 25 Mistakes That Break Apps, Burn Budgets, and Frustrate Users

A brutally honest catalog of the 25 most common Power Apps mistakes — from delegation disasters to licensing landmines — with concrete fixes for each one. Based on real incidents across banking, government, and enterprise deployments.

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

Introduction

Introduction

Every Power Apps project I have rescued has the same origin story: someone built a beautiful prototype that worked perfectly for 3 users and 50 records. Then it went to production with 500 users and 50,000 records. Then everything broke.

This article is the catalog of mistakes I see repeatedly — across industries, across skill levels, across organizations. Some are technical. Some are architectural. Some are organizational. All of them are preventable if you know what to watch for.

I have organized them into five categories: Data Mistakes, Performance Mistakes, Architecture Mistakes, Security Mistakes, and Organizational Mistakes.

Category 1: Data Mistakes (Pitfalls 1–6)

Pitfall 1: Ignoring Delegation Limits

The mistake: Using non-delegable functions like CountRows, Search, SortByColumns with text comparisons, or Lookup with in-memory filtering on large data sources.

Why it hurts: The app silently returns incomplete data. No error. No warning. The user sees 500 records and believes that is all there is. Financial reports are wrong. Queries miss records. Compliance reporting has gaps.

// ❌ PITFALL: Non-delegable — returns max 500/2000 records
Filter(Expenses, Month(SubmittedDate) = 3)
// Month() is NOT delegable to Dataverse or SQL

// ✅ FIX: Use delegable date comparison
Filter(
    Expenses,
    SubmittedDate >= Date(2026, 3, 1) &&
    SubmittedDate < Date(2026, 4, 1)
)
// Date comparisons ARE delegable

The rule: If you see a blue dotted underline in the formula bar, you have a delegation problem. Fix it before anything else.

Pitfall 2: Using SharePoint Lists as a Primary Database

The mistake: Storing 50,000+ records in a SharePoint list because "it was free and easy."

Why it hurts: SharePoint list view threshold is 5,000 items. Performance degrades dramatically. Complex filtering fails. No referential integrity. No transactions. No stored procedures. Lookup columns create N+1 query patterns.

{
  "sharepoint_list_limits": {
    "max_items_view_threshold": 5000,
    "max_columns_per_list": 512,
    "max_lookup_columns": 12,
    "max_file_attachment_size_mb": 250,
    "max_items_per_list_supported": 30000000,
    "performance_cliff": "Noticeable at 2000+ items with complex views",
    "when_acceptable": [
      "Document libraries (SharePoint's strength)",
      "Simple lists under 2000 items",
      "Lists with flat structure (no complex lookups)",
      "When budget genuinely cannot cover Dataverse"
    ],
    "when_to_migrate": [
      "More than 5000 items expected within 12 months",
      "Need relational data (foreign keys, joins)",
      "Need row-level security",
      "Need calculated rollups or aggregations",
      "Need offline/mobile scenarios"
    ]
  }
}

Pitfall 3: Not Planning for Data Volume Growth

The mistake: Building a query that works with 100 records and assuming it will work with 100,000.

// ❌ PITFALL: Loads ALL records into memory
ClearCollect(colAllOrders, Orders);
Set(varTotalRevenue, Sum(colAllOrders, Amount));
// At 100 records: 0.5 seconds. At 100,000 records: never finishes.

// ✅ FIX: Server-side aggregation via Power Automate
Set(varIsCalculating, true);
'CalculateRevenue'.Run(
    {
        startDate: varStartDate,
        endDate: varEndDate,
        department: varDepartment
    }
);
Set(varTotalRevenue, Value(varFlowResult.totalRevenue));
Set(varIsCalculating, false);

Pitfall 4: Storing Sensitive Data in Local Collections

The mistake: Caching user data, salaries, medical records, or authentication tokens in client-side collections.

// ❌ PITFALL: Sensitive data cached on client
ClearCollect(colEmployeeSalaries, 
    ShowColumns(Employees, "Name", "Salary", "SSN", "BankAccount")
);
// This data is now in browser memory, inspectable via dev tools

// ✅ FIX: Only retrieve what the current user should see
ClearCollect(colMyTeamSalaries,
    Filter(
        ShowColumns(Employees, "Name", "Salary"),
        Manager.Email = User().Email
    )
);
// Row-level security + column restriction + delegable

Pitfall 5: Not Indexing Dataverse Columns

The mistake: Filtering, sorting, or searching on columns that are not indexed. Every query becomes a full table scan.

Architecture Overview: # Check which columns are indexed in a Dataverse table

Pitfall 6: Using Patch Instead of SubmitForm

The mistake: Using Patch() for form submissions instead of the built-in SubmitForm() function.

// ❌ PITFALL: Manual Patch for forms
Patch(
    Expenses,
    Defaults(Expenses),
    {
        Title: txtTitle.Text,
        Amount: Value(txtAmount.Text),
        Category: drpCategory.Selected.Value,
        Date: dpDate.SelectedDate,
        Description: txtDescription.Text,
        Merchant: txtMerchant.Text,
        Status: "Submitted"
    }
);
// Problems: No validation, no error handling, no form reset

// ✅ FIX: Use EditForm + SubmitForm
SubmitForm(frmExpense);
// Benefits: Built-in validation, OnSuccess/OnFailure handlers,
// automatic form reset, undo support, required field checking

Category 2: Performance Mistakes (Pitfalls 7–12)

Category 2: Performance Mistakes (Pitfalls 7–12)

Pitfall 7: Loading Everything on App.OnStart

The mistake: Putting every ClearCollect, Set, and API call in App.OnStart, creating a 15-second splash screen.

// ❌ PITFALL: App.OnStart does everything
// App.OnStart
ClearCollect(colEmployees, Employees);
ClearCollect(colDepartments, Departments);
ClearCollect(colProjects, Projects);
ClearCollect(colTimesheets, Timesheets);
ClearCollect(colExpenses, Expenses);
ClearCollect(colApprovals, Approvals);
Set(varUser, LookUp(Employees, Email = User().Email));
Set(varManager, LookUp(Employees, ID = varUser.ManagerID));
// 12 seconds to load. User stares at blank screen.

// ✅ FIX: Load only what the first screen needs
// App.OnStart (fast — 1-2 seconds)
Concurrent(
    Set(varUser, LookUp(Employees, Email = User().Email)),
    ClearCollect(colLookups, Departments)
);

// Screen.OnVisible (load screen-specific data on demand)
// scrDashboard.OnVisible
If(IsEmpty(colMyTimesheets),
    ClearCollect(colMyTimesheets,
        Filter(Timesheets, Employee.ID = varUser.ID && 
               WeekStart >= DateAdd(Today(), -14, TimeUnit.Days))
    )
);

Pitfall 8: Gallery Inside Gallery (N+1 Problem)

The mistake: Nesting a gallery inside another gallery, causing each parent row to execute its own query.

// ❌ PITFALL: Nested gallery = N+1 queries
// Parent gallery: galDepartments (Items = Departments) — 20 rows
//   Child gallery: galEmployees (Items = Filter(Employees, DeptID = ThisItem.ID))
// Result: 1 query for departments + 20 queries for employees = 21 queries

// ✅ FIX: Pre-load and filter locally
// Screen.OnVisible
ClearCollect(colAllEmployees, 
    Filter(Employees, Status = "Active")
);

// Child gallery uses local collection (no additional queries)
// galEmployees.Items = Filter(colAllEmployees, DeptID = ThisItem.ID)
// Result: 1 query for departments + 1 query for employees = 2 queries

Pitfall 9: Images Without Optimization

The mistake: Users uploading 5MB photos from their iPhone directly into Dataverse image columns. Galleries with 50 items load 250MB of images.

Pitfall 10: Timer Polling Instead of Event-Driven Refresh

The mistake: Adding a Timer control that runs every 5 seconds to check for new data, consuming API calls and battery.

Pitfall 11: Complex Formulas in Gallery Templates

The mistake: Putting LookUp(), CountRows(), or Filter() calls in gallery item labels. Each row recalculates on every render.

// ❌ PITFALL: LookUp in every gallery row
// Gallery item label formula:
LookUp(Departments, ID = ThisItem.DepartmentID, Name)
// 50 gallery items = 50 LookUp calls on every render

// ✅ FIX: Create enriched collection once
ClearCollect(
    colEnrichedEmployees,
    AddColumns(
        Filter(Employees, Status = "Active"),
        "DepartmentName", LookUp(colDepartments, ID = DepartmentID, Name)
    )
);
// Gallery uses colEnrichedEmployees — no per-row lookups

Pitfall 12: Not Using Concurrent() for Independent Loads

// ❌ PITFALL: Sequential loading (9 seconds total)
ClearCollect(colA, SourceA);  // 3 seconds
ClearCollect(colB, SourceB);  // 3 seconds  
ClearCollect(colC, SourceC);  // 3 seconds

// ✅ FIX: Parallel loading (3 seconds total)
Concurrent(
    ClearCollect(colA, SourceA),
    ClearCollect(colB, SourceB),
    ClearCollect(colC, SourceC)
);

Category 3: Architecture Mistakes (Pitfalls 13–17)

Pitfall 13: One Mega-App Instead of Micro-Apps

The mistake: Building a single app with 40+ screens that handles expenses, timesheets, approvals, reporting, and admin functions.

{
  "mega_app_symptoms": [
    "More than 15 screens",
    "Load time exceeds 10 seconds",
    "Multiple unrelated data sources",
    "Different user roles see completely different screens",
    "App size exceeds 5MB (unpacked .msapp)"
  ],
  "micro_app_architecture": {
    "app_1": "Expense Submission (5 screens, 3s load)",
    "app_2": "Expense Approval (3 screens, 2s load)",
    "app_3": "Expense Reporting (4 screens, 3s load)",
    "app_4": "Admin Configuration (3 screens, 2s load)",
    "shared": "Component Library (reusable headers, nav, themes)"
  },
  "how_to_link": "Use Launch() for cross-app navigation with parameters"
}

Pitfall 14: Hardcoded Configuration Values

The mistake: Hardcoding environment URLs, API keys, email addresses, threshold values, and feature flags directly in formulas.

// ❌ PITFALL: Hardcoded everything
Set(varAPIEndpoint, "https://prod-api.contoso.com/v2");
Set(varApprovalThreshold, 5000);
Set(varAdminEmail, "john.smith@contoso.com");

// ✅ FIX: Configuration table in Dataverse
ClearCollect(
    colConfig,
    Filter(AppConfiguration, 
           AppName = "ExpenseTracker" && 
           Environment = LookUp(EnvironmentSettings, IsCurrent, Name)
    )
);
Set(varAPIEndpoint, LookUp(colConfig, Key = "APIEndpoint", Value));
Set(varApprovalThreshold, Value(LookUp(colConfig, Key = "ApprovalThreshold", Value)));
Set(varAdminEmail, LookUp(colConfig, Key = "AdminEmail", Value));
// Change config without republishing the app

Pitfall 15: No Error Handling Strategy

The mistake: Assuming every API call, Patch, and flow invocation will succeed. When one fails, the user sees a cryptic error or — worse — nothing at all.

// ❌ PITFALL: No error handling
Patch(Expenses, Defaults(Expenses), {Title: "New Expense"});
// If Patch fails: no feedback, no retry, data lost

// ✅ FIX: Comprehensive error handling
Set(varIsSaving, true);
IfError(
    Patch(Expenses, Defaults(Expenses), {
        Title: txtTitle.Text,
        Amount: Value(txtAmount.Text)
    }),
    // Error handler
    Notify(
        "Unable to save expense. Please check your connection and try again. " &
        "Error: " & FirstError.Message,
        NotificationType.Error,
        5000
    );
    Set(varSaveError, true);
    // Log error for support team
    'LogAppError'.Run(
        User().Email,
        "ExpenseCreate",
        FirstError.Message,
        Text(Now(), "yyyy-mm-dd hh:mm:ss")
    ),
    // Success handler
    Notify("Expense saved successfully", NotificationType.Success);
    Set(varSaveError, false);
    Navigate(scrDashboard, ScreenTransition.None)
);
Set(varIsSaving, false);

Pitfall 16: Building Without a Component Library

The mistake: Copy-pasting header bars, navigation menus, and styling across 10 apps. When the brand color changes, you update 10 apps manually.

Pitfall 17: No ALM (Application Lifecycle Management) Pipeline

The mistake: Developing directly in the production environment. Testing with live data. Publishing changes without backup.

# Minimum viable ALM pipeline
# 1. Export solution from Dev
pac solution export `
    --path "solutions/ExpenseTracker_1.0.0.zip" `
    --name "ExpenseTracker" `
    --managed

# 2. Import to Test environment
pac solution import `
    --path "solutions/ExpenseTracker_1.0.0.zip" `
    --environment "https://test-org.crm.dynamics.com"

# 3. Run automated tests (Power Apps Test Engine)
pac test run `
    --test-plan-file "tests/expense-smoke-tests.yaml"

# 4. If tests pass, import to Production
pac solution import `
    --path "solutions/ExpenseTracker_1.0.0.zip" `
    --environment "https://prod-org.crm.dynamics.com" `
    --import-as-holding

Category 4: Security Mistakes (Pitfalls 18–21)

Category 4: Security Mistakes (Pitfalls 18–21)

Pitfall 18: Relying on UI Hiding for Security

The mistake: Using Visible = false to hide admin buttons, assuming users cannot access admin functions. Anyone can inspect the app or share the admin screen deep link.

// ❌ PITFALL: Security by obscurity
// Admin button visible only to "admins"
btnAdmin.Visible = User().Email in ["admin@contoso.com"]
// Problems:
// - Anyone can deep-link to the admin screen
// - Data is still accessible via the data source
// - If someone gets added to the "admins" list by mistake, game over

// ✅ FIX: Server-side security (Dataverse security roles)
// 1. Create "Expense Admin" security role in Dataverse
// 2. Assign role to admin users
// 3. Configure table-level permissions (CRUD per role)
// 4. Configure column-level security for sensitive fields
// 5. UI visibility is cosmetic — server enforces access

Pitfall 19: Shared Connections with Elevated Permissions

The mistake: Using a shared connection with admin privileges so "the app works for everyone." Every user inherits admin-level database access.

Pitfall 20: No Audit Trail for Critical Actions

The mistake: Approvals, deletions, and financial transactions happen with no record of who did what, when, or why.

// ✅ Create audit log for every critical action
Patch(
    AuditLog,
    Defaults(AuditLog),
    {
        Action: "ExpenseApproved",
        EntityType: "Expense",
        EntityID: Text(varSelectedExpense.ID),
        PerformedBy: User().Email,
        PerformedAt: Now(),
        OldValue: JSON(varSelectedExpense, JSONFormat.IndentFour),
        NewValue: JSON({Status: "Approved", ApprovedAmount: varApprovedAmount}),
        IPAddress: "Captured via flow",
        Notes: txtApprovalNotes.Text
    }
);

Pitfall 21: Exposing API Keys in Power Automate Expressions

The mistake: Putting API keys directly in HTTP action URLs or headers where they appear in flow run history.

Category 5: Organizational Mistakes (Pitfalls 22–25)

Pitfall 22: No Executive Sponsor

The mistake: Building Power Apps as a grass-roots initiative without executive air cover. The first time the project needs budget, headcount, or cross-department cooperation, it stalls.

Pitfall 23: Not Measuring Adoption

The mistake: Launching the app and assuming success because nobody complained. Meanwhile, 80% of users went back to their Excel spreadsheet.

{
  "adoption_metrics_to_track": [
    {
      "metric": "Daily Active Users (DAU)",
      "target": "50% of licensed users within 30 days",
      "how": "Power Platform analytics + custom telemetry"
    },
    {
      "metric": "Task Completion Rate",
      "target": "85% of started tasks completed without abandonment",
      "how": "Custom logging: track screen navigations and form submissions"
    },
    {
      "metric": "Time to Complete Key Task",
      "target": "Below 3 minutes for core workflow",
      "how": "Timestamp on form open vs. form submit"
    },
    {
      "metric": "Support Tickets Per Week",
      "target": "Decreasing trend after launch",
      "how": "Tag tickets with app name in helpdesk system"
    },
    {
      "metric": "User Satisfaction (CSAT)",
      "target": "4.0+ out of 5.0",
      "how": "Monthly 3-question survey embedded in app"
    }
  ]
}

Pitfall 24: Licensing Surprises

The mistake: Building an app with premium connectors, deploying to 500 users, then discovering the licensing cost three days before go-live.

Scenario License Required Cost Impact
Canvas app + SharePoint only Microsoft 365 (included) $0 additional
Canvas app + Dataverse Power Apps per user ($20/mo) or per app ($5/mo) $12,000–$120,000/yr for 500 users
Model-driven app (any) Power Apps per user ($20/mo) $120,000/yr for 500 users
App + premium connector (SAP, Oracle, custom) Power Apps per user ($20/mo) $120,000/yr for 500 users
App + AI Builder Power Apps per user + AI Builder capacity $120,000+ add-on credits
Power Automate premium flow triggered by app Per user ($15/mo) or per flow ($100/mo) Varies significantly

The rule: Check licensing requirements during Week 1 of the project. Not Week 12.

Pitfall 25: Building Without User Research

The mistake: IT decides what the app should do. IT builds the app. IT shows it to users. Users say "that is not how we work." IT blames users for not adopting.

{
  "user_research_minimum_viable": {
    "before_building": [
      "Shadow 3 users doing the current process for 30 minutes each",
      "Count the clicks/steps in their current workflow",
      "Ask: What is the most annoying part of your current process?",
      "Ask: If you could change one thing, what would it be?"
    ],
    "during_building": [
      "Show clickable prototype to 5 users after Week 1",
      "Watch them try to complete a task. Do not help.",
      "If 3 out of 5 cannot complete the task, redesign",
      "Test every screen on mobile (most users will use mobile)"
    ],
    "after_launch": [
      "Monitor DAU daily for 30 days",
      "Call 3 heavy users and 3 non-users after Week 2",
      "Ask non-users: Why are you not using the app?",
      "The answer is always one of: did not know it existed, too slow, does not match my workflow, my manager does not use it"
    ]
  }
}

The Pitfall Severity Matrix

# Pitfall Severity Detection Fix Effort
1 Delegation limits Critical Blue underline in editor Medium
2 SharePoint as database High Performance complaints High (migration)
3 No volume planning High Gradual performance decay Medium
4 Sensitive data in collections Critical Security audit Low
5 Missing indexes Medium Slow queries Low
6 Patch instead of SubmitForm Low No validation errors Low
7 Everything in OnStart High Slow app launch Medium
8 Gallery N+1 High Network tab analysis Medium
9 Unoptimized images Medium Gallery scroll lag Low
10 Timer polling Medium API throttling Low
11 Formulas in gallery items Medium Gallery scroll lag Medium
12 No Concurrent() Medium Sequential slow loads Low
13 Mega-app High 40+ screens, 10s+ load High (refactor)
14 Hardcoded config Medium Env promotion failures Low
15 No error handling High Silent failures Medium
16 No component library Medium Inconsistent UX Medium
17 No ALM pipeline Critical Prod incidents High
18 UI hiding for security Critical Security audit Medium
19 Shared elevated connections Critical Security audit Medium
20 No audit trail High Compliance audit Medium
21 Exposed API keys Critical Flow run history review Low
22 No executive sponsor High Budget/staffing blocks Organizational
23 Not measuring adoption Medium Unknown usage Low
24 Licensing surprises Critical Budget review Planning
25 No user research High Low adoption Medium

Architecture Decision and Tradeoffs

When designing low-code development solutions with Power Apps, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

  • https://learn.microsoft.com/power-apps/
  • https://learn.microsoft.com/power-platform/admin/
  • https://learn.microsoft.com/power-platform/guidance/

Public Examples from Official Sources

  • These examples are sourced from official public Microsoft documentation and sample repositories.
  • Documentation examples: https://learn.microsoft.com/power-apps/
  • Sample repositories: https://github.com/microsoft/PowerApps-Samples
  • Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

Key Takeaways

  • Delegation is the #1 technical trap — the blue dotted underline in the formula bar is your early warning system, never ignore it
  • SharePoint lists are not databases — migrate to Dataverse when you expect 5,000+ records or need relational data
  • Load data on demand, not all at once — use Concurrent() and screen-level OnVisible instead of bloated App.OnStart
  • Security through UI hiding is not security — always enforce access at the data layer with Dataverse security roles and column-level security
  • Check licensing costs in Week 1, not Week 12 — premium connectors and Dataverse can add $120K+/year for 500 users
  • Build micro-apps (5–8 screens) linked via Launch(), not mega-apps with 40 screens and 15-second load times
  • Error handling is mandatory — wrap every Patch(), API call, and flow invocation in IfError() with user-friendly messages
  • Measure adoption with DAU, task completion rate, and CSAT — "nobody complained" is not a success metric
  • Shadow real users for 30 minutes before building — the workflow you imagine is never the workflow they actually follow
  • Every pitfall on this list was made by smart, experienced people — the difference is whether you learn from their mistakes or repeat them

Additional Resources

Discussion