The Supabase UE5 Plugin provides comprehensive query operations for database interaction through production-ready async nodes and Blueprint-friendly interfaces. All query operations are built with memory-safe, thread-safe patterns and integrate seamlessly with the SupabaseSubsystem for optimal performance.
The main async node for database queries with full Blueprint support and proper memory management.
Advanced filtering system supporting complex WHERE conditions, sorting, and pagination.
Static utility class providing simplified access to query operations.
Blueprint:
Query Table Async -> On Success
- Table: "users"
- Columns: "*"
- Query Filter: (none)
C++:
UAsyncQuery* QueryNode = UAsyncQuery::QueryTableAsync(
this,
TEXT("users"),
TEXT("*")
);
QueryNode->OnSuccess.AddDynamic(this, &AMyClass::OnQuerySuccess);
QueryNode->OnFailure.AddDynamic(this, &AMyClass::OnQueryFailure);
Blueprint:
Make Query Filter -> Add Filter -> Query Table Async
- Field Name: "active"
- Operator: Equals
- Value: "true"
C++:
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
Filter->AddFilter(TEXT("active"), EFilterOperator::Equals, TEXT("true"));
Filter->SetLimitFilter(10);
Filter->SetSortFilter(TEXT("created_at"), false); // DESC
UAsyncQuery* QueryNode = UAsyncQuery::QueryTableAsync(
this,
TEXT("users"),
TEXT("id,name,email"),
Filter
);
Select Specific Columns:
// Select only specific fields
UAsyncQuery::QueryTableAsync(this, TEXT("users"), TEXT("id,name,email"));
// Select with relationships (JSON expansion)
UAsyncQuery::QueryTableAsync(this, TEXT("users"), TEXT("*,profiles(*)"));
// Select with computed fields
UAsyncQuery::QueryTableAsync(this, TEXT("sales"), TEXT("*,total:price.multiply(quantity)"));
The QueryFilter supports multiple filter operators:
| Operator | Description | Example |
|---|---|---|
Equals |
Exact match | status = 'active' |
NotEquals |
Not equal | status != 'deleted' |
GreaterThan |
Greater than | age > 18 |
LessThan |
Less than | price < 100 |
Like |
Pattern matching | name LIKE '%john%' |
C++ Example:
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
// Multiple conditions (AND logic)
Filter->AddFilter(TEXT("status"), EFilterOperator::Equals, TEXT("active"));
Filter->AddFilter(TEXT("age"), EFilterOperator::GreaterThan, TEXT("18"));
Filter->AddFilter(TEXT("city"), EFilterOperator::Like, TEXT("%Berlin%"));
// Sorting and pagination
Filter->SetSortFilter(TEXT("created_at"), false); // DESC
Filter->SetLimitFilter(50);
Range Queries:
// Date range example
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
Filter->AddFilter(TEXT("created_at"), EFilterOperator::GreaterThan, TEXT("2024-01-01"));
Filter->AddFilter(TEXT("created_at"), EFilterOperator::LessThan, TEXT("2024-12-31"));
Text Search:
// Full-text search using LIKE
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
Filter->AddFilter(TEXT("description"), EFilterOperator::Like, TEXT("%game%"));
Uses subsystem by default - recommended for production.
static UAsyncQuery* QueryTableAsync(
UObject* WorldContextObject,
const FString& Table,
const FString& Columns,
UQueryFilter* QueryFilter = nullptr,
ESupabaseKey KeyType = ESupabaseKey::Service
);
For advanced use cases requiring specific connection control.
static UAsyncQuery* QueryTableAdvanced(
UObject* WorldContextObject,
USupabaseConnection* Connection,
const FString& Table,
const FString& Columns,
UQueryFilter* QueryFilter = nullptr,
ESupabaseKey KeyType = ESupabaseKey::Service
);
UFUNCTION()
void OnQueryFailure(FString Error)
{
if (USupabaseManager::IsNetworkError(Error))
{
// Handle network connectivity issues
RetryQuery();
}
else if (USupabaseManager::IsAuthenticationError(Error))
{
// Handle authentication problems
ReauthenticateUser();
}
else
{
// Handle other errors (syntax, permissions, etc.)
UE_LOG(LogTemp, Error, TEXT("Query failed: %s"), *Error);
}
}
The plugin automatically uses the SupabaseSubsystem for:
// ❌ Avoid selecting all columns for large tables
UAsyncQuery::QueryTableAsync(this, TEXT("large_table"), TEXT("*"));
// ✅ Select only needed columns
UAsyncQuery::QueryTableAsync(this, TEXT("large_table"), TEXT("id,name,status"));
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
Filter->SetLimitFilter(25); // Reasonable page size
Filter->SetSortFilter(TEXT("id"), true); // Consistent ordering
// Store query results in local cache
UPROPERTY()
TMap<FString, FJsonObjectWrapper> QueryCache;
void AGameMode::LoadActiveUsers()
{
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
Filter->AddFilter(TEXT("is_active"), EFilterOperator::Equals, TEXT("true"));
Filter->AddFilter(TEXT("last_login"), EFilterOperator::GreaterThan, TEXT("2024-01-01"));
Filter->SetSortFilter(TEXT("last_login"), false); // Most recent first
Filter->SetLimitFilter(100);
UAsyncQuery* Query = UAsyncQuery::QueryTableAsync(
this,
TEXT("users"),
TEXT("id,username,email,last_login,level"),
Filter
);
Query->OnSuccess.AddDynamic(this, &AGameMode::OnUsersLoaded);
Query->OnFailure.AddDynamic(this, &AGameMode::OnUsersLoadFailed);
}
void ALeaderboardWidget::LoadTopScores()
{
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
Filter->SetSortFilter(TEXT("score"), false); // Highest scores first
Filter->SetLimitFilter(10); // Top 10 only
UAsyncQuery* Query = UAsyncQuery::QueryTableAsync(
this,
TEXT("leaderboard"),
TEXT("player_name,score,achieved_at"),
Filter
);
Query->OnSuccess.AddDynamic(this, &ALeaderboardWidget::DisplayScores);
}
void AInventorySystem::SearchItems(const FString& SearchTerm)
{
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
Filter->AddFilter(TEXT("name"), EFilterOperator::Like, FString::Printf(TEXT("%%%s%%"), *SearchTerm));
Filter->AddFilter(TEXT("is_available"), EFilterOperator::Equals, TEXT("true"));
Filter->SetSortFilter(TEXT("rarity"), false); // Rarest first
UAsyncQuery* Query = UAsyncQuery::QueryTableAsync(
this,
TEXT("items"),
TEXT("id,name,description,rarity,price"),
Filter
);
Query->OnSuccess.AddDynamic(this, &AInventorySystem::OnItemsFound);
}
The AsyncQuery node provides automatic memory management:
virtual void BeginDestroy() override
{
SafeCleanup();
Super::BeginDestroy();
}
void SafeCleanup()
{
CancelPendingRequest();
UnbindEvents();
// Clear references
HttpRequest.Reset();
SupabaseClient = nullptr;
Connection = nullptr;
WorldContextObject = nullptr;
QueryFilter = nullptr;
}
UPROPERTY() for garbage collectionSetReadyToDestroy() when operation completeshttps://your-project.supabase.co/rest/v1/table_name?select=columns&filter_conditions
# Simple query
/rest/v1/users?select=*
# Filtered query
/rest/v1/users?select=id,name,email&status=eq.active&age=gt.18&limit=10&order=created_at.desc
# Complex query with relationships
/rest/v1/posts?select=*,author:users(name,email)&published=eq.true
// Query actors by type
UQueryFilter* Filter = USupabaseUtils::MakeQueryFilter();
Filter->AddFilter(TEXT("actor_class"), EFilterOperator::Equals, TEXT("APlayerCharacter"));
Filter->AddFilter(TEXT("level_name"), EFilterOperator::Equals, GetWorld()->GetMapName());
UAsyncQuery* Query = UAsyncQuery::QueryTableAsync(
this,
TEXT("persistent_actors"),
TEXT("*"),
Filter
);
// Query current state, then subscribe to changes
void AMultiplayerGameState::InitializeGameData()
{
// First, get current game state
UAsyncQuery* InitialQuery = UAsyncQuery::QueryTableAsync(
this, TEXT("game_sessions"), TEXT("*")
);
InitialQuery->OnSuccess.AddDynamic(this, &AMultiplayerGameState::OnInitialDataLoaded);
// Then subscribe to real-time updates
UAsyncRealtime* RealtimeNode = UAsyncRealtime::SubscribeToTableAsync(
this, TEXT("game_sessions")
);
RealtimeNode->OnInsert.AddDynamic(this, &AMultiplayerGameState::OnPlayerJoined);
RealtimeNode->OnUpdate.AddDynamic(this, &AMultiplayerGameState::OnGameStateChanged);
}
Query Returns Empty Results:
Performance Issues:
SELECT *Memory Leaks:
// Enable detailed logging
USupabaseManager::EnableDebugLogging(this, true);
// Check connection status
FString Status = USupabaseManager::GetConnectionStatus(this);
UE_LOG(LogTemp, Warning, TEXT("Connection Status: %s"), *Status);