
FeedLog
Own your feedback board, votes and public roadmap on Cloudflare Workers
FeedLog is a self-hosted Canny/Featurebase alternative built on Cloudflare (Hyperdrive, Queues, R2, Workers). Paid services required. Inspect the source and license in the linked repository.
Source & license
Upstream license: MIT
License TL;DR
You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.
Explain MIT in plain English →Summary of the main license. Separate packages and assets can have different terms.
Inspect repository ↗Read this project’s actual license ↗Repository owner
See the upstream repository for the original creator and contributors.
Maintain this project? Maintainer verification →Cloudflare hosting
Paid services required
Plan for Workers Paid at US$5 per account/month plus usage, and external PostgreSQL 17+ with vector through Hyperdrive. Neon Free is a limited database option (100 CU-hours/month, 0.5 GB storage, 5 GB transfer per project). R2 uploads, optional AI/email and maintenance need separate budgets; no zero-cost or measured-bill claim.
Hosting requirements
- The Workers Paid minimum is account-wide, not a separate $5 subscription for every app; usage beyond included allowances can add charges.
- Neon Free provides 100 CU-hours monthly, 0.5 GB storage and 5 GB network transfer per project; select PostgreSQL 17+ and enable vector before the first migration.
- A continuously busy database or retained feedback, attachments and embeddings can exhaust database allowances; check actual provider usage.
- Hyperdrive connection pooling and caching are included in Workers Paid; the external database is a separate service.
- Configure the feedlog-inbox Queue producer and consumer; writes, reads, deletes and retries consume operations.
- R2 BLOB uploads are optional at the application level. Standard R2 included storage is 10 GB-month per month, with request allowances and metered usage beyond limits.
- OpenAI-powered features require provider credentials and models. Resend email requires separate provider setup; neither is included in the Workers subscription.
- Without OAuth, email/password sign-in defaults on. Without Resend, email verification defaults off and password-reset email is unavailable; choose authentication settings deliberately.
- A real Hyperdrive ID, public auth URL, auth secret and admin-bootstrap emails are required; shipped values are placeholders.
- This is a source-based planning budget, not a fresh deployment, performance benchmark, security audit or guaranteed bill.
Review findings & limitations
- Source and public-demo review only; no fresh installation, migration, authenticated write, AI benchmark or measured bill.
- Mandatory external PostgreSQL 17+ with vector; database quotas and availability are separate from Cloudflare.
- Workers Paid is the conservative baseline; the application has not been benchmarked against Workers Free CPU limits.
- Optional AI and email require configured providers; no provider-free feature parity or competitor savings claimed.
Sources checked 01/10/2026
Repository snapshot: 697d3cc. Hosting eligibility reflects the deployment documentation and listed assumptions.
- canny ↗
**Open-source feedback & roadmap tool. Self-host on Cloudflare Workers, Vercel, or Docker.**
- featurebase ↗
**Open-source feedback & roadmap tool. Self-host on Cloudflare Workers, Vercel, or Docker.**
- workers ↗
name = "feedlog" compatibility_date = "2026-03-01" compatibility_flags = ["nodejs_compat"]
- hyperdrive ↗
[[hyperdrive]] binding = "POSTGRES" id = "REPLACE_WITH_YOUR_HYPERDRIVE_ID"
- r2 ↗
[[r2_buckets]] binding = "BLOB" bucket_name = "feedlog"
- queues ↗
[[queues.producers]] binding = "INBOX_QUEUE" queue = "feedlog-inbox"
- paid ↗
The Workers Paid plan includes Workers, Pages Functions, Workers KV, Hyperdrive, and Durable Objects usage for a minimum charge of $5 USD per month for an account.
- paid ↗
100 CU-hrs monthly per project
- paid ↗
0.5 GB of storage per project
- paid ↗
5 GB per project included
- paid ↗
| Extension | PG14 | PG15 | PG16 | PG17 | PG18 | Notes |
- paid ↗
| [pgvector](https://neon.com/docs/extensions/pgvector) | 0.8.0 | 0.8.0 | 0.8.0 | 0.8.0 | 0.8.6 | Install with `CREATE EXTENSION vector;` |
- paid ↗
Hyperdrive is included in both the Free and Paid [Workers plans](https://developers.cloudflare.com/workers/platform/pricing/).
- paid ↗
| Standard operations | 10,000 operations/day included | 1,000,000 operations/month included + $0.40/million operations |
- paid ↗
| Storage | 10 GB-month / month |
- paid ↗
Skipping this disables file uploads but the app still works.
- MIT ↗
MIT License Copyright (c) 2026 LinkCraft Studio Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- architecture ↗
"build:cf": "NITRO_PRESET=cloudflare-module NODE_OPTIONS=\"$NODE_OPTIONS --max-old-space-size=8192\" pnpm build",
- architecture ↗
"deploy:cf": "wrangler deploy --cwd .output",
- architecture ↗
const binding = (globalThis as any).__env__?.POSTGRES || (globalThis as any).POSTGRES
- architecture ↗
const { session, orgId } = await requireAuthInOrg(event)
- architecture ↗
const created = await createPostRecord({
- architecture ↗
export default defineEventHandler
- architecture ↗
nitroApp.hooks.hook('cloudflare:queue', async ({ batch, env }) => {
- architecture ↗
message.retry({ delaySeconds: 60 })
- architecture ↗
meta: { name: 'inbox:schedule', description: 'Recover missing delayed Inbox deliveries' }
- architecture ↗
crons = ["0 3 * * *"]
- architecture ↗
const requireEmailVerification = parseBool(env.AUTH_EMAIL_VERIFY) ?? hasEmailProvider
- architecture ↗
Delivery is best-effort: listeners run outside the response lifecycle, a
- architecture ↗
await generatePostEmbedding(row.id, domainEvent.orgId, row.title, row.content, row.contentHash)
Documented public demo screenshot · linkcraftstudio/feedlog repository contributors ↗. Depicts the upstream project. We have not deployed and tested a fresh installation here.
What it can replace
Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.
Editorial workflow alternative: Feedback board and public roadmap. No complete feature parity, migration compatibility or price saving asserted.
See supporting source ↗Editorial workflow alternative: Collecting product feedback and roadmap requests. No complete feature parity, migration compatibility or price saving asserted.
See supporting source ↗How it works
The shape of FeedLog on Cloudflare, and how it stacks up against the rented tools it replaces.
Architecture
Diagram based on the linked repository documentation. See the sources and hosting assumptions above.
View upstream source ↗Configuration and workflow sources
Reviewed commit 697d3cc7becc. This configuration evidence comes from reading source files. Execution checks, when available, appear in the project's runtime review.
Deployment configuration · 1 files
Cloudflare Workers · compatibility 2026-03-01
feedlog · default
Cron triggers (UTC): 0 3 * * *
BLOB→ R2POSTGRES→ HyperdriveINBOX_QUEUE→ Queues (producer) · queue feedlog-inboxfeedlog-inbox→ Queues (consumer) · queue feedlog-inbox
Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.
Runtime source · handlers, binding usage and workflow steps
Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.
- L12 · isBucketEndpoint calls (conditional paths may differ): hostname.startsWith
Environment references: process.env.S3_ACCESS_KEY_ID · process.env.S3_SECRET_ACCESS_KEY · process.env.S3_BUCKET · process.env.S3_ENDPOINT · process.env.S3_REGION
- L222 · safeRedirectTarget calls (conditional paths may differ): getQuery, candidate.startsWith
- L19 · shouldBypass calls (conditional paths may differ): BYPASS_PREFIXES.some, prefix.endsWith, path.startsWith
Environment references: process.env.DATABASE_URL
- L32 · readJournalEntries calls (conditional paths may differ): fs.readFileSync, JSON.parse, Array.isArray
- L42 · probeDatabaseState calls (conditional paths may differ): readJournalEntries, postgres, drizzle, db.transaction, tx.execute, String, client.end
- L88 · resolveState calls (conditional paths may differ): probeDatabaseState, resolveConnectionString
- L29 · collectModules calls (conditional paths may differ): seen.has, seen.add, collectModules
- L37 · collectFiles calls (conditional paths may differ): files.push
- L47 · collectHints calls (conditional paths may differ): find, Object.keys, ROUTE_MODULE_RE.test, Object.entries, collectModules, collectFiles, I18N_CONFIG_RE.test, shipped.has, shipped.add, hints.push, file.endsWith
- L23 · embedLocale calls (conditional paths may differ): filter, split, path.replace, segments.shift, segments.join, locales.includes
Environment references: process.env.NITRO_PRESET · process.env.VERCEL · process.env.NUXT_PUBLIC_UPLOAD_PREFIX
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, useDB, where, set, db.update, and, eq, returning, db.delete, createError, setResponseStatus
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, Promise.all, body.ids.map, where, set, db.update, and, eq, setResponseStatus
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, readValidatedBody, Object.keys, createError, useDB, returning, where, set, db.update, and, eq
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, where, from, db.select, eq, returning, values, db.insert, setResponseStatus
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, useDB, where, db.delete, eq, returning, and, createError, setResponseStatus
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError
- L10 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, createError, replace, useDB, where, from, db.select, and, eq, inArray, feedback.push, buildSystemPrompt, buildUserPrompt, client.chat.completions.create, parseAiResponse, parsed.suggested_tags.filter, validTags.has, parsed.title.slice
Environment references: process.env.OPENAI_API_KEY · process.env.OPENAI_BASE_URL · process.env.OPENAI_TEXT_MODEL
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, readValidatedBody, useDB, limit, where, from, db.select, and, eq, createError, returning, set, db.update
- L8 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, getQuery, Math.max, Number, Math.min, useDB, eq, conditions.push, or, and, where, from, db.select, offset, limit, orderBy, leftJoin, desc, rows.map
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError, returning, set, db.update
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, generateChangelogSlug, returning, values, db.insert, setResponseStatus
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readBody, useDB, where, from, db.select, eq, computeContentHash, stripMarkdown, onConflictDoUpdate, values, db.insert, set, db.update, isEmbeddingEnabled, limit, generatePostEmbedding, console.error
- L1 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, findHelpArticleDetail, getRouterParam, createError
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, readValidatedBody, useDB, and, eq, limit, where, from, db.select, createError, nextHelpArticlePosition, buildHelpTsv, generateHelpSlug, title.trim, content.trim, Object.keys, set, db.update, findHelpArticleDetail
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, useDB, returning, where, db.delete, and, eq, createError, setResponseStatus
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, returning, where, set, update, useDB, and, eq, inArray, ne
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, returning, where, set, db.update, and, eq, inArray, ne
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, returning, where, set, db.update, and, eq, inArray
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, limit, where, from, db.select, and, eq, createError, nextHelpArticlePosition, withHelpShortId, returning, values, db.insert, generateHelpSlug, buildHelpTsv, setResponseStatus, findHelpArticleDetail
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, getQuery, Math.max, Number, Math.min, useDB, and, eq, ilike, where, from, db.select, offset, limit, orderBy, innerJoin, asc, rows.map
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, Promise.all, body.ids.map, where, set, db.update, and, eq, setResponseStatus
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, useDB, db.transaction, returning, where, tx.delete, and, eq, createError, setResponseStatus
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, readValidatedBody, useDB, and, eq, returning, where, set, db.update, createError, orderBy, from, db.select, asc
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, getQuery, useDB, orderBy, where, from, db.select, eq, asc, and, inArray, collections.map, articles.filter, own.map
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, where, from, db.select, eq, returning, values, db.insert, setResponseStatus
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, useDB, where, from, db.select, eq
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, Promise.all, body.ids.map, where, set, db.update, and, eq, setResponseStatus
- L7 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError, db.delete, returning, setResponseStatus
- L14 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, getRouterParam, useDB, limit, where, innerJoin, from, db.select, eq, and, createError, row.expiresAt.getTime, Date.now, buildInviteLink, sendEmail, renderInvitationEmail
Environment references: process.env.RESEND_API_KEY
- L8 · Nuxt auto-imported H3 HTTP handler exported · calls getRouterParam, readValidatedBody, requireOrgPermission, useDB, limit, where, from, db.select, and, eq, createError, generateExcerpt, Object.keys, computeContentHash, returning, set, db.update, stripMarkdown, onConflictDoUpdate, values, db.insert, event.waitUntil, generatePostEmbedding
- L15 · Nuxt auto-imported H3 HTTP handler exported · calls getRouterParam, readValidatedBody, requireOrgPermission, useDB, limit, where, from, db.select, and, eq, createError, catch, markPostUnreadForAuthor, console.error, resolveOrgBranding, resolvePostThreadRecipients, event.waitUntil, deliverToRecipients, getRequestURL
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, useDB, orderBy, groupBy, where, leftJoin, from, db.select, eq, desc
- L11 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, getQuery, trim, parsePostFilter, useDB, rows.map, embedQueryOrNull, searchPostsBySemantic, flat, and, postFilterConditions, limit, orderBy, where, leftJoin, innerJoin, from, db.select, eq, desc
- L11 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, createError, useDB, limit, where, from, db.select, and, eq, db.transaction, set, tx.update, tx.execute, values, tx.insert
- L9 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, getQuery, Math.max, Number, Math.min, useDB, postFilterConditions, parsePostFilter, requireOrgPermission, safeParse, z.uuid, createError, limit, where, from, db.select, and, eq, filters.push, offset, orderBy, leftJoin, orderFn, rows.map
- L10 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, readValidatedBody, useDB, limit, where, from, db.select, and, eq, createError, db.transaction, set, tx.update, tx.execute, returning, tx.delete
- L8 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, limit, where, from, select, useDB, eq, resolveWidgetSettings, getRequestURL
- L7 · Nuxt auto-imported H3 HTTP handler exported
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls toWebRequest, guardSsoAuthRequest, auth.handler
- L31 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, isGuestSession, createError, getRequestIP, checkRateLimit, readValidatedBody, auth.api.getSession, claimGuestContent
- L10 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgPermission, updateWidgetSettingsSchema.safeParse, catch, readBody, createError, customRules.map, uuidv7, transaction, useDB, onConflictDoNothing, values, tx.insert, for, where, from, tx.select, eq, checkEnabledRuleCap, Object.keys, returning, set, tx.update, resolveWidgetSettings, getRequestURL
- L3 · Nuxt auto-imported H3 HTTP handler exported · calls auth.api.getSession, getHeaders, createError, readBody, auth.api.listUserAccounts, auth.api.setPassword, sendEmail, renderPasswordSetEmail
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, getQuery, reactionSchema.safeParse, createError, useDB, limit, where, from, db.select, and, eq, isNotNull, db.delete, groupBy, userRows.map
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls useDB, Promise.all, orderBy, groupBy, where, leftJoin, from, db.select, and, eq, asc
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, assertGuestMay, getRouterParam, readValidatedBody, useDB, limit, where, from, db.select, and, eq, isNotNull, createError, onConflictDoNothing, values, db.insert, getReactionState
- L39 · getReactionState calls (conditional paths may differ): groupBy, where, from, db.select, eq, and, userRows.map
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, getRouterParam, useDB, limit, where, from, db.select, and, eq, isNotNull, createError, groupBy, rows.map, toISOString
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, getQuery, Math.min, Number, useDB, eq, isNotNull, decodeCursor, conditions.push, and, limit, orderBy, where, from, db.select, desc, rows.slice, items.map, groupBy, inArray, push, toISOString, encodeCursor
- L107 · encodeCursor calls (conditional paths may differ): toString, Buffer.from, JSON.stringify
- L111 · decodeCursor calls (conditional paths may differ): JSON.parse, toString, Buffer.from
- L11 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, useDB, limit, where, leftJoin, from, db.select, eq, and, createError, readValidatedBody, returning, set, db.update
- L7 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, useDB, limit, where, leftJoin, from, db.select, eq, and, createError, requireOrgPermission, db.delete, set, db.update, setResponseStatus
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, useDB, limit, where, leftJoin, from, db.select, eq, and, createError, returning, db.delete, set, db.update
- L7 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgOwner, getRouterParam, createError, useDB, returning, where, db.delete, and, eq
- L7 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgOwner, getRouterParam, createError, readBody, slice, body.label.trim, Object.keys, useDB, returning, where, set, db.update, and, eq, ssoSecretToView
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, assertGuestMay, getRouterParam, useDB, limit, where, leftJoin, from, db.select, eq, and, createError, values, db.insert, returning, set, db.update
- L7 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgOwner, useDB, orderBy, where, from, db.select, eq, desc, rows.map
- L7 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgOwner, catch, readBody, useDB, db.$count, eq, createError, body.label.trim, slice, returning, values, db.insert, generateSsoSecret, setResponseStatus, ssoSecretToView
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls getRouterParam, createError, blobStorage.serve
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireHelpCenterOrg, split, getRouterParam, useDB, limit, where, innerJoin, from, db.select, eq, and, createError, orderBy, ne, asc
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireHelpCenterOrg, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError, orderBy, asc
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireHelpCenterOrg, useDB, orderBy, groupBy, where, innerJoin, from, db.select, and, eq, asc
- L8 · H3 HTTP handler exported · calls includes, createError, Object.keys, getQuery, readBody, Array.isArray, setResponseHeader, checkRateLimit, schedulerContext, withScheduledRuntime, scheduleInbox, delayTransport, restoreNodeTimers
- L7 · Nuxt auto-imported H3 HTTP handler exported · calls requireHelpCenterOrg, splitHelpQuery, trim, getQuery, buildHelpTsQuery, getRequestIP, checkRateLimit, createError, useDB, limit, orderBy, where, innerJoin, from, db.select, eq, and, asc, rows.map, buildHelpExcerpt, stripMarkdown, helpHighlightRanges
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, findOwnMemberPreference, useDB, createError, setResponseHeader, resolveMemberNotifications
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireOrgMember, updateMemberPreferenceSchema.safeParse, catch, readBody, createError, useDB, findOwnMemberPreference, saveMemberNotifications, setResponseHeader
- L23 · Nuxt auto-imported H3 HTTP handler exported · calls getRouterParam, createError, limit, where, innerJoin, from, db.select, eq, row.expiresAt.toISOString
- L16 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, readValidatedBody, useDB, limit, where, from, db.select, and, eq, createError, assertGuestMay, returning, values, db.insert, set, db.update, event.waitUntil, catch, emitCommentNotifications, isActorAdmin, getRequestURL, console.error, emitAdminNotification, setResponseStatus
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, getRouterParam, getQuery, Math.min, Number, useDB, limit, where, from, db.select, and, eq, createError, resolveAdminAuthorIds, decodeCursor, conditions.push, orderBy, leftJoin, asc, rows.slice, attachLikeStatus, encodeCursor, toISOString, isNull, desc, topRows.slice, topItems.map, isNotNull, inArray, childrenMap.has, childrenMap.set, childrenMap.get, arr.push, children.splice, topItems.filter, filter, mergedPostItems.map, Promise.all, mergedPostInfoMap.set, groupedSubComments.has, groupedSubComments.set, push, groupedSubComments.get, level2MergedPostIds.push, level2PostInfoMap.set, allSubComments.slice, subData.map, subItems.find, level2PostInfoMap.get, mergedPostCommentsMap.set, topData.map, topItems.find, mergedPostInfoMap.get, mergedPostCommentsMap.get
- L358 · resolveAdminAuthorIds calls (conditional paths may differ): where, from, select, useDB, and, eq, inArray, rows.map
- L367 · formatComment calls (conditional paths may differ): adminIds.has
- L384 · attachLikeStatus calls (conditional paths may differ): items.map, formatComment, where, from, db.select, and, eq, inArray, likes.map, likedSet.has
- L405 · encodeCursor calls (conditional paths may differ): toString, Buffer.from, JSON.stringify
- L409 · decodeCursor calls (conditional paths may differ): JSON.parse, toString, Buffer.from
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, getRouterParam, useDB, limit, where, leftJoin, from, db.select, eq, and, createError, db.execute, getOrgMemberRole, setResponseHeader
- L13 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError, requireOrgPermission, readValidatedBody, generateExcerpt, Object.keys, computeContentHash, returning, set, db.update, stripMarkdown, onConflictDoUpdate, values, db.insert, event.waitUntil, generatePostEmbedding
- L2 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, getRouterParam, getQuery, Math.min, Math.max, Number, searchSimilarByPostId
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError, db.delete
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError, returning, db.delete, set, db.update
- L8 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError, isActorAdmin, onConflictDoNothing, values, db.insert
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, assertGuestMay, getRouterParam, useDB, limit, where, from, db.select, and, eq, createError, values, db.insert, returning, set, db.update, isActorAdmin, onConflictDoNothing
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, getQuery, Math.min, Number, useDB, eq, isNull, conditions.push, decodeCursor, and, limit, orderBy, where, leftJoin, from, db.select, orderFn, result.map, inArray, votes.map, result.slice, items.map, votedPostIds.has, encodeCursor
- L110 · encodeCursor calls (conditional paths may differ): toString, Buffer.from, JSON.stringify
- L114 · decodeCursor calls (conditional paths may differ): JSON.parse, toString, Buffer.from
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, assertGuestMay, readValidatedBody, createPostRecord, isActorAdmin, publishDomainEvent, createDomainEvent, fetchPostAuthor, setResponseStatus
- L15 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, getQuery, trim, useDB, getRequestIP, checkRateLimit, embedQueryOrNull, searchPostsBySemantic, limit, orderBy, where, leftJoin, innerJoin, from, db.select, eq, and, isNull, rows.map, inArray, votes.map, votedPostIds.has
- L16 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, readValidatedBody, searchSimilarByText
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls requireLocalSession, createError, Number, getHeader, tooLarge, getRequestWebStream, reader.read, reader.cancel, chunks.push, reader.releaseLock, bytes.set, inspectAvatar, useRuntimeConfig, blobStorage.put, encodeURIComponent
Environment references: process.env.UPLOAD_PREFIX · process.env.NUXT_PUBLIC_UPLOAD_PREFIX
- L16 · Nuxt auto-imported H3 HTTP handler exported · calls getUserSession, getQuery, useDB, eq, ROADMAP_STATUSES.map, isNull, conditions.push, limit, orderBy, where, leftJoin, from, db.select, and, desc, inArray, countConditions.push, Promise.all, groupBy, counts.map, results.flat, allRows.map, votes.map, rows.slice, items.map, votedPostIds.has, encodeCursor, countMap.get
- L128 · encodeCursor calls (conditional paths may differ): toString, Buffer.from, JSON.stringify
- L12 · Nuxt auto-imported H3 HTTP handler exported · calls getQuery, validateReturnTo, createError, useDB, where, from, db.select, and, eq, verifySsoJwt, secrets.map, findOrCreateSsoUser, ctx.internalAdapter.createSession, signSessionCookieValue, rawSameSite.toLowerCase, setCookie, dataRawSameSite.toLowerCase, Object.keys, parseCookies, name.startsWith, deleteCookie, sendRedirect, console.warn, encodeURIComponent
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuth, useRuntimeConfig, blobStorage.handleUpload, createError
Environment references: process.env.UPLOAD_PREFIX · process.env.NUXT_PUBLIC_UPLOAD_PREFIX
- L14 · Nuxt auto-imported H3 HTTP handler exported · calls createError, getRequestIP, checkRateLimit, catch, readBody, body.jwt.trim, useDB, limit, where, from, db.select, eq, and, verifySsoJwt, secrets.map, findOrCreateSsoUser, ctx.internalAdapter.createSession, toISOString
- L11 · Nuxt auto-imported H3 HTTP handler exported · calls getOrgInfo, limit, where, from, select, useDB, eq, resolveBranding, setResponseHeader, resolveGuestAccess, pickBrandForegroundHex
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls withAgentRuntime, streamAgentReply, widgetActor
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls withAgentRuntime, readConversationItems, widgetActor
- L16 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, isConversationId, createError, useDB, limit, where, from, db.select, and, ownedConversation, withinRetention, orderBy, leftJoin, eq, asc, rows.map
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls withAgentRuntime, widgetActor, safeParse, z.uuid, getRouterParam, agentError, strict, z.object, safe, positive, int, z.number, catch, readBody, markCustomerRead, agentRuntime, countWidgetBadge
- L19 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, orderBy, where, from, select, useDB, and, eq, withinRetention, gt, desc, rows.map, handling
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getRouterParam, createError, useDB, limit, where, from, db.select, and, eq, db.delete, countWidgetBadge
- L19 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, getQuery, Math.min, Number, eq, decodeCursor, conditions.push, limit, orderBy, where, leftJoin, from, select, useDB, and, desc, map, rows.slice, encodeCursor, count
- L81 · encodeCursor calls (conditional paths may differ): toString, Buffer.from, JSON.stringify
- L91 · decodeCursor calls (conditional paths may differ): JSON.parse, toString, Buffer.from, Number.isNaN, Date.parse, UUID.test
- L1 · Nuxt auto-imported H3 HTTP handler exported · calls createError
- L5 · Nuxt auto-imported H3 HTTP handler exported · calls requireAuthInOrg, countWidgetBadge
- L12 · getDb calls (conditional paths may differ): drizzle, postgres, logger.info
Environment references: process.env.DATABASE_URL
- L10 · historyProcessor calls (conditional paths may differ): messages.filter, customerItemIds.has, completedRunIds.has, messages.map
- L27 · customerMemoryMessage calls (conditional paths may differ): parts.push, useRuntimeConfig, part.storage_key.startsWith, includes, part.storage_key.split, blobStorage.get, blob.type.startsWith, blob.arrayBuffer, JSON.stringify, toString, Buffer.from, padStart, String
- L46 · prepareAgent calls (conditional paths may differ): agentRuntime, memory.getThreadById, memory.createThread, pool.query, Promise.all, map, customerItems.filter, customerMemoryMessage, memory.saveMessages, historyProcessor, customerItems.map, completed.map, loadAgentPromptContext, JSON.stringify, feedlogTools, renderAgentSystemPrompt
Environment references: process.env.OPENAI_API_KEY · process.env.OPENAI_TEXT_MODEL · process.env.OPENAI_BASE_URL
- L9 · startRun calls (conditional paths may differ): catch, transaction, client.query, lockConversation, Number, agentError, isDeepStrictEqual, handling, recoverLocked, eventItem, appendItem, syncConversationTask, uuidv7, syncRunTask
- L71 · failRun calls (conditional paths may differ): transaction, client.query, syncRunTask
- L32 · searchHelpArticles calls (conditional paths may differ): helpArticleSearchInput.parse, pool.query, resolvePortalModules, query.replace, normalized.toLowerCase, seen.has, seen.add, queries.push, Promise.all, queries.map, groups.entries, articles.get, existing.matched_queries.push, buildHelpExcerpt, stripMarkdown, splitHelpQuery, articles.set, articles.values
- L17 · streamAgentReply calls (conditional paths may differ): assertGuestMay, safeParse, z.uuid, getRouterParam, agentError, chatInput.safeParse, catch, readBody, agentRuntime, customerMemoryMessage, checkRateLimit, startRun, requestSchedule, publicItem, publicRun, Number, handling, uuidv7, createEventStream, stream.push
- L7 · readConversationItems calls (conditional paths may differ): safeParse, z.uuid, getRouterParam, agentError, pageInput.safeParse, getQuery, agentRuntime, runtime.pool.query, transaction, client.query, rows.slice, items.reverse, map, items.filter, Number, handling, filter, items.map, runs.map, items.at
- L30 · renderAgentSystemPrompt calls (conditional paths may differ): context.supportRules.filter, rule.scenario.trim, join, filter, context.boards.map, board.description.trim, JSON.stringify, context.supportEmail.trim, rules.map, context.articles.map, sections.map, section.filter
- L133 · renderAgentPromptTemplate calls (conditional paths may differ): renderAgentSystemPrompt
- L24 · feedlogTools calls (conditional paths may differ): agentRuntime, digest, update, createHash, cards.findIndex, cards.push, signal.throwIfAborted, lockConversation, client.query, memory.getThreadById, effects, uuidv7, memory.updateThread, map, filter, Object.values, has, createdIds, ids.has, getRequestURL
- L35 · active calls (conditional paths may differ): signal.throwIfAborted, lockConversation, client.query
- L42 · effects calls (conditional paths may differ): memory.getThreadById
- L46 · reserve calls (conditional paths may differ): effects, uuidv7, memory.updateThread
- L55 · createdIds calls (conditional paths may differ): effects, map, filter, Object.values
- L59 · owned calls (conditional paths may differ): has, createdIds
- L62 · feedbackEditable calls (conditional paths may differ): ids.has
- L65 · feedbackDetails calls (conditional paths may differ): getRequestURL, feedbackEditable
- L70 · postRow calls (conditional paths may differ): client.query
- L84 · tool calls (conditional paths may differ): createTool, signal.throwIfAborted, uuidv7, Date.now, emit, execute
- L102 · write calls (conditional paths may differ): transaction, active, fn
- L110 · attachImages calls (conditional paths may differ): pool.query, rows.map, images.some, allowed.has, join, map
- L268 · resultParts calls (conditional paths may differ): pool.query, resolvePortalModules, visible.map, Promise.all, map, cards.flatMap, postRow, cards.map, card, posts.get, refreshed.flatMap, part.articles.some, ids.has, part.articles.filter, cards.some
- L289 · feedbackResults calls (conditional paths may differ): resultParts, console.warn, parts.filter
- L4 · collectConversationReply calls (conditional paths may differ): stream.pipeThrough, write, controller.enqueue, readUIMessageStream
- L12 · initialize calls (conditional paths may differ): memory.settled, Promise.all, pool.end, memoryPool.end
- L27 · agentRuntime calls (conditional paths may differ): initialize, requestRuntime
- L40 · requestRuntime calls (conditional paths may differ): publishDomainEvent, createDomainEvent
- L44 · withAgentRuntime calls (conditional paths may differ): action, event.waitUntil, finally, Promise.resolve, runtime.close
- L54 · agentError calls (conditional paths may differ): createError
- L57 · widgetActor calls (conditional paths may differ): requireAuthInOrg, agentRuntime, pool.query, agentError
- L66 · transaction calls (conditional paths may differ): runtime.pool.connect, client.query, fn, takeInboxEvents, console.warn, client.release
- L80 · lockConversation calls (conditional paths may differ): client.query, agentError
- L86 · appendItem calls (conditional paths may differ): slice, stripMarkdown, join, input.content.parts.map, client.query, JSON.stringify
- L98 · publicItem calls (conditional paths may differ): parts.find, Number
Environment references: process.env.DATABASE_URL
- L15 · safeEmailTopic calls (conditional paths may differ): value.trim, Object.hasOwn
- L20 · sourcePageUrl calls (conditional paths may differ): pageContext.safeParse
- L28 · customerQuestionText calls (conditional paths may differ): slice, join, map, messages.filter, message.content.parts.filter
- L34 · summarizeEmailQuestion calls (conditional paths may differ): customerQuestionText, input.trim, join, Object.keys, agent.generate, AbortSignal.timeout, safeEmailTopic
Environment references: process.env.OPENAI_API_KEY · process.env.OPENAI_TEXT_MODEL · process.env.OPENAI_BASE_URL
- L3 · assertConversationImage calls (conditional paths may differ): key.startsWith, some, key.split, agentError
- L10 · sendInboxEmail calls (conditional paths may differ): runtime.pool.query, agentError, resolveEmailProvider, find, customerMessages.map, sourcePageUrl, summarizeEmailQuestion, reverse, join, parts.map, sendEmail, renderInboxReplyEmail, console.warn
- L60 · deliverReplyNow calls (conditional paths may differ): runtime.pool.query, deliverReplyTask
- L78 · deliverReplyTask calls (conditional paths may differ): runtime.pool.query, finish, BigInt, sendInboxEmail, setTimeout
- L99 · retryReplyEmail calls (conditional paths may differ): runtime.pool.query, agentError
- L18 · consumeBackground calls (conditional paths may differ): withScheduledRuntime, publications.push, publishBackgroundDomainEvent, createDomainEvent, consumeInbox, delayTransport, Promise.allSettled
- L28 · arm calls (conditional paths may differ): timers.get, clearTimeout, setTimeout, timers.delete, consumeBackground, console.warn, schedulerFailure, arm, Math.max, Math.min, timers.set
- L43 · stopInboxTimers calls (conditional paths may differ): timers.values, clearTimeout, timers.clear
- L48 · delayTransport calls (conditional paths may differ): queue.send, send, stopInboxTimers, arm, Math.max
- L69 · requestSchedule calls (conditional paths may differ): schedulerContext, localFetch, useNitroApp, Object.assign, response.json, console.warn, schedulerFailure
- L85 · restoreNodeTimers calls (conditional paths may differ): withScheduledRuntime, runtime.pool.query, arm, toISOString, Date.now, requestSchedule
Environment references: context.cloudflare.env.INBOX_QUEUE · process.env.VERCEL
- L7 · listConversations calls (conditional paths may differ): digest, update, createHash, JSON.stringify, values.push, conditions.push, bind, input.q.replace, parse, z.tuple, z.iso.datetime, z.uuid, z.literal, JSON.parse, toString, Buffer.from, agentError, runtime.pool.query, conditions.join, rows.slice
- L58 · readInbox calls (conditional paths may differ): transaction, client.query, agentError, rows.slice, items.reverse, Object.fromEntries, emailTasks.map, conversationResult, items.map, Number, items.at
- L3 · markCustomerRead calls (conditional paths may differ): runtime.pool.query
- L20 · schedulerFailure calls (conditional paths may differ): test, includes
- L27 · wakeAt calls (conditional paths may differ): Math.min, Math.ceil
- L33 · isQuotaExhausted calls (conditional paths may differ): String, test
- L39 · pending calls (conditional paths may differ): client.query
- L48 · notifyQuota calls (conditional paths may differ): resolveEmailProvider, client.query, sendEmail, console.warn
- L65 · publish calls (conditional paths may differ): client.query, toISOString, transport.send, Math.max, Math.min, Math.ceil, Date.now, expectAt.toISOString, isQuotaExhausted, console.warn, schedulerFailure, none
- L82 · locked calls (conditional paths may differ): runtime.pool.connect, client.query, fn, client.release
- L96 · scheduleInbox calls (conditional paths may differ): locked, none, pending, client.query, wakeAt, uuidv7, publish, notifyQuota
- L129 · consumeInbox calls (conditional paths may differ): test, locked, client.query, toISOString, includes, Date.now, inboxTick, scheduleClient.query, none, pending, publish, wakeAt, notifyQuota
- L158 · withScheduledRuntime calls (conditional paths may differ): action, pool.end
- L167 · runScheduledInbox calls (conditional paths may differ): withScheduledRuntime
Environment references: context.cloudflare.env.POSTGRES · process.env.DATABASE_URL
- L5 · inboxTick calls (conditional paths may differ): Date.now, runtime.pool.query, rows.slice, deliverReplyTask, transaction, client.query, includes, recoverLocked, eventItem, console.warn, console.info
- L26 · conversationResult calls (conditional paths may differ): Number
- L38 · lockInbox calls (conditional paths may differ): client.query, agentError
- L48 · cancelRun calls (conditional paths may differ): client.query, syncRunTask
- L56 · eventItem calls (conditional paths may differ): appendItem, uuidv7
- L70 · handoffLocked calls (conditional paths may differ): client.query, appendItem, cancelRun, includes, syncRunTask, syncConversationTask, console.warn, queueInboxEvent, eventItem
- L118 · handoff calls (conditional paths may differ): transaction, handoffLocked, lockInbox, save, console.warn
- L139 · recoverLocked calls (conditional paths may differ): client.query, handoffLocked, cancelRun
- L151 · canonical calls (conditional paths may differ): Array.isArray, join, value.map, map, sort, Object.entries, a.localeCompare, JSON.stringify, canonical
- L161 · staffCommand calls (conditional paths may differ): hash, transaction, lockInbox, client.query, agentError, receipt, conversationResult, Number, includes, queueInboxEvent, appendItem, getTime, replyCloseDelayMs, scheduleReplyEmail, due.getTime, due.toISOString, cancelRun, eventItem, syncConversationTask
- L264 · receipt calls (conditional paths may differ): Number
- L4 · inboxOpenActivity calls (conditional paths may differ): runtime.pool.query, rows.map, Number
- L4 · Nuxt auto-imported H3 HTTP handler exported · calls isWidgetApiPath, applyWidgetCors, handleWidgetPreflight
- L14 · Nuxt auto-imported H3 HTTP handler exported · calls event.path.split, path.startsWith, setResponseHeader
- L6 · Nuxt auto-imported H3 HTTP handler exported · calls resolveOrgContext
Environment references: process.env.RESEND_API_KEY · process.env.EMAIL_FROM
- L7 · H3 HTTP handler exported · calls getHeader, handleCallback, consumeBackground, handler, toWebRequest
Environment references: process.env.VERCEL
Environment references: process.env.DATABASE_URL · process.env.MIGRATIONS_DIR
- L1 · Nuxt auto-imported H3 HTTP handler exported · calls toISOString
- L9 · findOwnMemberPreference calls (conditional paths may differ): limit, where, leftJoin, innerJoin, from, db.select, eq, and
- L25 · saveMemberNotifications calls (conditional paths may differ): returning, onConflictDoUpdate, values, db.insert, JSON.stringify, resolveMemberNotifications
- L39 · findStaffFeedbackRecipients calls (conditional paths may differ): where, leftJoin, innerJoin, from, db.select, eq, and, inArray, ne, map, rows.filter, resolveMemberNotifications
- L13 · getUserSession calls (conditional paths may differ): auth.api.getSession
- L29 · requireAuth calls (conditional paths may differ): getUserSession, createError
- L41 · assertNotGuestSession calls (conditional paths may differ): createError
- L51 · requireLocalSession calls (conditional paths may differ): requireAuth, createError
- L74 · requireOrgPermission calls (conditional paths may differ): requireAuth, assertNotGuestSession, createError, orgApi.hasPermission
- L109 · requireAuthInOrg calls (conditional paths may differ): requireAuth, createError
- L120 · requireOrgMember calls (conditional paths may differ): requireAuth, assertNotGuestSession, createError
- L151 · requireOrgOwner calls (conditional paths may differ): requireOrgMember, createError
- L27 · parseBool calls (conditional paths may differ): includes, v.toLowerCase
- L103 · loadOrgList calls (conditional paths may differ): where, innerJoin, from, db.select, eq, and
- L127 · bootstrapAfterUserCreate calls (conditional paths may differ): systemAdminEmails.includes, onConflictDoNothing, values, db.insert, uuidv7
- L141 · nameGuestUser calls (conditional paths may differ): where, set, db.update, guestTag, eq
- L167 · buildAuthConfig calls (conditional paths may differ): sendEmail, renderInvitationEmail, rawSendInvite, logger.warn, defu, nameGuestUser, bootstrap, drizzleAdapter, bearer, admin, anonymous, uuidv7, organization, customSession, loadOrgList
- L285 · getAuthInstance calls (conditional paths may differ): betterAuth, buildAuthConfig
Environment references: env.SYSTEM_ADMIN_EMAILS · env.GOOGLE_CLIENT_ID · env.GOOGLE_CLIENT_SECRET · env.GITHUB_CLIENT_ID · env.GITHUB_CLIENT_SECRET · env.RESEND_API_KEY · env.AUTH_EMAIL_ENABLED · env.AUTH_EMAIL_VERIFY · env.BETTER_AUTH_URL
- L111 · buildSystemPrompt calls (conditional paths may differ): SYSTEM_PROMPT_TEMPLATE.replace
- L115 · buildUserPrompt calls (conditional paths may differ): parts.push, feedback.forEach, changeContent.trim, parts.join
- L155 · parseAiResponse calls (conditional paths may differ): JSON.parse, validateAiOutput, raw.match, raw.indexOf, raw.lastIndexOf, raw.slice, createError
- L185 · validateAiOutput calls (conditional paths may differ): Array.isArray, o.suggested_tags.every
- L9 · renderInboxReplyEmail calls (conditional paths may differ): join, filter, layout, escapeHtml, actionButton
- L16 · layout calls (conditional paths may differ): escapeHtml, signature
- L33 · fallbackLink calls (conditional paths may differ): url.slice
- L60 · renderVerificationEmail calls (conditional paths may differ): layout, escapeHtml, actionButton, fallbackLink, expiryNote
- L79 · renderInvitationEmail calls (conditional paths may differ): layout, escapeHtml, actionButton, fallbackLink, expiryNote
- L92 · renderResetPasswordEmail calls (conditional paths may differ): layout, escapeHtml, actionButton, fallbackLink, expiryNote
- L110 · renderPasswordSetEmail calls (conditional paths may differ): layout, escapeHtml
- L127 · escapeHtml calls (conditional paths may differ): replace, s.replace
- L141 · notificationShell calls (conditional paths may differ): escapeHtml
- L164 · statusChangeCard calls (conditional paths may differ): escapeHtml, pillButton
- L182 · personCard calls (conditional paths may differ): toUpperCase, charAt, test, escapeHtml, pillButton
- L201 · adminReplyCard calls (conditional paths may differ): personCard
- L209 · newFeedbackCard calls (conditional paths may differ): personCard
- L217 · userCommentCard calls (conditional paths may differ): personCard
- L227 · renderNotificationEmail calls (conditional paths may differ): statusChangeCard, adminReplyCard, newFeedbackCard, userCommentCard, normalizeBrandHex, replaceAll, notificationShell, card.html.replaceAll, escapeHtml, pickBrandForegroundHex
- L140 · toHookName calls (conditional paths may differ): name.replace
- L156 · createDomainEvent calls (conditional paths may differ): uuidv7, toISOString
- L169 · publishDomainEvent calls (conditional paths may differ): getRequestURL, event.waitUntil, dispatchDomainEvent, useNitroApp
- L183 · publishBackgroundDomainEvent calls (conditional paths may differ): dispatchDomainEvent, useNitroApp
- L194 · dispatchDomainEvent calls (conditional paths may differ): hooks.callHookWith, Promise.allSettled, handlers.map, handler, console.error, errorSummary, toHookName
- L229 · errorSummary calls (conditional paths may differ): String
- L236 · onDomainEvent calls (conditional paths may differ): nitroApp.hooks.hook, toHookName
- L248 · onAnyDomainEvent calls (conditional paths may differ): nitroApp.hooks.hook, toHookName
- L21 · registerEmailProvider calls (conditional paths may differ): providers.set
- L25 · resolveEmailProvider calls (conditional paths may differ): providers.has, providers.get
- L40 · sendEmail calls (conditional paths may differ): options.subject.replace, send, resolveEmailProvider
Environment references: process.env.EMAIL_PROVIDER
- L5 · embedQueryOrNull calls (conditional paths may differ): isEmbeddingEnabled, generateEmbedding
- L11 · generateEmbedding calls (conditional paths may differ): replace, fetch, JSON.stringify, response.text, response.json
- L39 · generatePostEmbedding calls (conditional paths may differ): isEmbeddingEnabled, stripMarkdown, generateEmbedding, useDB, limit, where, from, db.select, eq, set, db.update, and, onConflictDoNothing, values, db.insert, console.error
Environment references: process.env.OPENAI_API_KEY · process.env.OPENAI_BASE_URL
- L34 · claimGuestContent calls (conditional paths may differ): transaction, useDB, alias, returning, where, tx.delete, and, eq, exists, from, tx.select, set, tx.update, inArray, droppedVotes.map, droppedLikes.map
- L5 · requireHelpCenterOrg calls (conditional paths may differ): getOrgInfo, resolvePortalModules, createError
- L3 · computeContentHash calls (conditional paths may differ): digest, update, createHash
- L18 · splitHelpQuery calls (conditional paths may differ): slice, filter, split, q.trim, tokens.push, CJK_CHAR.test, flush, tokens.filter, test
- L44 · buildHelpTsQuery calls (conditional paths may differ): tokens.map, join, split, cjkBigrams, parts.reduce
- L55 · escapeRegExp calls (conditional paths may differ): text.replace
- L59 · mergeRanges calls (conditional paths may differ): ranges.sort, Math.max, out.push
- L72 · helpHighlightRanges calls (conditional paths may differ): token.text.replace, escapeRegExp, text.matchAll, ranges.push, mergeRanges
- L97 · truncateToWidth calls (conditional paths may differ): charWidth, text.slice
- L106 · buildHelpExcerpt calls (conditional paths may differ): helpHighlightRanges, truncateToWidth, trim, charWidth, body.slice, map, hits.filter
- L23 · buildInviteLink calls (conditional paths may differ): _builder
- L45 · resolvePostThreadRecipients calls (conditional paths may differ): useDB, db.execute
- L76 · resolveOrgBranding calls (conditional paths may differ): useDB, limit, where, from, db.select, eq, resolveBranding
- L83 · deliverToRecipients calls (conditional paths may differ): sendNotification
- L108 · emitAdminReply calls (conditional paths may differ): useDB, resolveOrgBranding, console.error, resolvePostThreadRecipients, limit, where, from, db.select, eq, deliverToRecipients, input.snippet.slice
- L128 · emitCommentNotifications calls (conditional paths may differ): resolveCommentEvents, catch, markPostUnreadForAuthor, console.error, emitAdminReply
- L151 · resolveOrgAdminRecipients calls (conditional paths may differ): findStaffFeedbackRecipients, useDB
- L155 · emitAdminNotification calls (conditional paths may differ): useDB, resolveOrgBranding, console.error, resolveOrgAdminRecipients, limit, where, from, db.select, eq, input.snippet.slice, sendNotification
- L13 · generateHelpShortId calls (conditional paths may differ): shortId
- L17 · generateHelpSlug calls (conditional paths may differ): join, map, pinyin, slice, slugify
- L25 · cjkBigrams calls (conditional paths may differ): CJK.test, out.push, text.slice, out.join
- L33 · buildHelpTsv calls (conditional paths may differ): stripMarkdown, cjkBigrams
- L43 · withHelpShortId calls (conditional paths may differ): insert, generateHelpShortId, createError
- L55 · findHelpArticleDetail calls (conditional paths may differ): useDB, limit, where, innerJoin, from, db.select, eq, and
- L86 · nextHelpArticlePosition calls (conditional paths may differ): useDB, where, from, db.select, eq
- L27 · resolveOrgContext calls (conditional paths may differ): _resolver
- L17 · bytesToHex calls (conditional paths may differ): join, Array.from, padStart, b.toString
- L21 · hexToBytes calls (conditional paths may differ): parseInt, hex.slice
- L34 · deriveBits calls (conditional paths may differ): crypto.subtle.importKey, encode, password.normalize, crypto.subtle.deriveBits
- L46 · hashPassword calls (conditional paths may differ): crypto.getRandomValues, deriveBits, bytesToHex
- L52 · verifyPassword calls (conditional paths may differ): hash.split, parseInt, Number.isFinite, hexToBytes, deriveBits, constantTimeEqual
- L24 · buildPostLink calls (conditional paths may differ): _builder
Environment references: process.env.BETTER_AUTH_URL
- L14 · searchPostsBySemantic calls (conditional paths may differ): useDB, embedding.join, postFilterConditions, conditions.push, limit, orderBy, where, leftJoin, innerJoin, from, db.select, eq, and
- L4 · checkRateLimit calls (conditional paths may differ): useStorage, Math.floor, Date.now, storage.getItem, storage.setItem
- L22 · list calls (conditional paths may differ): filter, map, v.split, s.trim
- L28 · date calls (conditional paths may differ): Number.isNaN, d.getTime
- L34 · parsePostFilter calls (conditional paths may differ): list, date
- L55 · boardCondition calls (conditional paths may differ): f.boardId.filter, isNull, inArray, or, f.boardIdNot.filter, isNotNull, notInArray, and
- L73 · postFilterConditions calls (conditional paths may differ): eq, conditions.push, isNotNull, isNull, inArray, notInArray, boardCondition, gte, lt
- L29 · runRuntimeMigration calls (conditional paths may differ): Date.now, postgres, drizzle, db.execute, migrate, String, client.end
- L24 · searchSimilarByText calls (conditional paths may differ): stripMarkdown, isEmbeddingEnabled, generateEmbedding, searchSimilarByEmbedding, searchSimilarByTrgm
- L40 · searchSimilarByEmbedding calls (conditional paths may differ): useDB, embedding.join, eq, isNull, conditions.push, ne, limit, orderBy, where, innerJoin, from, db.select, and, attachHasVoted
- L76 · searchSimilarByTrgm calls (conditional paths may differ): useDB, eq, isNull, conditions.push, ne, limit, orderBy, where, innerJoin, from, db.select, and, attachHasVoted
- L111 · searchSimilarByPostId calls (conditional paths may differ): useDB, isEmbeddingEnabled, limit, where, from, db.select, eq, searchSimilarByEmbedding, searchSimilarByTrgm
- L145 · attachHasVoted calls (conditional paths may differ): rows.map, where, from, db.select, and, eq, votes.map, votedSet.has
- L10 · generateSlugWith calls (conditional paths may differ): nanoid, slugify, exists, createError
- L23 · generateSlug calls (conditional paths may differ): useDB, generateSlugWith, limit, where, from, db.select, eq
- L32 · generateChangelogSlug calls (conditional paths may differ): useDB, generateSlugWith, limit, where, from, db.select, eq
- L40 · localAuthRequired calls (conditional paths may differ): createError
- L48 · assertTargetOrgIsSessionOrg calls (conditional paths may differ): createError, reject, getQuery, check, PAYLOAD_METHODS.has, json, request.clone
- L87 · guardSsoAuthRequest calls (conditional paths may differ): path.startsWith, path.slice, rest.startsWith, rest.split, GLOBAL_IDENTITY_ENDPOINTS.has, auth.api.getSession, createError, localAuthRequired, split, rest.slice, ORG_SCOPED_ORGANIZATION_ENDPOINTS.has, assertTargetOrgIsSessionOrg
- L28 · verifySsoJwt calls (conditional paths may differ): jwtVerify, enc.encode, createError, Math.floor, Date.now, email.includes, toLowerCase, email.trim, payload.name.trim
- L84 · findOrCreateSsoUser calls (conditional paths may differ): limit, where, from, db.select, eq, onConflictDoNothing, values, db.insert, uuidv7, createError
- L116 · validateReturnTo calls (conditional paths may differ): returnTo.includes, returnTo.startsWith, getRequestURL
- L132 · generateSsoSecret calls (conditional paths may differ): crypto.getRandomValues, padStart, toString
- L150 · ssoSecretToView calls (conditional paths may differ): row.createdAt.toISOString
- L170 · signSessionCookieValue calls (conditional paths may differ): crypto.subtle.importKey, enc.encode, crypto.subtle.sign, String.fromCharCode, btoa
- L11 · readAgentEvents calls (conditional paths may differ): stream.getReader, reader.read, decoder.decode, exec, buffer.slice, block.split, lines.find, line.startsWith, join, map, lines.filter, trimStart, line.slice, onEvent, JSON.parse, reader.releaseLock
- L16 · resolveMemberNotifications calls (conditional paths may differ): storedNotificationsSchema.parse
- L50 · checkEnabledRuleCap calls (conditional paths may differ): WIDGET_BUILTIN_RULE_IDS.filter, disabledBuiltins.includes, customRules.filter
- L16 · inspectAvatar calls (conditional paths may differ): imageMeta, includes, Number.isInteger
- L34 · hexToLinear calls (conditional paths may differ): trim, hex.replace, lin, parseInt, raw.slice
- L43 · linearToOklch calls (conditional paths may differ): Math.cbrt, Math.sqrt, Math.atan2
- L56 · oklchToLinear calls (conditional paths may differ): Math.cos, Math.sin, Math.max, Math.min, clamp
- L73 · linearToHex calls (conditional paths may differ): toUpperCase, padStart, toString, Math.round, Math.max, Math.min, gam, channel
- L90 · prefersWhiteForeground calls (conditional paths may differ): luminance, oklchToLinear, contrast
- L95 · pickForeground calls (conditional paths may differ): prefersWhiteForeground
- L100 · pickBrandForegroundHex calls (conditional paths may differ): linearToOklch, hexToLinear, normalizeBrandHex, prefersWhiteForeground
- L108 · isValidBrandHex calls (conditional paths may differ): test, hex.trim
- L112 · normalizeBrandHex calls (conditional paths may differ): hex.trim, trimmed.startsWith, isValidBrandHex, normalized.toUpperCase
- L119 · deriveBrandVars calls (conditional paths may differ): normalizeBrandHex, linearToOklch, hexToLinear, round, Math.min, Math.max, oklchStr, pickForeground
- L171 · deriveNeutralVars calls (conditional paths may differ): Object.fromEntries, map, Object.entries, l.toFixed
- L176 · derivePortalThemeVars calls (conditional paths may differ): deriveNeutralVars, deriveBrandVars
- L183 · styleObjectToCss calls (conditional paths may differ): join, map, Object.entries
- L189 · parseOrgMetadata calls (conditional paths may differ): Array.isArray, JSON.parse
- L200 · parseBranding calls (conditional paths may differ): parseOrgMetadata, brandingSchema.safeParse
- L206 · resolveBranding calls (conditional paths may differ): parseBranding, normalizeBrandHex
- L216 · mergeBrandingMetadata calls (conditional paths may differ): parseOrgMetadata, brandingSchema.parse
- L1 · formatConversationTime calls (conditional paths may differ): Number.isFinite, date.getTime, Math.floor, Math.max, date.toLocaleDateString, date.getFullYear, getFullYear
- L33 · parseGuestAccess calls (conditional paths may differ): guestAccessSchema.safeParse, parseOrgMetadata
- L38 · resolveGuestAccess calls (conditional paths may differ): parseGuestAccess
- L47 · mergeGuestAccessMetadata calls (conditional paths may differ): parseOrgMetadata, guestAccessSchema.parse
- L73 · isGuestEmail calls (conditional paths may differ): endsWith, email.toLowerCase
- L19 · parsePortalModules calls (conditional paths may differ): portalModulesSchema.safeParse, parseOrgMetadata
- L24 · resolvePortalModules calls (conditional paths may differ): parsePortalModules
- L29 · mergePortalModulesMetadata calls (conditional paths may differ): parseOrgMetadata, portalModulesSchema.parse
- L46 · countEnabledRules calls (conditional paths may differ): WIDGET_BUILTIN_RULE_IDS.filter, disabledBuiltins.includes, customRules.filter
- L52 · resolveWidgetSettings calls (conditional paths may differ): WIDGET_BUILTIN_RULES.map, disabledBuiltins.includes, countEnabledRules
- L76 · getEnabledSupportRules calls (conditional paths may differ): resolveWidgetSettings, map, settings.rules.builtins.filter, settings.rules.custom.filter
Build and deployment pipeline · 2 GitHub Actions workflows
Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.
Triggers: pull_request, push, workflow_dispatch
build · no job dependencies declared
- Checkout
actions/checkout@v4 - Setup pnpm
pnpm/action-setup@v4 - Setup Node.js
actions/setup-node@v4 - Install dependencies
pnpm install --frozen-lockfile - Build (Node server)
pnpm build
docker-build · no job dependencies declared
- Checkout
actions/checkout@v4 - Set up Docker Buildx
docker/setup-buildx-action@v3 - Validate Docker build (no push)
docker/build-push-action@v6
Triggers: push, workflow_dispatch
build-and-push · no job dependencies declared
- Checkout
actions/checkout@v4 - Set up Docker Buildx
docker/setup-buildx-action@v3 - Log in to GHCR
docker/login-action@v3 - Extract metadata
docker/metadata-action@v5 - Build and push
docker/build-push-action@v6
build: NODE_OPTIONS="--max-old-space-size=4096 $NODE_OPTIONS" nuxt build && mkdir -p .output/server/db && cp -r server/db/migrations .output/server/db/migrationsbuild:cf: NITRO_PRESET=cloudflare-module NODE_OPTIONS="$NODE_OPTIONS --max-old-space-size=8192" pnpm buildbuild:vercel: pnpm migrate && NITRO_PRESET=vercel pnpm builddeploy:cf: wrangler deploy --cwd .outputmigrate: drizzle-kit migrate
Repository README
View original on GitHub ↗Full upstream document by @linkcraftstudio · README.md · snapshot 697d3cc
FeedLog
Open-source feedback & roadmap tool. Self-host on Cloudflare Workers, Vercel, or Docker.
Collect product feedback, run a public roadmap, and publish changelogs — from a tool you own and control.
FeedLog is a feedback-collection and product-communication hub for software teams. Users post ideas, vote, and discuss; teams triage into a roadmap and publish changelog entries when features ship. Built on Nuxt 4, it deploys to three platforms with zero vendor lock-in.

Why FeedLog?
- 🌐 Deploy anywhere. Cloudflare Workers, Vercel, or Docker — pick your stack, keep your data.
- 🤖 AI-native, not AI-washed. Similar-idea detection and AI-assisted changelog drafting — first-class features, not bolted-on chatbots.
- 🎨 Modern Vue stack. Nuxt 4, TypeScript, Drizzle ORM, Tailwind CSS v4, shadcn-vue.
- 🔓 MIT licensed, open core. This repository is the complete current product. Future enterprise add-ons live separately; the open-source version stays fully functional.
- 📦 Own your data. Postgres + blob storage (Cloudflare R2, Vercel Blob, or any S3-compatible: AWS, MinIO, Alibaba OSS).
Live Demo
Try FeedLog running in production: feedback.feedlog.ai
The roadmap, changelog, and feedback boards you see at that URL are all FeedLog itself running in production — every feature listed below was first requested, voted on, and shipped through it.
Self Hosting
FeedLog is built to be self-hosted. Three deploy targets cover the common cases: serverless platforms (Cloudflare Workers, Vercel) for zero-ops managed hosting, and Docker for full control on a VPS / NAS / Docker PaaS.
Whichever path you pick, you'll need a PostgreSQL 17+ with the
vector extension reachable from
the runtime. Neon and Supabase
both enable it via their UI; on self-hosted Postgres run
CREATE EXTENSION IF NOT EXISTS vector; once.
A Deploy with Cloudflare Workers or Vercel
Both platforms offer a one-click deploy that forks the repo into your account and prompts for the required environment variables in the same form. Build and deploy run automatically; no local toolchain needed.
- Cloudflare Workers uses Hyperdrive (Postgres acceleration) and R2
(blob storage). On first request, the deployed Worker serves a
/setuppage that runs migrations against your Hyperdrive-bound database. Full guide:docs/deploy/cloudflare-workers.md. - Vercel runs
pnpm migrate && pnpm buildin the build step; failed migrations abort the deploy and leave the previous version serving traffic. Pair with Neon or Supabase from the Vercel Marketplace for the fastest setup. Full guide:docs/deploy/vercel.md.
Keep your fork up to date
Both platforms deploy from a fork of this repo, so to pick up upstream
fixes you'll want either a manual git pull from linkcraftstudio/feedlog,
or a scheduled GitHub Action (e.g. tgymnich/fork-sync)
that opens a PR against your fork's main whenever upstream changes.
B Deploy with Docker
Multi-arch image (linux/amd64 + linux/arm64) on
ghcr.io/linkcraftstudio/feedlog. Works on any Linux host — VPS, NAS,
Coolify, Dokploy, Portainer, Kubernetes.
Quick start with the bundled compose.yml
The repo ships a compose.yml that brings up the app
and a pgvector-enabled Postgres in one command. Three lines:
git clone https://github.com/linkcraftstudio/feedlog.git && cd feedlog
echo "BETTER_AUTH_SECRET=$(openssl rand -hex 32)" > .env
echo "SYSTEM_ADMIN_EMAILS=you@example.com" >> .env
docker compose up -d
The app is at http://localhost:3000. Sign up with an email listed in
SYSTEM_ADMIN_EMAILS to land as admin. Optional variables (OAuth, AI,
transactional email) can be added to the same .env — compose.yml
passes them through to the app container.
Single-container run
If you bring your own Postgres (with pgvector):
docker run -d --name feedlog -p 3000:3000 \
-e DATABASE_URL="postgresql://user:pass@host:5432/feedlog" \
-e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \
-e SYSTEM_ADMIN_EMAILS="you@example.com" \
ghcr.io/linkcraftstudio/feedlog:v0.1.0
The default image falls back to NuxtHub's local-filesystem driver for
uploads — fine for a trial; mount a volume on /app/.data to make
those uploads survive container restarts. For S3-compatible storage
(AWS S3 / R2 / MinIO / Alibaba OSS / etc.), set the S3_* env vars at
runtime — no rebuild needed. See docs/deploy/docker.md.
[!TIP] Both quick starts pin to
:v0.1.0. In production prefer an immutable tag (:vX.Y.Zor:sha-abc1234) over:latestto avoid surprise upgrades ondocker pull.
Environment variables
The three variables in the Required table are the minimum to boot a
working install. The most useful optional variables are listed below; the
full configuration reference (and
.env.example as the annotated source) covers
everything else — S3 storage, transactional email, platform-specific
overrides, and authentication toggles.
Required
| Variable | Required | Description | Example |
|---|---|---|---|
DATABASE_URL |
Yes | PostgreSQL 17+ connection string. The database must have the vector extension installed. On Cloudflare Workers, this is replaced by the Hyperdrive binding. |
postgresql://user:pass@host:5432/feedlog?sslmode=require |
BETTER_AUTH_SECRET |
Yes | Session and cookie encryption secret, 32+ random characters. Generate with openssl rand -hex 32. Rotating it invalidates every existing session. |
0a1b2c3d4e5f... (64 hex chars) |
SYSTEM_ADMIN_EMAILS |
Yes | Comma-separated emails granted admin role on first sign-up. Set this before any user signs up — otherwise the install has no admin and the dashboard is locked. | you@example.com,ops@example.com |
Common optional
| Variable | Required | Description | Example |
|---|---|---|---|
BETTER_AUTH_URL |
No | Public URL of the app (no trailing slash). Inferred from the request Host header by default. Set explicitly behind Host-rewriting proxies or to anchor OAuth callback URLs. |
https://feedback.yourdomain.com |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
No | Enable Google OAuth. Authorized redirect URI: <BETTER_AUTH_URL>/api/auth/callback/google. |
…apps.googleusercontent.com |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET |
No | Enable GitHub OAuth. Callback URL: <BETTER_AUTH_URL>/api/auth/callback/github. |
Iv1.abc123… |
OPENAI_API_KEY |
No | Enable AI features (similar-idea detection, AI-drafted changelog entries). Without it, those features are silently disabled. | sk-… |
OPENAI_BASE_URL |
No | Override the OpenAI endpoint — works with Azure OpenAI, LiteLLM, self-hosted gateways. Defaults to https://api.openai.com/v1. |
https://your-litellm-host/v1 |
RESEND_API_KEY |
No | Enable transactional email via Resend (password resets, optional sign-up verification). Without it, emails are logged to stdout. | re_… |
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY / S3_BUCKET (+ S3_ENDPOINT / S3_REGION) |
No | Enable S3-compatible blob storage at runtime — works with AWS S3, Cloudflare R2, MinIO, Alibaba OSS, Tencent COS, etc. Without these, uploads use NuxtHub's local-filesystem driver. See the service cheat sheet. | LTAI5t… / … / feedlog-prod (+ endpoint/region) |
Features
- Feedback boards — Organize ideas into public or private boards with voting, categories, and status workflows.
- Roadmap — Drag-and-drop view of planned, in-progress, and shipped work.
- Changelog — Publish release notes with AI-drafted style presets (concise, structured, benefit-led, witty).
- Similar-idea merge — Vector embeddings (OpenAI
text-embedding-3-large, 768-dim) surface duplicate feedback before it fragments the board. - Authentication — Email / password, Google OAuth, GitHub OAuth, admin role handling — powered by better-auth.
- Comments & discussions — Markdown-powered threads on every post.
- Notifications — Authors and upvoters are subscribed automatically and emailed when a post's status changes or the team replies officially; unsubscribe per post at any time. Admins aren't notified. Requires an email provider.
- Inbox — Manage Widget conversations, human handoffs and reply email notifications.
Tech Stack
| Layer | Choice |
|---|---|
| Framework | Nuxt 4 (Vue 3, Nitro, TypeScript) |
| Database | PostgreSQL 17+ with pgvector for embeddings |
| ORM / driver | Drizzle ORM + postgres-js |
| Authentication | better-auth (email/password + Google OAuth + GitHub OAuth + admin plugin) |
| AI | OpenAI-compatible API (OpenAI, Azure, LiteLLM, Ollama…) |
| Blob storage | Cloudflare R2 binding, Vercel Blob, or S3-compatible via aws4fetch (AWS, MinIO, OSS) |
| State | Pinia |
| UI | Tailwind CSS v4, shadcn-vue (new-york), Lucide icons |
| Editor | md-editor-v3 |
| Deploy targets | Cloudflare Workers, Vercel, Node.js Docker |
| Package manager | pnpm |
Local Development
git clone https://github.com/linkcraftstudio/feedlog.git
cd feedlog
pnpm install
cp .env.example .env # fill in DATABASE_URL, BETTER_AUTH_SECRET, SYSTEM_ADMIN_EMAILS
pnpm migrate # initialize database schema
pnpm dev # http://localhost:3000
See CONTRIBUTING.md for the full development setup
and PR process.
Roadmap
Live, user-voted roadmap: feedback.feedlog.ai/roadmap.
Release history: see CHANGELOG.md.
Contributing
Pull requests, bug reports, and feature discussions are welcome.
- Read
CONTRIBUTING.mdfor development setup and the PR process. - All commits are signed off under the DCO (
git commit -s). - See
CODE_OF_CONDUCT.md.
Security
Found a vulnerability? Please email feedlog.oss@outlook.com rather than filing a public issue. See SECURITY.md for details and response timelines.
Community
- GitHub Discussions — questions, ideas, show-and-tell
- Issue tracker — bugs and feature requests
License
MIT © 2026 LinkCraft Studio
Frequently asked about FeedLog
What is FeedLog?+
FeedLog is a self-hosted Canny/Featurebase alternative built on the Cloudflare developer platform. Own your feedback board, votes and public roadmap on Cloudflare Workers
What does FeedLog replace?+
FeedLog is listed as an alternative to Canny, Featurebase. Compare the features and tradeoffs before migrating.
What Cloudflare primitives does FeedLog use?+
FeedLog is built on Hyperdrive, Queues, R2, Workers.
How much does FeedLog cost to run?+
Plan for Workers Paid at US$5 per account/month plus usage, and external PostgreSQL 17+ with vector through Hyperdrive. Neon Free is a limited database option (100 CU-hours/month, 0.5 GB storage, 5 GB transfer per project). R2 uploads, optional AI/email and maintenance need separate budgets; no zero-cost or measured-bill claim. The Workers Paid minimum is account-wide, not a separate $5 subscription for every app; usage beyond included allowances can add charges. Neon Free provides 100 CU-hours monthly, 0.5 GB storage and 5 GB network transfer per project; select PostgreSQL 17+ and enable vector before the first migration. A continuously busy database or retained feedback, attachments and embeddings can exhaust database allowances; check actual provider usage. Hyperdrive connection pooling and caching are included in Workers Paid; the external database is a separate service. Configure the feedlog-inbox Queue producer and consumer; writes, reads, deletes and retries consume operations. R2 BLOB uploads are optional at the application level. Standard R2 included storage is 10 GB-month per month, with request allowances and metered usage beyond limits. OpenAI-powered features require provider credentials and models. Resend email requires separate provider setup; neither is included in the Workers subscription. Without OAuth, email/password sign-in defaults on. Without Resend, email verification defaults off and password-reset email is unavailable; choose authentication settings deliberately. A real Hyperdrive ID, public auth URL, auth secret and admin-bootstrap emails are required; shipped values are placeholders. This is a source-based planning budget, not a fresh deployment, performance benchmark, security audit or guaranteed bill. Check current Cloudflare pricing before deploying.
Is FeedLog open source?+
The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/linkcraftstudio/feedlog/697d3cc7becccc4d38f57333b2acd13dbfb3d6c6/LICENSE. Source code and contributor credit are available at https://github.com/linkcraftstudio/feedlog.
Community rating
No ratings yet. Tried this project? Share your experience.
One rating per verified account. You can change or remove yours. Accounts are email verified; use of the software is self-reported.



Discussion · 0
sign in to comment →