appwrite/appwrite · error · Appwrite\Extend\Exception
user_target_not_found
user_target_not_found
Error message
The target could not be found.
What it means
Thrown by the update-push-target endpoint (PUT/PATCH /v1/account/targets/:targetId) when the target document fetched for the given targetId is empty. The target does not exist. Lookup uses authorization->skip so absence is real, not a permission issue.
Source
Thrown at app/controllers/api/account.php:4733
model: Response::MODEL_TARGET,
)
],
contentType: ContentType::JSON
))
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
->inject('queueForEvents')
->inject('user')
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('authorization')
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($user->getId() !== $target->getAttribute('userId')) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($identifier) {
$target
->setAttribute('identifier', $identifier)
->setAttribute('expired', false);
}
$detector = new Detector($request->getUserAgent());
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
$device = $detector->getDevice();
$target->setAttribute('name', "{$device['deviceBrand']} {$device['deviceModel']}");View on GitHub (pinned to cd368e707d)
Solutions
- Before updating, ensure the target still exists (GET) or refresh the local target list.
- On this error, register a new target via createTarget('unique()', ...) and store the returned ID.
- Do not cache targetId indefinitely; refetch from the server when the app starts.
Example fix
// before: update a cached target id that may be gone
await account.updateTarget(cachedTargetId, { identifier: newToken });
// after: fall back to create when the target is missing
try {
await account.updateTarget(cachedTargetId, { identifier: newToken });
} catch (e) {
if (e.code === 'user_target_not_found') {
const created = await account.createTarget('unique()', { identifier: newToken, providerId });
cacheTargetId(created.$id);
} else { throw e; }
} Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the cached target ID is still listed before updating.
const targets = await account.listTargets();
const exists = targets.targets.some(t => t.$id === cachedId);
if (!exists) throw new ClientError('Target is gone; re-register.'); Type guard
function isTargetIdLike(id) {
return typeof id === 'string' && id.length >= 8;
} Try / catch
try {
await account.updateTarget(cachedId, { identifier: token });
} catch (e) {
if (e.code === 'user_target_not_found') {
const created = await account.createTarget('unique()', { identifier: token, providerId });
cacheTargetId(created.$id);
} else { throw e; }
} Prevention
- Refetch the target list on app launch rather than trusting a cached ID.
- After delete, evict the ID from local caches immediately.
- On token refresh, be prepared to create rather than update.
When it happens
Trigger: PUT /v1/account/targets/:targetId where :targetId does not exist. Common after the target was deleted, the ID is stale, or the client is using an invented/hardcoded value.
Common situations: Target was purged by a delete call but the client cached the ID; push-token refresh runs after the device row was deleted; client constructed the URL with a wrong ID; database migration dropped targets.
Related errors
AI-assisted analysis of appwrite/appwrite@cd368e707d (2026-08-12).
Data as JSON: /api/errors/cc98bdb73a79e2ee.
Report an issue: GitHub.