Building Views 
Perfect! Your panel now has data sources feeding in information and columns structuring that data. Time to create views - the final piece that makes your data useful and actionable for different audiences.
What You'll Learn 
By the end of this tutorial, you'll know how to:
- ✅ Create custom filtered views of your data
- ✅ Set up sorting and organization
- ✅ Configure view-specific column visibility
- ✅ Publish views for different user groups
- ✅ Manage view permissions and access
Prerequisites 
- ✅ Completed Creating Columns
- ✅ Have a panel with data sources and columns configured
- ✅ Column data is populated and visible
Understanding Views 
Views are customized presentations of your panel data:
Core Concepts 
- 🎯 Filtered subsets of your panel data
- 📊 Custom sorting and organization
- 👁️ Column visibility control per view
- 🔐 Permission-based access control
- 📱 Responsive layouts for different devices
View Types 
- Default View: The primary panel view with all data
- Filtered Views: Specific subsets (e.g., "Active Users Only")
- Sorted Views: Data organized by specific criteria
- Role-based Views: Customized for different user types
Step 1: Your First Custom View 
Let's start by creating a view for active users only:
// views-demo.ts
import { viewsAPI } from '@panels/app/api'
// Use your panel ID from previous tutorials
const PANEL_ID = "123" // Replace with your actual panel ID
const TENANT_ID = "tenant-123"
const USER_ID = "user-456"
console.log("Starting views tutorial...")Create a Filtered View 
async function createActiveUsersView() {
  try {
    console.log("🎯 Creating active users view...")
    
    const activeUsersView = await viewsAPI.create(PANEL_ID, {
      title: "Active Users",
      description: "View showing only active user accounts",
      filters: [
        {
          columnTitle: "Account Status",
          operator: "equals",
          value: "active"
        }
      ],
      sorting: [
        {
          columnTitle: "Created Date",
          direction: "desc" // Newest first
        }
      ],
      columnVisibility: {
        "Full Name": true,
        "Email Address": true,
        "Account Status": true,
        "Status Label": false, // Hide redundant label
        "Created Date": true,
        "Account Age (Days)": true,
        "User Score": true,
        "Profile Link": true,
        "Display Name": false // Hide calculated display name
      },
      tenantId: TENANT_ID,
      userId: USER_ID
    })
    console.log("✅ Active users view created!")
    console.log(`   View ID: ${activeUsersView.id}`)
    console.log(`   Title: ${activeUsersView.title}`)
    console.log(`   Filters: ${activeUsersView.filters.length}`)
    console.log(`   Sorting: ${activeUsersView.sorting.length}`)
    
    return activeUsersView
  } catch (error) {
    console.error("❌ Failed to create active users view:", error)
    throw error
  }
}
// Create the view
const activeUsersView = await createActiveUsersView()Create a New Users View 
Let's create another view for recently created accounts:
async function createNewUsersView() {
  try {
    console.log("🆕 Creating new users view...")
    
    const newUsersView = await viewsAPI.create(PANEL_ID, {
      title: "New Users (Last 30 Days)",
      description: "Recently created user accounts",
      filters: [
        {
          columnTitle: "Account Age (Days)",
          operator: "lessThanOrEqual",
          value: 30
        }
      ],
      sorting: [
        {
          columnTitle: "Created Date", 
          direction: "desc" // Newest first
        },
        {
          columnTitle: "User Score",
          direction: "desc" // Highest scores first (secondary sort)
        }
      ],
      columnVisibility: {
        "Full Name": true,
        "Email Address": true,
        "Account Status": true,
        "Status Label": true, // Show status labels for new users
        "Created Date": true,
        "Account Age (Days)": true,
        "User Score": false, // Less relevant for new users
        "Profile Link": true,
        "Display Name": false
      },
      tenantId: TENANT_ID,
      userId: USER_ID
    })
    console.log("✅ New users view created!")
    console.log(`   View ID: ${newUsersView.id}`)
    console.log(`   Title: ${newUsersView.title}`)
    console.log(`   Age Filter: ≤ 30 days`)
    
    return newUsersView
  } catch (error) {
    console.error("❌ Failed to create new users view:", error)
    throw error
  }
}
// Create new users view
const newUsersView = await createNewUsersView()Step 2: Advanced View Configuration 
Let's create more sophisticated views with multiple filters and complex sorting:
High-Value Users View 
async function createHighValueUsersView() {
  try {
    console.log("💎 Creating high-value users view...")
    
    const highValueView = await viewsAPI.create(PANEL_ID, {
      title: "High-Value Users",
      description: "Active users with high engagement scores",
      filters: [
        {
          columnTitle: "Account Status",
          operator: "equals",
          value: "active"
        },
        {
          columnTitle: "User Score",
          operator: "greaterThan",
          value: 75
        },
        {
          columnTitle: "Account Age (Days)",
          operator: "greaterThan",
          value: 7 // At least a week old
        }
      ],
      sorting: [
        {
          columnTitle: "User Score",
          direction: "desc" // Highest scores first
        },
        {
          columnTitle: "Account Age (Days)",
          direction: "desc" // Older accounts second
        }
      ],
      columnVisibility: {
        "Full Name": true,
        "Email Address": true,
        "Account Status": false, // All active, no need to show
        "Status Label": false,
        "Created Date": true,
        "Account Age (Days)": true,
        "User Score": true, // Key metric
        "Profile Link": true,
        "Display Name": true // Show formatted names
      },
      tenantId: TENANT_ID,
      userId: USER_ID
    })
    console.log("✅ High-value users view created!")
    console.log(`   Filters: Status=active, Score>75, Age>7 days`)
    console.log(`   Sorting: Score DESC, Age DESC`)
    
    return highValueView
  } catch (error) {
    console.error("❌ Failed to create high-value users view:", error)
    throw error
  }
}
// Create high-value view
const highValueView = await createHighValueUsersView()Problem Users View 
async function createProblemUsersView() {
  try {
    console.log("⚠️ Creating problem users view...")
    
    const problemUsersView = await viewsAPI.create(PANEL_ID, {
      title: "Users Needing Attention",
      description: "Suspended users and old pending accounts",
      filters: [
        {
          // Using OR logic for multiple conditions
          logicalOperator: "OR",
          conditions: [
            {
              columnTitle: "Account Status",
              operator: "equals",
              value: "suspended"
            },
            {
              logicalOperator: "AND",
              conditions: [
                {
                  columnTitle: "Account Status", 
                  operator: "equals",
                  value: "pending"
                },
                {
                  columnTitle: "Account Age (Days)",
                  operator: "greaterThan",
                  value: 7 // Pending for more than a week
                }
              ]
            }
          ]
        }
      ],
      sorting: [
        {
          columnTitle: "Account Status",
          direction: "asc" // Suspended first
        },
        {
          columnTitle: "Account Age (Days)", 
          direction: "desc" // Oldest problems first
        }
      ],
      columnVisibility: {
        "Full Name": true,
        "Email Address": true,
        "Account Status": true, // Important to see status
        "Status Label": true, // Visual status indicators
        "Created Date": true,
        "Account Age (Days)": true,
        "User Score": false, // Not relevant for problem users
        "Profile Link": true,
        "Display Name": false
      },
      tenantId: TENANT_ID,
      userId: USER_ID
    })
    console.log("✅ Problem users view created!")
    console.log("   Logic: Suspended OR (Pending AND Age > 7 days)")
    
    return problemUsersView
  } catch (error) {
    console.error("❌ Failed to create problem users view:", error)
    throw error
  }
}
// Create problem users view
const problemUsersView = await createProblemUsersView()Step 3: View Management 
Let's learn how to list, update, and organize our views:
List All Views 
async function listAllViews() {
  try {
    console.log("📋 Listing all panel views...")
    
    const views = await viewsAPI.list(PANEL_ID, TENANT_ID, USER_ID)
    console.log(`🎯 Panel has ${views.length} view(s):`)
    console.log("=".repeat(60))
    
    views.forEach((view, index) => {
      console.log(`📊 ${index + 1}. ${view.title}`)
      console.log(`   Description: ${view.description}`)
      console.log(`   Filters: ${view.filters?.length || 0}`)
      console.log(`   Sorting: ${view.sorting?.length || 0} criteria`)
      
      // Count visible columns
      const visibleColumns = Object.values(view.columnVisibility || {})
        .filter(visible => visible).length
      console.log(`   Visible Columns: ${visibleColumns}`)
      
      console.log(`   Published: ${view.isPublished ? "Yes" : "No"}`)
      console.log("")
    })
    
    return views
  } catch (error) {
    console.error("❌ Failed to list views:", error)
    throw error
  }
}
// List all views
const allViews = await listAllViews()Update View Configuration 
async function updateViewConfig() {
  try {
    console.log("⚙️ Updating view configuration...")
    
    // Update the active users view to include more sorting
    const updatedView = await viewsAPI.update(
      PANEL_ID,
      activeUsersView.id,
      {
        sorting: [
          {
            columnTitle: "User Score",
            direction: "desc" // Add score sorting
          },
          {
            columnTitle: "Created Date",
            direction: "desc" // Keep date sorting as secondary
          }
        ],
        columnVisibility: {
          ...activeUsersView.columnVisibility,
          "User Score": true // Show scores now that we're sorting by them
        }
      },
      TENANT_ID,
      USER_ID
    )
    console.log("✅ View configuration updated!")
    console.log(`   View: ${updatedView.title}`)
    console.log(`   New Sorting: Score DESC, Date DESC`)
    
    return updatedView
  } catch (error) {
    console.error("❌ Failed to update view:", error)
    throw error
  }
}
// Update view
const updatedActiveView = await updateViewConfig()Step 4: Publishing Views 
Now let's publish our views to make them available to users:
Publish Individual Views 
async function publishViews() {
  try {
    console.log("📢 Publishing views...")
    
    // Publish the active users view for general use
    await viewsAPI.publish(
      PANEL_ID,
      activeUsersView.id,
      {
        publishedTitle: "Active Users", // Public facing title
        description: "View all active user accounts",
        permissions: ["read"], // Users can view but not edit
        userGroups: ["managers", "support"], // Who can access
        isDefault: false
      },
      TENANT_ID,
      USER_ID
    )
    
    console.log("✅ Active Users view published!")
    console.log("   Available to: managers, support")
    
    // Publish the high-value users view for managers only
    await viewsAPI.publish(
      PANEL_ID,
      highValueView.id,
      {
        publishedTitle: "High-Value Users",
        description: "Users with high engagement scores",
        permissions: ["read"],
        userGroups: ["managers"], // Restricted access
        isDefault: false
      },
      TENANT_ID,
      USER_ID
    )
    
    console.log("✅ High-Value Users view published!")
    console.log("   Available to: managers only")
    
    // Publish problem users view with edit permissions
    await viewsAPI.publish(
      PANEL_ID,
      problemUsersView.id,
      {
        publishedTitle: "Users Needing Attention", 
        description: "Accounts requiring review or action",
        permissions: ["read", "edit"], // Can modify user status
        userGroups: ["admins", "support"],
        isDefault: false
      },
      TENANT_ID,
      USER_ID
    )
    
    console.log("✅ Problem Users view published!")
    console.log("   Available to: admins, support")
    console.log("   Permissions: read, edit")
    
  } catch (error) {
    console.error("❌ Failed to publish views:", error)
    throw error
  }
}
// Publish views
await publishViews()Set Default View 
async function setDefaultView() {
  try {
    console.log("🏠 Setting default view...")
    
    // Make active users the default view for most users
    await viewsAPI.publish(
      PANEL_ID,
      activeUsersView.id,
      {
        publishedTitle: "Active Users",
        description: "Default view showing active user accounts",
        permissions: ["read"],
        userGroups: ["managers", "support", "viewers"],
        isDefault: true // This will be the default view
      },
      TENANT_ID,
      USER_ID
    )
    
    console.log("✅ Default view set!")
    console.log("   Default: Active Users view")
    console.log("   Available to: managers, support, viewers")
    
  } catch (error) {
    console.error("❌ Failed to set default view:", error)
    throw error
  }
}
// Set default view
await setDefaultView()Step 5: Advanced View Features 
Let's explore some advanced view capabilities:
View with Custom Sorting 
async function createCustomSortingView() {
  try {
    console.log("📊 Creating view with advanced sorting...")
    
    const sortedView = await viewsAPI.create(PANEL_ID, {
      title: "Users by Engagement",
      description: "All users sorted by multiple engagement metrics",
      sorting: [
        {
          columnTitle: "Account Status",
          direction: "asc", // Active first, then others
          customOrder: ["active", "pending", "inactive", "suspended"]
        },
        {
          columnTitle: "User Score",
          direction: "desc" // High scores first within each status
        },
        {
          columnTitle: "Account Age (Days)",
          direction: "desc" // Older accounts as tiebreaker
        }
      ],
      columnVisibility: {
        "Full Name": true,
        "Email Address": true,
        "Account Status": true,
        "Status Label": true,
        "Created Date": false, // Age is more relevant
        "Account Age (Days)": true,
        "User Score": true,
        "Profile Link": true,
        "Display Name": false
      },
      tenantId: TENANT_ID,
      userId: USER_ID
    })
    console.log("✅ Advanced sorting view created!")
    console.log("   Sort: Status (custom order) → Score DESC → Age DESC")
    
    return sortedView
  } catch (error) {
    console.error("❌ Failed to create custom sorting view:", error)
    throw error
  }
}
// Create custom sorting view
const sortedView = await createCustomSortingView()Complete Views Workflow 
Here's a complete example that creates a full set of views:
import { viewsAPI } from '@panels/app/api'
const PANEL_ID = "123" // Your panel ID
const TENANT_ID = "tenant-123"
const USER_ID = "user-456"
async function createCompleteViewSet() {
  console.log("🚀 Creating Complete View Set\n")
  
  try {
    // 1. Create core views
    console.log("1️⃣ Creating core views...")
    
    const coreViews = await Promise.all([
      // All users view (default)
      viewsAPI.create(PANEL_ID, {
        title: "All Users",
        description: "Complete list of all users",
        sorting: [{ columnTitle: "Created Date", direction: "desc" }],
        tenantId: TENANT_ID,
        userId: USER_ID
      }),
      
      // Active users view
      viewsAPI.create(PANEL_ID, {
        title: "Active Users",
        description: "Active user accounts only",
        filters: [{ columnTitle: "Account Status", operator: "equals", value: "active" }],
        sorting: [{ columnTitle: "User Score", direction: "desc" }],
        tenantId: TENANT_ID,
        userId: USER_ID
      }),
      
      // Recent users view
      viewsAPI.create(PANEL_ID, {
        title: "Recent Users",
        description: "Users created in the last 7 days",
        filters: [{ columnTitle: "Account Age (Days)", operator: "lessThanOrEqual", value: 7 }],
        sorting: [{ columnTitle: "Created Date", direction: "desc" }],
        tenantId: TENANT_ID,
        userId: USER_ID
      })
    ])
    
    console.log(`   ✅ Created ${coreViews.length} core views\n`)
    
    // 2. Create specialized views
    console.log("2️⃣ Creating specialized views...")
    
    const specializedViews = await Promise.all([
      // High performers view
      viewsAPI.create(PANEL_ID, {
        title: "Top Performers",
        description: "Users with highest engagement scores",
        filters: [
          { columnTitle: "Account Status", operator: "equals", value: "active" },
          { columnTitle: "User Score", operator: "greaterThan", value: 80 }
        ],
        sorting: [{ columnTitle: "User Score", direction: "desc" }],
        tenantId: TENANT_ID,
        userId: USER_ID
      }),
      
      // Action needed view
      viewsAPI.create(PANEL_ID, {
        title: "Action Required",
        description: "Accounts needing attention",
        filters: [
          {
            logicalOperator: "OR",
            conditions: [
              { columnTitle: "Account Status", operator: "equals", value: "suspended" },
              { columnTitle: "Account Status", operator: "equals", value: "pending" }
            ]
          }
        ],
        sorting: [{ columnTitle: "Account Age (Days)", direction: "desc" }],
        tenantId: TENANT_ID,
        userId: USER_ID
      })
    ])
    
    console.log(`   ✅ Created ${specializedViews.length} specialized views\n`)
    
    // 3. Publish views with appropriate permissions
    console.log("3️⃣ Publishing views...")
    
    const allViews = [...coreViews, ...specializedViews]
    
    // Publish each view with role-based access
    await Promise.all([
      // All users - default view for everyone
      viewsAPI.publish(PANEL_ID, coreViews[0].id, {
        publishedTitle: "All Users",
        permissions: ["read"],
        userGroups: ["viewers", "managers", "admins"],
        isDefault: true
      }, TENANT_ID, USER_ID),
      
      // Active users - for managers and up
      viewsAPI.publish(PANEL_ID, coreViews[1].id, {
        publishedTitle: "Active Users",
        permissions: ["read"],
        userGroups: ["managers", "admins"]
      }, TENANT_ID, USER_ID),
      
      // Top performers - managers only
      viewsAPI.publish(PANEL_ID, specializedViews[0].id, {
        publishedTitle: "Top Performers",
        permissions: ["read"],
        userGroups: ["managers", "admins"]
      }, TENANT_ID, USER_ID),
      
      // Action required - admins with edit permissions
      viewsAPI.publish(PANEL_ID, specializedViews[1].id, {
        publishedTitle: "Action Required",
        permissions: ["read", "edit"],
        userGroups: ["admins"]
      }, TENANT_ID, USER_ID)
    ])
    
    console.log("   ✅ All views published with permissions\n")
    
    // 4. Verify final view set
    console.log("4️⃣ Verifying final view set...")
    const publishedViews = await viewsAPI.list(PANEL_ID, TENANT_ID, USER_ID)
    
    console.log(`📊 Panel now has ${publishedViews.length} views:`)
    publishedViews.forEach((view, i) => {
      const defaultBadge = view.isDefault ? " (DEFAULT)" : ""
      console.log(`   📋 ${view.title}${defaultBadge}`)
      console.log(`      ${view.description}`)
    })
    
    console.log("\n🎉 View creation completed!")
    console.log("   Your panel is now ready for users!")
    
  } catch (error) {
    console.error("💥 View creation failed:", error.message)
    throw error
  }
}
// Run the complete workflow
createCompleteViewSet()
  .then(() => {
    console.log("\n🎯 Panel setup completed! Your users can now access structured data views.")
  })
  .catch(error => {
    console.error("\n💥 Setup failed:", error.message)
  })View Configuration Reference 
