nexu-io/open-design · error · DeployError
Cloudflare API token is required.
Error message
Cloudflare API token is required.
What it means
Thrown by writeCloudflarePagesConfig in apps/daemon/src/deploy.ts:145 as a `DeployError('Cloudflare API token is required.', 400)`. After merging the submitted input into `next`, if `next.token` is falsy the save is aborted. Note the token merge logic: if the submitted token equals `SAVED_CLOUDFLARE_TOKEN_MASK` (the masked placeholder shown back to the UI), the previously saved `current.token` is reused; so this throws when the user submits the mask (or empty) AND no prior token was ever saved.
Source
Thrown at apps/daemon/src/deploy.ts:145
export async function writeCloudflarePagesConfig(input: Partial<DeployConfig>) {
const current = await readCloudflarePagesConfig();
const tokenInput = typeof input?.token === 'string' ? input.token.trim() : '';
const cloudflarePages = normalizeCloudflarePagesConfigHints(input?.cloudflarePages, current.cloudflarePages);
const next: DeployConfig = {
token:
tokenInput && tokenInput !== SAVED_CLOUDFLARE_TOKEN_MASK
? tokenInput
: current.token,
accountId: typeof input?.accountId === 'string' ? input.accountId.trim() : current.accountId,
// Legacy installs may already have a saved Cloudflare Pages projectName.
// New writes intentionally stop treating it as user configuration: the
// deploy route derives a Pages project name from the current OD project,
// mirroring Vercel's automatic `od-${projectId}` deployment name.
projectName: '',
};
if (Object.keys(cloudflarePages).length > 0) next.cloudflarePages = cloudflarePages;
if (!next.token) throw new DeployError('Cloudflare API token is required.', 400);
if (!next.accountId) throw new DeployError('Cloudflare account ID is required.', 400);
await writeDeployConfigFile(deployConfigPath(CLOUDFLARE_PAGES_PROVIDER_ID), next);
return publicCloudflarePagesConfig(next);
}
async function writeDeployConfigFile(file: string, config: DeployConfig) {
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
try {
fs.chmodSync(file, 0o600);
} catch {
// Best effort on filesystems that do not support chmod.
}
}
export function publicDeployConfig(config: Partial<DeployConfig>) {
return {
providerId: VERCEL_PROVIDER_ID,View on GitHub (pinned to 5be4028344)
Solutions
- Provide a real Cloudflare API token in the request body (not the mask placeholder).
- If resubmitting from a UI that shows the mask, ensure the field is re-entered with the actual token value.
- Generate a token at Cloudflare dashboard -> My Profile -> API Tokens with the Pages + DNS permissions the deploy flow needs.
- Verify the config file path resolves correctly (OD_USER_STATE_DIR override) and that a prior write actually persisted a token.
Example fix
// before
await writeCloudflarePagesConfig({ token: SAVED_CLOUDFLARE_TOKEN_MASK, accountId: 'abc' }); // first-time save, no prior token
// after
await writeCloudflarePagesConfig({ token: process.env.CF_API_TOKEN, accountId: 'abc' }); Defensive patterns
Strategy: validation
Validate before calling
const token = typeof input?.token === 'string'
? input.token.trim()
: '';
if (!token || token === SAVED_CLOUDFLARE_TOKEN_MASK) {
const current = await readCloudflarePagesConfig();
if (!current.token) throw new Error('Provide a real Cloudflare API token');
} Type guard
function isUnmaskedToken(input: unknown, currentToken: string): boolean {
return typeof input === 'string'
&& input.trim().length > 0
&& input.trim() !== SAVED_CLOUDFLARE_TOKEN_MASK
? true
: Boolean(currentToken);
} Try / catch
try {
await writeCloudflarePagesConfig(input);
} catch (err) {
if (err instanceof DeployError && err.status === 400 && /token/i.test(err.message)) {
return res.status(400).json({ error: 'Enter your Cloudflare API token to save credentials.' });
}
throw err;
} Prevention
- Never submit the mask placeholder on a first-time save; always send the real token.
- Gate the Save button on a non-empty, non-mask token field when no prior token exists.
- Generate the token with Cloudflare Pages + DNS edit permissions to avoid follow-up errors.
When it happens
Trigger: POSTing Cloudflare config (token/accountId) to the deploy config route where the trimmed submitted token is empty or is the mask string, and `current.token` from the existing cloudflare-pages.json is also empty (file missing or never written).
Common situations: First-time Cloudflare setup where the user left the token field blank; the masked placeholder was submitted unchanged after a fresh install; the cloudflare-pages.json under OD_USER_STATE_DIR (~/.open-design) was deleted so `readCloudflarePagesConfig` returned an empty token; user copy-pasted only the accountId.
Related errors
- Vercel token is required.
- Cloudflare account ID is required.
- Cloudflare zone is required for a custom domain.
- Select a valid Cloudflare domain for the custom domain.
- Enter a valid subdomain prefix, for example "demo".
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/6c62cb428a21e47f.
Report an issue: GitHub.