appwrite/appwrite · error · Appwrite\Extend\Exception
project_invalid_success_url
project_invalid_success_url
Error message
Invalid redirect URL for OAuth success.
What it means
Thrown by GET /v1/account/sessions/oauth2/:provider/redirect when the success redirect URL fails validation against the project's allowed hostnames (and no dev key is present). The redirect validator enforces allow-listed domains to prevent open-redirect abuse.
Source
Thrown at app/controllers/api/account.php:1566
], fn ($domain) => \is_string($domain) && $domain !== '');
if (!empty($domains)) {
$rules = $authorization->skip(fn () => $dbForPlatform->find('rules', [
Query::equal('domain', \array_values(\array_unique($domains))),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::limit(2)
]));
foreach ($rules as $rule) {
$allowedHostnames = $redirectValidator->getAllowedHostnames();
$allowedHostnames[] = $rule->getAttribute('domain', '');
$redirectValidator->setAllowedHostnames($allowedHostnames);
}
}
}
if ($devKey->isEmpty() && !$redirectValidator->isValid($state['success'])) {
throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);
}
if ($devKey->isEmpty() && !empty($state['failure']) && !$redirectValidator->isValid($state['failure'])) {
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
}
$failure = [];
if (!empty($state['failure'])) {
$failure = URLParser::parse($state['failure']);
}
$failureRedirect = (function (string $type, ?string $message = null, ?int $code = null, ?\Throwable $previous = null, array $params = []) use ($failure, $response, $project, $oauthDefaultFailure) {
$exception = new Exception($type, $message, $code, $previous, params: $params);
if (!empty($failure)) {
$query = URLParser::parseQuery($failure['query']);
$query['error'] = json_encode([
'message' => $exception->getMessage(),
'type' => $exception->getType(),
'code' => !\is_null($code) ? $code : $exception->getCode(),View on GitHub (pinned to cd368e707d)
Solutions
- Add the success URL's host to the project's allowed hostnames in Console > Auth > Allowed Hostnames.
- Use a registered project domain or a listed localhost host.
- If developing locally, use a dev key or add the local host to the allow-list.
Example fix
// before
await account.createOAuth2Session('github', 'https://unregistered.example.com/success'); // throws
// after: add unregistered.example.com to allowed hostnames, then
await account.createOAuth2Session('github', 'https://app.example.com/success'); Defensive patterns
Strategy: validation
Validate before calling
const allowedHosts = ['app.example.com','localhost'];
function successUrlOk(url) {
const host = new URL(url).hostname;
return allowedHosts.includes(host);
}
if (!successUrlOk(success)) throw new Error('Register success host in Console'); Type guard
const isAllowedHost = (url, hosts) => hosts.includes(new URL(url).hostname);
Try / catch
try { await account.createOAuth2Session(p, success, failure); } catch (e) { if (e?.type === 'project_invalid_success_url') { promptRegisterHost(success); } else throw e; } Prevention
- Register every success-redirect host in Console > Auth > Allowed Hostnames.
- Use the same host for success and failure redirects.
- For local dev, allow-list localhost or use a dev key.
When it happens
Trigger: success URL host not in the project's allowed hostname list; success URL omitted/malformed when required; cross-origin redirect to an unregistered domain.
Common situations: New frontend domain not added to Auth > Allowed hosts; localhost used but not allow-listed; success URL pointing to a third-party domain.
Related errors
- project_invalid_failure_url
- project_provider_unsupported
- project_provider_disabled
- Missing required parameter: "fileId"
- Missing required parameter: "bucketId"
AI-assisted analysis of appwrite/appwrite@cd368e707d (2026-08-12).
Data as JSON: /api/errors/57b67f4edbe0c183.
Report an issue: GitHub.