Forked from yusufipk/OpenFrame, which is Fair Source (FSL-1.1-ALv2) — self-hosting for internal use is explicitly permitted, and each release becomes Apache 2.0 two years after publication. Everything below is upstream's README and still true. What we added:
OPENFRAME_API_TOKENS— token auth for non-browser callers, so a render pipeline can deliver a video without holding a next-auth session. A token maps to a user and acts as them, so every authorisation check downstream is unchanged. Seelib/api-token.ts.Deliberately not a database table: a token model means a Prisma migration, and this fork would then carry a schema change to rebase against every upstream migration. The whole diff is one new file plus a one-line swap in each of six routes.
Used by:
rm-shareand the Studio's Review page.
OpenFrame is a fair source video review and approval platform for teams that need clear feedback, version control, and client-friendly review links in one place. It supports collaborative review workflows out of the box and can be self-hosted with the Docker setup included in this repository.
Prefer not to self-host? You can try OpenFrame at open-frame.net with a 7-day free trial that needs no card, then continue on the hosted plan starting at $10.
OpenFrame is built for video teams that want one system for review, revision, approval, and delivery feedback.
- Timestamped comments directly on the video timeline
- Voice notes, image attachments, and frame annotations
- Version history with side-by-side compare and per-version subtitle tracks
- Approval requests and sign-off tracking
- Share links for client review with optional guest commenting
- Workspaces, projects, member roles, and invitation flows
- Comment tags, resolved states, and CSV/PDF exports
- Video-linked assets for supporting media and references
- Email and Telegram notifications
- URL-based YouTube video intake plus optional direct uploads (Bunny Stream or self-hosted S3)
- Add a video to a project from a YouTube URL or direct upload flow.
- Share a review link with internal collaborators or external stakeholders.
- Collect timestamped feedback with text, voice, images, and annotations.
- Compare versions, resolve comments, and request approvals.
- Export feedback or keep everything tracked inside the project timeline.
- Timestamped comments anchor every note to an exact moment in the cut.
- Reviewers can leave text, voice notes, image attachments, and drawn annotations.
- Comment threads support replies, resolution states, and project-specific tags.
- Videos support multiple versions inside the same review thread, each with its own subtitle tracks uploaded as SRT or WebVTT.
- Teams can switch between versions without losing review context.
- Compare mode lets reviewers inspect two versions side by side.
- Share links can be configured for view or comment access.
- Guest review is supported for external stakeholders.
- Workspaces and projects support member roles, invitations, and scoped access.
- Approval requests can be sent to specific reviewers.
- Approval decisions are tracked per request with pending, approved, rejected, and canceled states.
- Comments can be exported as CSV or PDF for offline review and handoff.
- Videos can include related assets such as images, supplementary videos, and audio.
- Notification settings support email and Telegram delivery.
- Self-hosted setups can run with bundled S3-compatible storage or external object storage.
- Optional integrations include Stripe billing, Bunny direct uploads, OAuth providers, SMTP, and Telegram notifications.
OpenFrame is built with:
- Next.js 16 and React 19
- Bun
- TypeScript
- Prisma
- PostgreSQL
- NextAuth.js
- Tailwind CSS
- MinIO or other S3-compatible object storage for self-hosted media and direct video uploads
- Bunny Stream for optional hosted direct video uploads (mutually exclusive with S3 video uploads)
OpenFrame ships with a Docker Compose setup for self-hosting. The default stack brings up:
- OpenFrame on
http://localhost:3000 - PostgreSQL for the application database
- MinIO for S3-compatible object storage
cp .env.docker.example .env.dockerEdit .env.docker, set strong values for NEXTAUTH_SECRET, POSTGRES_PASSWORD, and the MinIO credentials, then start the stack:
docker compose up --buildOpen http://localhost:3000 after the containers become healthy.
MinIO is bound to 127.0.0.1 by default, so the S3 API and admin console stay local to the host unless you intentionally re-publish those ports.
The Docker template already trusts localhost:3000 for Auth.js via AUTH_TRUST_HOST=true, so the default local Compose flow does not require extra auth host setup.
- The app waits for PostgreSQL and MinIO before starting.
- Prisma migrations run automatically on container boot.
- The MinIO bucket is created automatically when
SELF_HOSTED_AUTO_CREATE_BUCKET=true.
- PostgreSQL data is stored in the
postgres-dataDocker volume. - MinIO objects are stored in the
minio-dataDocker volume. - After updating the repo, rebuild and restart with
docker compose up --build.
If you do not want to build OpenFrame locally, use the published Docker Hub image instead.
Pull a specific version:
podman pull docker.io/yusufipk/openframe:v0.1.0You can also inspect these tags on Docker Hub:
yusufipk/openframe:v0.1.0for a fixed releaseyusufipk/openframe:latestfor the newest build from themainbranchyusufipk/openframe:sha-<commit>for a commit-pinned image
Use latest if you want the newest mainline build. For real deployments, prefer a fixed version tag such as v0.1.0 instead of latest.
To use the published image in Compose, open docker-compose.yml and change only the app service from a local build: block to an image: reference such as docker.io/yusufipk/openframe:v0.1.0. Keep the rest of the service and the postgres and minio services unchanged.
Replace this:
app:
build:
context: .dockerfile: DockerfileWith this:
app:
image: docker.io/yusufipk/openframe:v0.1.0If the build: block is still present, podman compose up -d will try to build locally from the current directory instead of pulling the published image.
Then start the stack normally:
podman compose up -dOpen http://localhost:3000/login after the containers become healthy.
To verify a published image manually, point your Compose app service at a fixed image tag such as docker.io/yusufipk/openframe:v0.1.0, start the stack with podman compose up -d, and open http://localhost:3000/login after the containers become healthy.
The Docker example disables hosted-only features by default:
OPENFRAME_ENABLE_STRIPE=false
OPENFRAME_ENABLE_BUNNY_UPLOADS=false
OPENFRAME_REQUIRE_INVITE_CODE=falseBehavior when disabled:
OPENFRAME_ENABLE_STRIPE=falsedisables Stripe checkout and customer portal flows and removes billing-based workspace restrictions.OPENFRAME_ENABLE_BUNNY_UPLOADS=falsehides Bunny direct-upload entry points. URL-based providers such as YouTube remain available. When enabling it, setBUNNY_CDN_URL(not onlyNEXT_PUBLIC_BUNNY_CDN_URL): it is read at request time, so a published image picks up the playback host without a rebuild.OPENFRAME_ENABLE_S3_VIDEO_UPLOADS=true(withR2_*configured) enables presigned uploads to your own S3-compatible storage. SetOPENFRAME_ENABLE_BUNNY_UPLOADS=false— only one direct-upload backend can be active. The bucket must allow CORSPUTfrom your app origin (for examplehttp://localhost:3000in dev and your production URL). For Docker + MinIO, keepR2_ENDPOINT=http://minio:9000(app-internal) and setR2_PRESIGN_ENDPOINTto the browser-reachable MinIO origin (for examplehttp://localhost:9000locally, orhttps://minio.example.comwhen MinIO is behind a reverse proxy). Use the origin only — no path suffix. The app's Content-Security-Policy is generated from runtime env at request time, so published Docker images pick up customR2_PRESIGN_ENDPOINTvalues without rebuilding or editingnext.config.ts.OPENFRAME_REQUIRE_INVITE_CODE=falseallows open registration while keeping invitation-link registration intact.OPENFRAME_ENABLE_ANALYTICS=truerecords first-touch attribution and funnel events into your own database, readable on/admin/growth, or as JSON on/api/admin/growthby a script sendingAuthorization: Bearer $OPENFRAME_ADMIN_API_TOKEN(at least 32 characters, unset by default, in which case an admin session is the only way in). Off by default, and nothing leaves the instance either way.
For self-hosted MinIO behind a reverse proxy, choose one of these browser-facing layouts:
- Separate storage host: route
https://minio.example.comto MinIO and setR2_PRESIGN_ENDPOINT=https://minio.example.complusR2_PUBLIC_BASE_URL=https://minio.example.com/openframe. - Same app host: route the bucket path, for example
https://openframe.example.com/openframe/*, to MinIO and keep all other paths routed to the OpenFrame app. SetR2_PRESIGN_ENDPOINT=https://openframe.example.comandR2_PUBLIC_BASE_URL=https://openframe.example.com/openframe.
Do not add an extra path prefix such as /s3 in front of the bucket unless your proxy rewrites it away before MinIO sees the request. S3 path-style presigned URLs expect the first path segment to be the bucket name, so /openframe/videos/... is valid while /s3/openframe/videos/... makes MinIO treat s3 as the bucket.
These integrations remain optional for self-hosted deployments and can be enabled later by setting the related environment variables:
- Stripe billing
- Bunny direct uploads (hosted) or S3 video uploads via
OPENFRAME_ENABLE_S3_VIDEO_UPLOADS(self-hosted) - SMTP for invitation and notification delivery
- Telegram notifications
- External S3-compatible storage such as Cloudflare R2 or another compatible provider instead of bundled MinIO
- Google and GitHub OAuth
Install dependencies and run validation with Bun:
bun install
bun run checkFeature flags and self-hosting environment variables are documented in .env.example and .env.docker.example.
The testing stack, layout, and conventions are documented in TESTING.md.
bun run test# unit and component suites, no database needed
bun run test:api # API integration suites, needs the test database
bun run test:e2e # Playwright end-to-end specs, needs the test database
bun run verify # bun run check plus the unit and component suitesThe API and end-to-end suites need the disposable Postgres defined in docker-compose.test.yml, and the end-to-end suite also needs the MinIO service in its e2e profile. Start Postgres with bun run test:db:up and stop everything with bun run test:db:down. Run those two suites one at a time: they share a database, and the API suite empties every table between its tests.
scripts/test.sh <unit|api|e2e|all> is the shortcut: it runs a suite inside a container, so no package manager runs on your host, and it starts the test database first when the suite needs one.
./scripts/test.sh unit
./scripts/test.sh apiThe pre-push Git hook runs bun run verify on every push. It leaves bun run test:api out on purpose, because that suite needs the database container.
OpenFrame is Fair Source, licensed under the Functional Source License (FSL-1.1-ALv2). The full source code is publicly available, you can self-host it, and every release automatically becomes Apache 2.0 open source two years after its publication. See LICENCE for the full terms.
Contributions are welcome.
- Read CONTRIBUTING.md for workflow, conventions, and PR requirements.
- Use SECURITY.md for responsible vulnerability reporting.
- Follow CODE_OF_CONDUCT.md in all project interactions.
- Contact: info@open-frame.net
