Browse by type
Live app · Quick start · Self-hosting · Configuration
Paste a YouTube link - or upload your own video file - and get back a structured, first-person article you can publish as-is.
The AI watches the video itself, audio and visuals, through multimodal input. There is no transcript step and no caption requirement, so it works on videos that have no subtitles at all.
ytb_ API keys (Pro), including style and styleInstructions fieldsFrom a YouTube link
From an uploaded video
| Framework | Next.js 16 (App Router, Turbopack) |
| Language | TypeScript |
| Database | Neon Postgres + Drizzle ORM |
| Auth | better-auth (email/password + Google) |
| AI | Vercel AI SDK via AI Gateway |
| File storage | Vercel Blob |
| Billing | Creem (merchant of record) - optional |
| UI | Tailwind CSS + shadcn/ui |
| Tooling | Biome / Ultracite, node --test |
Prerequisites: Node.js 20+, pnpm, a Neon database, and a YouTube Data API v3 key.
git clone https://github.com/TheOrcDev/youtube-to-blog.git
cd youtube-to-blog
pnpm install
Create .env.local:
# Required
DATABASE_URL=your_neon_connection_string
YOUTUBE_API_KEY=your_youtube_api_key
NEXT_PUBLIC_APP_URL=http://localhost:3000
# AI Gateway - one of the two (OIDC is provided automatically on Vercel)
AI_GATEWAY_API_KEY=vck_your_gateway_key
# VERCEL_OIDC_TOKEN=provided_by_vercel
# Required only for the video upload feature
BLOB_READ_WRITE_TOKEN=your_blob_read_write_token
Then set up the database and start the app:
pnpm db:migrate
pnpm dev
Open http://localhost:3000.
Billing is entirely optional and off by default. Leave CREEM_API_KEY
unset and the app runs with unlimited generations, no quotas, and no
pricing or billing UI - the code skips those paths completely. Bring your own
AI and YouTube keys and everything works.
Everything under Billing applies only if you want to run a paid, hosted instance.
| Plan | Price | Generations / month | Video uploads | API access | Custom style notes | Model |
|---|---|---|---|---|---|---|
| Free | $0 | 5 | - | - | - (presets only) | Gemini 2.5 Flash |
| Pro | $9/mo or $79/yr | 100 | ✅ up to 64MB | ✅ | ✅ | Premium model |
| Self-hosted | - | Unlimited | ✅ up to 64MB | ✅ | ✅ | Gemini 2.5 Flash |
Usage is counted per calendar month (UTC). A YouTube video that already has a post costs no AI call and no quota; uploads always generate a fresh post and count as one.
The 64MB upload cap exists because video bytes are sent inline (base64) through
the AI Gateway, which rejects bodies around ~100MB. It lives in
lib/entitlements/policy.ts as MAX_UPLOAD_BYTES,
alongside the per-tier limits and models. Prices live in
lib/billing/pricing.ts.
Pro users (and self-hosted instances) can drive the app over a REST API. Create a key under Dashboard → API keys, then:
curl -X POST https://www.youtube2blog.com/api/v1/blogs \
-H "Authorization: Bearer ytb_your_api_key" \
-H "Content-Type: application/json" \
-d '{"youtubeUrl": "https://www.youtube.com/watch?v=..."}'
Endpoints: POST /api/v1/blogs (create), GET /api/v1/blogs (list),
GET /api/v1/blogs/{slug} (fetch with Markdown content). Keys are limited to
10 requests per minute; generation shares the account's monthly quota. Full
reference at /docs/api.
The hosted version uses Creem for subscriptions:
CREEM_API_KEY=your_creem_api_key
CREEM_WEBHOOK_SECRET=your_creem_webhook_secret
CREEM_PRO_PRODUCT_ID=your_monthly_product_id
CREEM_PRO_YEARLY_PRODUCT_ID=your_yearly_product_id
CREEM_TEST_MODE=true # use Creem's test environment while developing
Point your Creem dashboard webhook at /api/webhooks/creem. Locally, expose it
with a tunnel, for example cloudflared tunnel --url http://localhost:3000.
Accounts listed in ADMIN_EMAILS are never metered - unlimited generations on
the premium model, no subscription needed. Comma-separate for several; matching
ignores case and surrounding whitespace.
ADMIN_EMAILS=you@example.com,teammate@example.com
| Script | Purpose |
|---|---|
pnpm dev |
Start the dev server |
pnpm build |
Production build |
pnpm test |
Run the test suite (node --test) |
pnpm lint |
Biome check |
pnpm format |
Biome format |
pnpm db:generate |
Generate a migration after editing db/schema.ts |
pnpm db:migrate |
Apply migrations to a fresh database |
pnpm db:push |
Sync schema to an existing database without a migration file |
Upgrading a database created before this repo had migrations? The baseline migration creates every table, so it cannot be replayed against a database that already has the auth and blogs tables. Apply just the additive pieces:
node --env-file=.env.local scripts/apply-billing-migration.mjs(billing tables),node --env-file=.env.local scripts/apply-upload-migration.mjs(upload columns),node --env-file=.env.local scripts/apply-api-key-migration.mjs(apikey table)
Deploys to Vercel as-is. Push to GitHub, import the repo, and set the environment variables:
DATABASE_URL, YOUTUBE_API_KEY, NEXT_PUBLIC_APP_URLAI_GATEWAY_API_KEY, or the VERCEL_OIDC_TOKEN Vercel provides automaticallyBLOB_READ_WRITE_TOKEN - created automatically when you add a Blob store to the projectPaid instances additionally need CREEM_API_KEY, CREEM_WEBHOOK_SECRET,
CREEM_PRO_PRODUCT_ID, CREEM_PRO_YEARLY_PRODUCT_ID, and optionally
ADMIN_EMAILS.
Pull requests are welcome.
mainpnpm lint and pnpm test should passFound a bug or have an idea? Open an issue.
MIT - see license.md.
Made with 🪓 by OrcDev
browse all types & interfaces →
$ claude mcp add youtube-to-blog \
-- python -m otcore.mcp_server <graph>