Filter Operators 
| Operator | Description | Example | 
|---|---|---|
| equals | Exact match | Status = "active" | 
| notEquals | Not equal | Status ≠ "inactive" | 
| contains | Text contains | Name contains "john" | 
| startsWith | Text starts with | Email starts with "admin" | 
| endsWith | Text ends with | Email ends with ".com" | 
| greaterThan | Number/date greater | Score > 75 | 
| lessThan | Number/date less | Age < 30 | 
| greaterThanOrEqual | Number/date >= | Score >= 50 | 
| lessThanOrEqual | Number/date <= | Age <= 365 | 
| between | Range | Score between 50-100 | 
| in | In list | Status in ["active", "pending"] | 
| notIn | Not in list | Status not in ["suspended"] | 
Sorting Options 
interface SortingCriteria {
  columnTitle: string         // Column to sort by
  direction: 'asc' | 'desc'   // Sort direction
  customOrder?: string[]      // Custom ordering for select fields
  nullsFirst?: boolean        // Where to put null values
}Permission Levels 
| Permission | Description | User Can | 
|---|---|---|
| read | View only | See data, filter, sort | 
| edit | Modify data | Update column values | 
| create | Add records | Create new rows | 
| delete | Remove records | Delete rows | 
| manage | Full control | Edit view settings | 
Troubleshooting 
Common Issues 
View Not Showing Data:
// Check if filters are too restrictive
const view = await viewsAPI.get(panelId, viewId, tenantId, userId)
console.log("Filters:", view.filters)
// Verify column visibility settings
console.log("Visible columns:", Object.entries(view.columnVisibility)
  .filter(([col, visible]) => visible)
  .map(([col]) => col))Sorting Not Working:
