calcom/cal.diy · error · HttpError

`code` must be a string

Error message

`code` must be a string

What it means

Thrown as an HttpError (HTTP 400) by the Dub OAuth callback when the `code` query parameter is not a string. OAuth callbacks receive code as a single string; a non-string (undefined, or string[] when duplicated) means the callback was invoked without an authorization code (user denied consent, or a malformed redirect). The handler only throws if no safe returnTo/onErrorReturnTo redirect target exists.

Source

Thrown at packages/app-store/dub/api/callback.ts:27

import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential";
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
import { dubAppKeysSchema } from "../lib/utils";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { code } = req.query;

  const state = decodeOAuthState(req, "dub");

  if (typeof code !== "string") {
    if (state?.onErrorReturnTo || state?.returnTo) {
      res.redirect(
        getSafeRedirectUrl(state.onErrorReturnTo) ??
          getSafeRedirectUrl(state?.returnTo) ??
          `${WEBAPP_URL}/apps/installed`
      );
      return;
    }
    throw new HttpError({ statusCode: 400, message: "`code` must be a string" });
  }

  if (!req.session?.user?.id) {
    throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
  }

  const { client_id, redirect_uris, client_secret } = await getParsedAppKeysFromSlug("dub", dubAppKeysSchema);

  const codeExchangeUrl = `https://api.dub.co/oauth/token`;

  const result = await fetch(codeExchangeUrl, {
    method: "POST",
    body: new URLSearchParams({
      code,
      client_id,
      redirect_uri: redirect_uris,
      client_secret,
      grant_type: "authorization_code",

View on GitHub (pinned to 176037d0af)

Solutions

  1. Always encode a returnTo / onErrorReturnTo in the OAuth state so denial degrades to a redirect, not a thrown 400.
  2. Handle the OAuth `error` and `error_description` query params (e.g. access_denied) explicitly before checking code.
  3. If code is an array, take the first element: const code = Array.isArray(req.query.code) ? req.query.code[0] : req.query.code.
  4. Confirm the Dub app's redirect_uris matches the Cal.com callback URL exactly.

Example fix

// before
if (typeof code !== "string") {
  if (state?.onErrorReturnTo || state?.returnTo) { res.redirect(...); return; }
  throw new HttpError({ statusCode: 400, message: "`code` must be a string" });
}

// after - treat denial / array gracefully
const code = Array.isArray(req.query.code) ? req.query.code[0] : req.query.code;
if (typeof code !== "string") {
  if (req.query.error) {
    res.redirect(`${WEBAPP_URL}/apps/installed?error=${encodeURIComponent(req.query.error)}`);
    return;
  }
  res.redirect(`${WEBAPP_URL}/apps/installed`);
  return;
}
Defensive patterns

Strategy: validation

Validate before calling

const rawCode = Array.isArray(req.query.code) ? req.query.code[0] : req.query.code;
if (typeof rawCode !== "string") {
  if (req.query.error) {
    res.redirect(`${WEBAPP_URL}/apps/installed?error=${encodeURIComponent(String(req.query.error))}`);
    return;
  }
  res.redirect(`${WEBAPP_URL}/apps/installed`);
  return;
}

Type guard

const isStringCode = (v: unknown): v is string => typeof v === "string" && v.length > 0;

Try / catch

if (typeof code !== "string") {
  const fallback = getSafeRedirectUrl(state?.onErrorReturnTo) ?? getSafeRedirectUrl(state?.returnTo) ?? `${WEBAPP_URL}/apps/installed`;
  res.redirect(fallback);
  return;
}

Prevention

When it happens

Trigger: Dub redirects back to /api/dub/callback with no `code` (user clicked 'Deny' on the Dub consent screen), with code as an array (?code=a&code=b), or with an error param instead of code; AND state.onErroredReturnTo / state.returnTo are also unset.

Common situations: User cancels the Dub OAuth consent; Dub misconfigured redirect_uri sending extra query params; integrations page opened the OAuth flow without setting a returnTo state.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/7ed595e475e891c1. Report an issue: GitHub.