Troubleshooting
Common issues and how to fix them.
Dev Server
Server won't start -- port already in use
Symptom: npx edgebase dev fails with Error: address already in use :8787.
Fix: Kill the process holding the port, then restart:
# Find the process
lsof -i :8787
# Kill it
kill -9 <PID>
# Restart
npx edgebase dev
Or use a different port:
npx edgebase dev --port 8788
Server won't start -- missing or invalid config
Symptom: Startup crashes with Cannot find module './edgebase.config.ts' or a config validation error.
Fix:
- Make sure
edgebase.config.tsexists at the project root. - Check that you are running
npx edgebase devfrom the project directory (the one containingedgebase.config.ts). - Validate your config structure -- every table must live inside a
databases.{namespace}.tablesobject. A baretableskey at the top level is invalid.
Server won't start -- esbuild errors
Symptom: Errors mentioning esbuild during npx edgebase dev or npx edgebase deploy.
Fix:
- Delete
node_modulesand reinstall:rm -rf node_modules
npm install - If you are on a new architecture (e.g., Apple Silicon) and installed Node through Rosetta, reinstall Node natively for your platform.
- Check that your
functions/files have valid TypeScript syntax. A syntax error in any function file causes the build to fail.
Function hot-reload not working
Symptom: You edit a file in functions/ but the dev server keeps running the old version.
Fix:
- Check for syntax errors in the file you changed. The watcher silently skips files that fail to compile.
- Make sure the file is inside the
functions/directory at the project root. - Restart the dev server:
npx edgebase dev. The watcher occasionally misses events after OS sleep or largegit checkoutoperations.
Authentication
JWT errors after dev server restart
Symptom: Existing tokens return 401 Unauthorized or invalid signature after restarting npx edgebase dev.
Why: The dev server auto-generates JWT signing keys on first run and stores them in .env.development. If that file is deleted or regenerated, old tokens become invalid.
Fix: Clear your client-side tokens and sign in again. If you need stable keys across restarts, keep .env.development intact and do not delete it.
Tokens expire immediately
Symptom: After sign-in, requests fail with 401 almost instantly.
Fix: Check your auth.session config. If accessTokenTTL is set very low, tokens expire quickly:
auth: {
session: {
accessTokenTTL: '15m', // default -- should be fine
refreshTokenTTL: '7d',
},
}
Also check that your server clock and client clock are not significantly out of sync. A clock difference of more than a few seconds can cause JWT time-based validation to fail.
OAuth callback fails with "redirect_uri mismatch"
Symptom: OAuth login redirects to the provider, but the callback returns an error about mismatched redirect URIs.
Fix:
- Set
baseUrlin your config to the exact origin where your EdgeBase server is reachable:export default defineConfig({
baseUrl: 'https://api.example.com',
// ...
}); - In your OAuth provider's console, add
{baseUrl}/api/auth/callback/{provider}as an authorized redirect URI. - If using
allowedRedirectUrls, make sure your app's callback URL matches one of the patterns.
Database
DB Live Query not receiving updates
Symptom: onSnapshot is registered but never fires on changes.
Cause 1 -- Authentication required: Database subscriptions require an authenticated WebSocket connection. Make sure the user is signed in before calling onSnapshot.
Cause 2 -- Same-client writes: The onSnapshot callback fires for changes made by other clients as well as the same client. If your callback is not firing, check that the write actually succeeded (check the network tab or server logs).
Cause 3 -- Access rule blocks the read: The subscription uses the table's read access rule at subscribe time. If the rule returns false, the subscription is silently rejected. Verify your access rules.
Cause 4 -- Wrong namespace: Make sure the db() namespace and optional ID match on both the write and subscribe sides:
// These must match
client.db('app').table('posts').onSnapshot(/* ... */);
client.db('app').table('posts').insert(/* ... */);
Access rules blocking requests unexpectedly
Symptom: A client request returns 403 Forbidden even though you think the user should have access.
Debugging steps:
- Check
releasemode. Withrelease: true, any table without explicit access rules denies all requests. During development, setrelease: false(the default) to allow unconfigured tables. - Inspect the
authobject. Logauthinside your access rule to see what the server receives:Check the dev server terminal for the output.access: {
read(auth, row) {
console.log('auth:', JSON.stringify(auth));
console.log('row:', JSON.stringify(row));
return auth !== null && auth.id === row.userId;
},
} - Check field names. A common mistake is comparing
auth.idto a field that does not exist on the row (e.g.,row.ownerIdwhen the schema field isuserId). - Check DB block access. For dynamic namespaces (
user,workspace), the DB-block-levelaccessrule is evaluated before table-level rules. Make sure both pass.
DB trigger not firing
Symptom: You defined a function in functions/ with a DB trigger name, but it never executes.
Fix:
- The function filename must match the pattern. For a table trigger on
app.posts, the file should be named using the convention documented in Triggers. - Check the dev server logs for errors in your function. A runtime error (e.g., accessing
undefined) causes the trigger to fail silently from the client's perspective. - Verify you are using the correct
context.datashape. ForafterInsertandafterUpdate, the new record is incontext.data.after. ForafterDelete, the deleted record is incontext.data.before:export default async function onTodoCreated(doc, context) {
const newRecord = context.data.after; // after insert/update
const oldRecord = context.data.before; // before update, or the deleted record
}
Storage
Upload fails with 403
Symptom: client.storage.bucket('photos').upload(file) returns 403.
Fix: Check your storage access rules. With release: true, a bucket without a write rule denies all uploads:
storage: {
buckets: {
photos: {
access: {
write(auth) { return auth !== null; },
},
},
},
},
Also verify the user is authenticated before uploading.
Plugins
Plugin table not found -- "table not found" or empty results
Symptom: You installed a plugin but queries to its tables return errors.
Fix: Plugin tables are namespaced. When accessing them via the Admin SDK or Admin Dashboard, use the plugin prefix:
// Wrong
admin.db('app').table('subscriptions');
// Correct -- include the plugin namespace prefix
admin.db('app').table('stripe_subscriptions');
Check the plugin's documentation for its exact table names.
Deployment
npx edgebase deploy fails with Cloudflare API errors
Symptom: Deploy fails with Authentication error or 10000: Unknown error.
Fix:
- Make sure you are logged into Wrangler:
npx wrangler login - Verify your Cloudflare account has Workers and D1 access.
- If using a CI pipeline, set
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDas environment variables. - Check your
wrangler.tomlfor syntax errors. Redeploy after fixing.
Deploy succeeds but the app returns 500 errors
Symptom: npx edgebase deploy completes, but requests to the deployed URL return 500.
Fix:
- Check if your production environment variables are set. The deploy does not copy
.env.developmentto production. Set secrets via the Cloudflare dashboard or Wrangler:npx wrangler secret put JWT_SECRET - If using OAuth, set provider credentials as secrets as well.
- Check Cloudflare Worker logs for the actual error:
npx wrangler tail
CORS
CORS errors in the browser console
Symptom: Browser console shows Access-Control-Allow-Origin errors.
Fix:
- Add your frontend origin to the
cors.originlist:cors: {
origin: ['https://my-app.com', 'http://localhost:3000'],
credentials: true,
}, - If your frontend uses cookies or auth headers,
credentials: trueis required. Whencredentialsistrue,origincannot be'*'-- you must list specific origins. - After changing the config, restart the dev server or redeploy.
- Make sure you are not sending requests to
http://from anhttps://frontend (mixed content).
Rate Limiting
429 Too Many Requests during development
Symptom: Rapid development or testing triggers 429 responses.
Fix: Increase the rate limits in your config for the affected group:
rateLimiting: {
db: { requests: 500, window: '60s' },
auth: { requests: 100, window: '60s' },
authSignin: { requests: 50, window: '1m' },
},
The defaults are intentionally conservative. Increase them during development, but keep them reasonable for production to protect against abuse.
Rate limiting hits too early for specific users
Symptom: Legitimate users get rate limited while traffic is low.
Fix: The default rate limiting keys on client IP. If your users are behind a shared IP (corporate VPN, mobile carrier NAT), many users share the same rate limit bucket. Consider increasing the limits or adjusting the window.
WebSocket / Real-Time
WebSocket connection drops and does not reconnect
Symptom: Real-time subscriptions stop working after a period of inactivity.
Fix: The SDK auto-reconnects with exponential backoff by default. If you are seeing persistent disconnects:
- Check your network -- VPNs and corporate firewalls sometimes kill idle WebSocket connections.
- On Cloudflare, idle connections are hibernated (not terminated). On wake-up, the SDK automatically re-registers subscriptions. No action needed.
- If using a reverse proxy in front of EdgeBase, make sure it supports WebSocket upgrades and has a reasonable idle timeout.
Subscription revoked after token refresh
Symptom: onSnapshot stops receiving events and you see a SUBSCRIPTION_REVOKED error.
Why: When a client's auth token is refreshed, the server re-evaluates all active subscriptions against the read access rule. If the user's permissions changed (e.g., role was removed), subscriptions that are no longer authorized are revoked.
Fix: Listen for revocation errors and re-subscribe or redirect the user:
client.databaseLive.onError((error) => {
if (error.code === 'SUBSCRIPTION_REVOKED') {
// Handle the revocation (e.g., redirect to login, show a message)
}
});
Still Stuck?
- Check the dev server terminal for detailed error messages.
- Open the Admin Dashboard at
http://localhost:5180/adminto inspect tables, users, and logs. - Review the Configuration page for config structure.
- See the FAQ for additional common questions.