// Verify column titles in sorting match actual column titles
const columns = await panelsAPI.columns.list(panelId, tenantId, userId)
const columnTitles = columns.map(col => col.title)
console.log("Available columns:", columnTitles)
// Check if sorted columns are visible
view.sorting.forEach(sort => {
  const isVisible = view.columnVisibility[sort.columnTitle]
  console.log(`${sort.columnTitle}: ${isVisible ? "Visible" : "Hidden"}`)
})Permission Errors:
// Verify user groups and permissions
console.log("User groups:", userGroups)
console.log("View permissions:", view.permissions)
console.log("Published for:", view.userGroups)Next Steps 
Congratulations! You've built a complete panel with data sources, columns, and views. Here's what you can do next:
🎯 Immediate Next Steps:
- Next Steps → - Complete your learning journey
🔗 Related Guides:
- View Permissions - Advanced access control
- Complex Filtering - Multi-criteria filters
- View Templates - Reusable view configurations
🛠️ Advanced Topics:
- Multi-tenant Views - Tenant-specific views
- Performance Optimization - Large dataset handling
- Custom Widgets - Enhanced view components
Summary 
In this tutorial, you learned how to:
✅ Create Custom Views - Filtered subsets of your panel data
 ✅ Configure Sorting - Multiple criteria and custom ordering
 ✅ Control Column Visibility - Show relevant data per view
 ✅ Publish Views - Make views available to specific user groups
 ✅ Set Permissions - Control who can view and edit data
 ✅ Organize Access - Role-based view distribution
Your panel is now a fully functional data management system that serves different user needs with tailored views!