Version 1.34.2 | Last Updated: August 2025
Delete operations provide safe, efficient row deletion from your Supabase database tables with comprehensive validation, authentication, and error handling.
The Supabase UE5 Plugin offers multiple approaches for deleting data:
All delete operations are asynchronous, thread-safe, and provide comprehensive error handling with automatic cleanup.
// C++ - Delete by ID using subsystem (recommended)
UAsyncDeleteRow* DeleteTask = UAsyncDeleteRow::DeleteRowAsync(
this, // World context
TEXT("users"), // Table name
TEXT("123"), // Row ID
ESupabaseKey::Service // Authentication level
);
DeleteTask->OnSuccess.AddDynamic(this, &AMyActor::OnDeleteSuccess);
DeleteTask->OnFailure.AddDynamic(this, &AMyActor::OnDeleteFailed);
DeleteTask->Activate();
Blueprint Usage
Use the Delete Row Async node found in the Supabase|Async category. Connect your success and failure event handlers.
The primary delete method using the subsystem for automatic connection management.
Function Signature:
static UAsyncDeleteRow* DeleteRowAsync(
UObject* WorldContextObject,
const FString& Table,
const FString& RowID,
ESupabaseKey KeyType = ESupabaseKey::Service
);
Parameters:
WorldContextObject - Context for the operation (usually this)Table - Database table nameRowID - Primary key value of the row to deleteKeyType - Authentication level (Anonymous/Authenticated/Service)Example:
void AGameActor::DeletePlayerRecord(const FString& PlayerID)
{
UAsyncDeleteRow* DeleteOp = UAsyncDeleteRow::DeleteRowAsync(
this,
TEXT("player_records"),
PlayerID,
ESupabaseKey::Service
);
DeleteOp->OnSuccess.AddDynamic(this, &AGameActor::OnPlayerDeleted);
DeleteOp->OnFailure.AddDynamic(this, &AGameActor::OnDeleteError);
DeleteOp->Activate();
}
UFUNCTION()
void AGameActor::OnPlayerDeleted(const FString& Message, bool bSuccess)
{
UE_LOG(LogTemp, Log, TEXT("Player record deleted successfully"));
}
UFUNCTION()
void AGameActor::OnDeleteError(const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Delete failed: %s"), *ErrorMessage);
}
Advanced delete method with direct connection control for custom configurations.
Function Signature:
static UAsyncDeleteRow* DeleteRowAdvanced(
UObject* WorldContextObject,
USupabaseConnection* Connection,
const FString& Table,
const FString& RowID,
ESupabaseKey KeyType = ESupabaseKey::Service
);
When to use:
Example:
void ACustomManager::DeleteWithCustomConnection(const FString& ItemID)
{
// Use a specific connection instance
UAsyncDeleteRow* DeleteOp = UAsyncDeleteRow::DeleteRowAdvanced(
this,
MyCustomConnection, // Direct connection reference
TEXT("inventory_items"),
ItemID,
ESupabaseKey::Authenticated
);
DeleteOp->OnSuccess.AddDynamic(this, &ACustomManager::OnItemDeleted);
DeleteOp->Activate();
}
Delete rows using field-based filters instead of primary keys.
Function Signature:
static UAsyncDeleteRow* DeleteRowWithFilterAsync(
UObject* WorldContextObject,
const FString& Table,
const FString& FilterField,
const FString& FilterValue,
ESupabaseKey KeyType = ESupabaseKey::Service
);
Example:
void ASessionManager::DeleteExpiredSessions()
{
// Delete all sessions for a specific user
UAsyncDeleteRow* DeleteOp = UAsyncDeleteRow::DeleteRowWithFilterAsync(
this,
TEXT("user_sessions"),
TEXT("user_id"), // Filter field
TEXT("user123"), // Filter value
ESupabaseKey::Service
);
DeleteOp->OnSuccess.AddDynamic(this, &ASessionManager::OnSessionsCleared);
DeleteOp->Activate();
}
Delete multiple rows using complex filter combinations.
Function Signature:
static UAsyncDeleteRow* DeleteRowsWithFiltersAsync(
UObject* WorldContextObject,
const FString& Table,
const TMap<FString, FString>& Filters,
ESupabaseKey KeyType = ESupabaseKey::Service
);
Example:
void ACleanupManager::DeleteOldLogs()
{
// Create filter map
TMap<FString, FString> Filters;
Filters.Add(TEXT("log_level"), TEXT("debug"));
Filters.Add(TEXT("created_before"), TEXT("2024-01-01"));
UAsyncDeleteRow* DeleteOp = UAsyncDeleteRow::DeleteRowsWithFiltersAsync(
this,
TEXT("application_logs"),
Filters,
ESupabaseKey::Service
);
DeleteOp->OnSuccess.AddDynamic(this, &ACleanupManager::OnLogsCleared);
DeleteOp->Activate();
}
| Key Type | Use Case | Security Level |
|---|---|---|
Anonymous |
Public data deletion | Low |
Authenticated |
User-owned data deletion | Medium |
Service |
Admin/system operations | High |
Security Recommendations:
Service key for administrative operationsAuthenticated key for user-owned dataAnonymous key for sensitive deletions404 - Row Not Found
UFUNCTION()
void AMyActor::OnDeleteError(const FString& ErrorMessage)
{
if (ErrorMessage.Contains(TEXT("not found")))
{
// Handle case where row doesn't exist
UE_LOG(LogTemp, Warning, TEXT("Row already deleted or doesn't exist"));
}
}
403 - Insufficient Permissions
// Ensure proper authentication level
UAsyncDeleteRow* DeleteOp = UAsyncDeleteRow::DeleteRowAsync(
this,
TEXT("admin_settings"),
TEXT("setting_id"),
ESupabaseKey::Service // Use service key for admin operations
);
401 - Authentication Required
// Check authentication status before deletion
USupabaseSubsystem* Supabase = USupabaseManager::GetSupabaseSubsystem(this);
if (Supabase && Supabase->IsUserLoggedIn())
{
// Proceed with authenticated deletion
UAsyncDeleteRow::DeleteRowAsync(this, TABLE, ID, ESupabaseKey::Authenticated);
}
else
{
// Handle authentication requirement
UE_LOG(LogTemp, Error, TEXT("User must be logged in to delete data"));
}
The plugin performs comprehensive input validation:
// ✅ Recommended - Uses subsystem
UAsyncDeleteRow::DeleteRowAsync(this, TABLE, ID);
// ❌ Avoid - Manual connection management
UAsyncDeleteRow::DeleteRowAdvanced(this, CustomConnection, TABLE, ID);
void ADataManager::DeleteRecord(const FString& RecordID)
{
UAsyncDeleteRow* DeleteOp = UAsyncDeleteRow::DeleteRowAsync(
this, TEXT("records"), RecordID
);
// Always bind both success and failure handlers
DeleteOp->OnSuccess.AddDynamic(this, &ADataManager::OnDeleteSuccess);
DeleteOp->OnFailure.AddDynamic(this, &ADataManager::OnDeleteFailure);
DeleteOp->Activate();
}
UFUNCTION()
void ADataManager::OnDeleteSuccess(const FString& Message, bool bSuccess)
{
// Update UI, refresh lists, etc.
RefreshDataDisplay();
}
UFUNCTION()
void ADataManager::OnDeleteFailure(const FString& ErrorMessage)
{
// Show user-friendly error message
ShowErrorNotification(TEXT("Failed to delete record. Please try again."));
// Log detailed error for debugging
UE_LOG(LogTemp, Error, TEXT("Delete operation failed: %s"), *ErrorMessage);
}
// User deleting their own profile
UAsyncDeleteRow::DeleteRowAsync(this, TEXT("user_profiles"), UserID, ESupabaseKey::Authenticated);
// Admin deleting any user (requires service key)
UAsyncDeleteRow::DeleteRowAsync(this, TEXT("user_profiles"), UserID, ESupabaseKey::Service);
// Cleaning up public cache data
UAsyncDeleteRow::DeleteRowAsync(this, TEXT("public_cache"), CacheID, ESupabaseKey::Anonymous);
void AInventoryManager::DeleteItem(const FString& ItemID)
{
// Validate item exists and user owns it
if (ItemID.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("Cannot delete item: Invalid ID"));
return;
}
if (!DoesUserOwnItem(ItemID))
{
UE_LOG(LogTemp, Error, TEXT("Cannot delete item: User doesn't own item"));
return;
}
// Proceed with deletion
UAsyncDeleteRow::DeleteRowAsync(this, TEXT("inventory"), ItemID);
}
Delete operations integrate seamlessly with real-time subscriptions:
void AGameManager::InitializeRealtimeMonitoring()
{
// Subscribe to player table changes
UAsyncRealtime* RealtimeOp = UAsyncRealtime::RealtimeSubscribeAsync(
this, TEXT("players")
);
RealtimeOp->OnDelete.AddDynamic(this, &AGameManager::OnPlayerDeleted);
RealtimeOp->Activate();
}
UFUNCTION()
void AGameManager::OnPlayerDeleted(const FString& EventType, const FString& Payload)
{
// Parse the deletion event
// Update local player list
// Refresh UI elements
RefreshPlayersList();
}
For multiple deletions, use filter-based operations instead of individual calls:
// ❌ Inefficient - Multiple individual deletions
for (const FString& ItemID : ItemsToDelete)
{
UAsyncDeleteRow::DeleteRowAsync(this, TEXT("items"), ItemID);
}
// ✅ Efficient - Single batch operation
TMap<FString, FString> Filters;
Filters.Add(TEXT("owner_id"), CurrentPlayerID);
Filters.Add(TEXT("marked_for_deletion"), TEXT("true"));
UAsyncDeleteRow::DeleteRowsWithFiltersAsync(this, TEXT("items"), Filters);
The plugin handles automatic cleanup, but you can optimize by avoiding unnecessary references:
void ADataManager::DeleteMultipleRecords(const TArray<FString>& RecordIDs)
{
for (int32 i = 0; i < RecordIDs.Num(); i++)
{
UAsyncDeleteRow* DeleteOp = UAsyncDeleteRow::DeleteRowAsync(
this, TEXT("records"), RecordIDs[i]
);
// Don't store references - let garbage collection handle cleanup
DeleteOp->OnSuccess.AddDynamic(this, &ADataManager::OnRecordDeleted);
DeleteOp->Activate();
}
}
Always implement RLS policies for production applications:
-- Example RLS policy for user-owned data
CREATE POLICY "Users can delete own records" ON user_records
FOR DELETE USING (auth.uid() = user_id);
-- Enable RLS on the table
ALTER TABLE user_records ENABLE ROW LEVEL SECURITY;
Consider database-level cascade rules for related data:
-- Automatically delete related records
ALTER TABLE user_sessions
ADD CONSTRAINT fk_user_sessions_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE;
“Connection cannot be null when not using subsystem”
DeleteRowAsync instead of DeleteRowAdvanced, or ensure valid connection“Invalid table name format”
“Row ID contains invalid characters”
“No active connection in subsystem”
// Initialize connection first
USupabaseManager::InitializeSupabase(this, MyConnection);
// Then perform delete operations
UAsyncDeleteRow::DeleteRowAsync(this, TEXT("table"), TEXT("id"));
If migrating from direct client usage:
// Old approach (deprecated)
USupabaseClient* Client = NewObject<USupabaseClient>();
Client->SetSupabaseConnection(Connection);
Client->DeleteRow(TABLE, ID, EAuthenticationToken::Service);
// New approach (recommended)
UAsyncDeleteRow::DeleteRowAsync(this, TABLE, ID, ESupabaseKey::Service);
| Method | Description |
|---|---|
DeleteRowAsync |
Delete single row by ID using subsystem |
DeleteRowAdvanced |
Delete with custom connection |
DeleteRowWithFilterAsync |
Delete using field filter |
DeleteRowsWithFiltersAsync |
Batch delete with multiple filters |
| Event | Parameters | Description |
|---|---|---|
OnSuccess |
Message: FString, bSuccess: bool |
Fired on successful deletion |
OnFailure |
ErrorMessage: FString |
Fired on deletion failure |
Previous: Insert Operations