# API Overview
Source: https://docs.anotherwrapper.com/api/overview
Understand the main route groups and patterns used by the backend
AnotherWrapper mostly uses App Router route handlers. Many "API features" don't live in one central `/api` folder -- app-specific routes live **right next to the app** that owns them.
## Two Main API Areas
### Shared API Routes (`app/api/*`)
These are routes used across the whole product:
* Better Auth handler and provider callbacks
* Avatar uploads
* OG image generation
* Payment webhooks
### App-Specific API Routes
These live inside the product apps themselves, keeping logic close to the UI it belongs to:
* `app/(apps)/chat/api/*`
* `app/(apps)/image-studio/api/*`
* `app/(apps)/video-studio/api/*`
* `app/(apps)/voice/api/*`
* `app/(apps)/audio/api/*`
* `app/(apps)/vision/api/*`
* `app/(apps)/marketing-plan/api/*`
* `app/(apps)/launch-simulator/api/*`
Good rule of thumb: if a route is tightly tied to one app's UX, keep it in that app's folder. If it's shared across the whole product, it belongs under `app/api`.
## Common Backend Patterns
Many routes use helpers like `requireApiUser()` so protected actions fail fast if the user isn't logged in. No guessing, no silent failures.
AI generation routes typically follow this pattern:
1. Authenticate the user
2. Reserve credits
3. Call the AI provider
4. Store the result
5. Refund credits if the generation fails
This pattern shows up across multiple AI app routes and keeps your credit system reliable.
The backend doesn't hardcode one provider flow per page. Instead, it routes through shared model/provider helpers. This means you can swap models without rewriting routes.
Uploads go through storage first. Then the stored asset URL is passed into the downstream generation or analysis flow. This keeps your file handling consistent across all apps.
## Important Route Groups
**Route:** `app/api/auth/[...all]/route.ts`
The catch-all auth handler powered by Better Auth. Handles sign-in, sign-up, sessions, and provider callbacks.
**Route:** `app/api/payments/[provider]/route.ts`
The shared webhook entrypoint for supported payment providers (Polar, Stripe, LemonSqueezy). Handles purchase verification, purchase persistence, credit allocation, and plan-state updates.
The chat area has the deepest API surface:
* Streaming chat
* Conversation history
* Document generation
* Document upload/link/unlink/delete
* Document vectorization
* File upload
Image Studio, Video Studio, Voice Studio, Vision, Audio, Marketing Plan, and Launch Simulator each have their own generation routes with their own validation and provider requirements.
# Audio
Source: https://docs.anotherwrapper.com/apps/audio
Record or upload audio, transcribe it, and turn it into clean notes with AI
The Audio app is a **voice-to-notes workflow**. Users record or upload audio, get a transcript, then generate a structured summary with action items. Way more useful than a plain "speech-to-text" page.
## What Your Users Can Do
The app is built around a simple but practical workflow:
Users can record directly in the browser or upload an existing audio file.
The audio is sent to Replicate Whisper for accurate transcription.
OpenAI takes the transcript and produces a structured summary with a title and action items.
Everything is saved -- users can come back to any recording, re-read the transcript, and check their action items.
This is a strong pattern for meeting notes, voice memos, interviews, and internal productivity tools.
## What Powers It
* **Replicate Whisper** for transcription
* **OpenAI via the AI SDK** for structured summaries
* **Storage** for uploaded audio files
* **Better Auth + PostgreSQL** for user accounts, recordings, and summaries
## What You Need
Auth, saved recordings, transcripts, and summaries
Uploaded recordings
Structured summary generation
Whisper transcription
## How It Works Under the Hood
The user records audio in-app or uploads a file.
The file is saved to object storage.
Replicate Whisper creates a text transcript from the audio.
The transcript is saved in PostgreSQL, linked to the user's account.
OpenAI generates a structured summary, title, and action items from the transcript.
The user can reopen any recording later and review everything in one place.
This app isn't just transcription. It turns raw audio into something you can actually act on. That's what makes it useful as a product pattern.
## Great Starting Point For...
* Meeting summarizers
* Founder voice memo tools
* Podcast notes
* Sales call summaries
* Interview analysis
* Private internal knowledge capture
## Files to Customize
If you want to make this your own, start here:
* `app/(apps)/audio/toolConfig.ts`
* `app/(apps)/audio/api/transcribe/route.ts`
* `app/(apps)/audio/api/summarize/route.ts`
* `app/(apps)/audio/components/*`
## Verification
Your setup is working if:
* You can upload or record audio
* Transcription completes successfully
* A summary is generated with a title and action items
* Recordings remain available after refresh
# AI Chat
Source: https://docs.anotherwrapper.com/apps/chat
Multi-provider AI assistant with web search, PDF RAG, generative UI, and reasoning models
This is the most advanced app in AnotherWrapper Premium. It's not a basic chatbot -- it's a **multi-model AI workspace** with streaming responses, web browsing, PDF chat, citations, generative UI, and persistent conversation history.
## What Your Users Get
From the user's perspective, this feels like a serious AI assistant, not a toy prompt box. They can:
* **Switch models** on the fly -- GPT, Claude, Gemini, Grok, DeepSeek, Llama
* **Chat with streaming** responses that feel instant
* **Upload PDFs** and ask questions about them (with citations!)
* **Browse the web** when browsing mode is enabled
* **Work with images** and other multimodal inputs
* **Generate documents** and UI blocks inside the chat
* **Keep conversation history** tied to their account
If you want a "main AI app" in your product, this is the one.
## Supported Models
| Provider | Models |
| ------------- | ------------------------------------------------------------------------- |
| **OpenAI** | GPT-5, GPT-5 mini, GPT-5 nano, GPT-4o, o3 |
| **Anthropic** | Claude Opus 4.5, Sonnet 4.5, Haiku 4.5 |
| **Google** | Gemini 3 Pro, Gemini 3 Pro Image, Gemini 3.1 Flash Lite, Gemini 2.5 Flash |
| **Groq** | Llama 4 Scout, Llama 4 Maverick |
| **xAI** | Grok 4, Grok 4.1, Grok 4.1 Fast Reasoning |
| **DeepSeek** | DeepSeek Chat |
Models are configured in `lib/ai/models.ts`. You can add, remove, or tweak available models there.
## What Makes It Feel Like a Real Product
This isn't just "call an API and show text." It combines several systems:
* **Vercel AI SDK** for streaming, tools, and provider abstraction
* **Multiple providers** so users aren't locked into one model family
* **Native web search tools** for supported providers
* **RAG** so uploaded PDFs can be searched semantically
* **Better Auth + PostgreSQL** for auth, sessions, and persistence
* **Object storage** for uploads
* **Credit gating** so premium models and actions can be monetized
That combination is what makes it feel like a product instead of a demo.
## Setup
Get chat running with the essentials.
You need auth, a database, and at least one configured chat provider. Storage is optional unless you want uploads or PDF chat.
Auth + hosted Postgres for this boilerplate
OpenAI, Anthropic, Google, Groq, xAI, or DeepSeek
Only needed for uploads, file attachments, and PDF workflows
Start the dev server and open the chat page. You should see streaming responses from your configured model.
Switch between models, send a few messages, and confirm that conversations persist after a refresh.
Get every feature running -- PDF chat, web search, and all model families.
For the full document workflow, set up auth, database, storage, and OpenAI.
Auth + hosted Postgres
S3-compatible storage
GPT models + PDF embeddings
Each provider unlocks its model family in the chat UI.
Claude models
Gemini models
Llama models
Grok models
DeepSeek models
Follow the [Vector Database & RAG guide](/services/vector-rag) to enable document uploads, semantic search, and citations.
Test model switching, PDF uploads with citations, web browsing on supported models, and conversation persistence.
### Web Search
Web search uses the native tool support from each provider -- there's no separate search API to configure.
Providers with native search support:
1. OpenAI
2. Anthropic
3. Google
4. xAI
Search availability depends on the selected model/provider. Models without native web search support (like some Groq or DeepSeek paths) won't get a fallback search provider automatically.
## How a Chat Request Works
The message hits the server along with the chosen model, browsing mode, and any attached documents.
If documents are active, the app retrieves relevant chunks from PostgreSQL via semantic search.
The server builds the system prompt and tool list based on the model, context, and active features.
The answer streams back in real time to the user's browser.
The final messages and metadata are saved in PostgreSQL for conversation history.
## PDF Chat, Vector Search, and Citations
The document workflow is one of the most powerful parts of this app.
When a user uploads a PDF:
1. The file is stored in object storage
2. Text is extracted from the PDF
3. The text is split into chunks
4. Each chunk is embedded with OpenAI
5. Embeddings are stored in PostgreSQL with `pgvector`
Later, when the user asks a question, those chunks are matched semantically and injected into the prompt.
When the chat retrieves context from uploaded documents, the relevant source chunks are tracked. The UI then renders citations alongside the AI's response so users can see exactly where the information came from.
Check out the full guide: [Vector Database & RAG](/services/vector-rag)
## Generative UI and Tools
This chat app isn't text-only. It supports tool-driven interactions like:
* **Document creation** -- generate and update docs inside the chat
* **App suggestions** -- contextual recommendations
* **Web search** -- provider-native browsing when enabled
That's what makes the chat feel "more like an agent" and less like a plain chatbot.
## Credit Gating
The repo includes an app-wide credit layer that makes monetization straightforward:
* Free models can stay free
* Premium models can cost credits
* Browsing can also cost credits
* The app returns usage metadata so the UI can show what happened
## Good First Customizations
Ready to make this your own? Here are the usual first edits:
* Update the model list in `lib/ai/models.ts`
* Change which models are free vs. premium
* Adjust the system prompt in `app/(apps)/chat/prompt.ts`
* Add or remove tools under `app/(apps)/chat/tools/`
* Tune the document retrieval flow under `lib/rag/`
## Verification
Your chat setup is working if:
* The page loads and streams responses
* Switching models changes the provider/model badge
* Browsing works on models that support native search
* Uploaded PDFs can be indexed and cited
* New messages persist after refresh
Learn why streaming, tools, and model switching work the way they do.
Learn how document chat and citations are implemented.
Uploads and file-backed workflows depend on storage being configured.
# Image Studio
Source: https://docs.anotherwrapper.com/apps/image-studio
Generate images with OpenAI and Replicate-backed image models
Image Studio isn't just "enter prompt, get image." It's a full creative workflow with a gallery, model switching, history, lightbox previews, downloading, and deletion. One of the most product-ready apps in the repo.
## What Your Users Can Do
* Write a prompt and generate images
* Switch between several image models
* Choose between OpenAI and Replicate-backed image models
* Browse creations in a visual gallery
* Preview images in a lightbox
* Search previous generations
* Download or delete results
It feels like a real AI creative tool, not a demo.
## Supported Models
| Model | Provider | Notes |
| --------------- | --------- | ------------------------- |
| GPT Image | OpenAI | Direct OpenAI integration |
| Nano Banana 2 | Replicate | -- |
| Nano Banana Pro | Replicate | -- |
| Seedream 5 Lite | Replicate | -- |
| Imagen 4 | Replicate | -- |
| Flux 1.1 Pro | Replicate | -- |
| Flux Schnell | Replicate | -- |
| Ideogram V2 | Replicate | -- |
| Recraft V3 | Replicate | -- |
Models are configured in `app/(apps)/image-studio/lib/models.ts`.
## What You Need
Auth and saved generation history
Generated files and persistent gallery items
Most non-OpenAI image models
You also need `OPENAI_API_KEY` for GPT Image.
## How It Works
The user types what they want and selects from the available model list.
The app routes the request to the correct provider (OpenAI or Replicate).
The generated image is uploaded to object storage and linked to the user's account.
The gallery updates and shows the new image alongside previous creations.
Users can preview images in a lightbox, search through their history, download files, or clean up old generations.
The UI is built around a gallery plus a floating generation bar -- a much stronger pattern than hiding everything behind a form-heavy dashboard.
## Great Starting Point For...
You can reshape this app into:
* AI ad creative generators
* Social media asset tools
* Product mockup generators
* Brand visual tools
* Internal creative studios
It already handles the annoying product details like history, storage-backed assets, and model switching.
## Good First Customizations
* The model catalog in `app/(apps)/image-studio/lib/models.ts`
* Default model settings
* Credit pricing per model
* Branding and empty states
* Gallery filtering and sorting behavior
## Verification
Your setup is working if:
* The page loads
* At least one configured model is available
* Generations complete successfully
* New images appear in the gallery
* Download and delete both work
# Apps Overview
Source: https://docs.anotherwrapper.com/apps/index
Explore the 8 AI apps included in AnotherWrapper Premium
Your starter kit comes loaded with **8 production-ready AI apps**. These aren't random demos -- they're real product patterns you can ship, remix, or build on top of.
Across the suite, you reuse the same auth, credits, generation history, and PostgreSQL layers. Upload-heavy and media-heavy apps also plug into the shared storage system, while the schema-first apps stay lighter.
## The Apps
The flagship. Multi-model AI assistant with streaming, web browsing, PDF chat, citations, generative UI, and conversation history. This is the one most people start with.
Gallery-based image generation with model switching, lightbox previews, search, and download. Think: your own AI creative tool.
Text-to-video and image-to-video generation with Sora, Veo, Kling, and WAN models. Async queue, polling, and a clean gallery workflow built in.
Text-to-speech, voice transformation, music generation, and sound effects -- all powered by ElevenLabs, wrapped in one product shell.
Record or upload audio, get a transcript, then generate structured summaries with action items. Voice-to-notes, done right.
Upload a meal photo, get calorie estimates and macro breakdowns. A clean example of the "image in, structured data out" pattern.
Fill in a short form, pick a model, get a validated growth strategy back. The go-to example for schema-based business tools.
Simulate a Product Hunt launch with AI-generated stats, timeline events, top comments, and founder drama. Serious architecture, playful output.
## Shared Building Blocks
All 8 apps reuse the same core infrastructure:
* **Auth** -- Better Auth for sign-in, sessions, and user management
* **Credits** -- a shared credit system for monetizing premium features
* **Provider abstraction** -- swap AI models without rewriting your app
* **Storage** -- S3-compatible object storage for upload-heavy and media-heavy apps
* **Generation history** -- every output is saved and accessible later
* **PostgreSQL persistence** -- Drizzle ORM for clean, typed data access
That shared foundation is the real value. You're not maintaining 8 separate codebases -- you're building on one.
## Where to Start
If you want to understand the repo fast, explore these three apps in order. Together they cover most of the key product patterns in the codebase.
The most advanced app. Covers streaming, tools, RAG, generative UI, and multi-model switching.
A gallery-based creative workflow. Great for understanding storage, model catalogs, and asset management.
The simplest "image in, structured data out" pattern. Perfect for learning schema-based AI workflows.
# Launch Simulator
Source: https://docs.anotherwrapper.com/apps/launch-simulator
Generate schema-based Product Hunt launch simulations with multiple AI models
Launch Simulator is the most **playful** app in the repo -- but don't let the fun fool you. Under the hood, it's the same serious architecture: validated output, saved generations, model switching, credit gating, and custom result rendering.
## What Is This?
Ever wondered how your product would do on Product Hunt? Now you can find out -- sort of.
Fill in a short form about your product, hit generate, and watch the AI simulate your entire launch day. Complete with upvote counts, drama in the comments, and a bingo card of launch-day moments.
It's entertainment-first AI, but built with production-grade patterns.
## What You Get Back
The generated simulation includes structured sections like:
* **Launch stats** -- upvotes, ranking, traffic estimates
* **Timeline events** -- hour-by-hour drama of your launch day
* **Top comments** -- AI-generated community reactions (the good, the bad, and the snarky)
* **Founder profile** -- how the AI sees you as a founder
* **Competitor drama** -- because every launch has haters
* **Bingo card moments** -- the classic launch-day tropes, checked off one by one
All of this is structured data rendered through a custom output component -- not a wall of text.
## How It Works
Fill in a short form with your product details. Keep it real -- the AI uses this to craft the simulation.
Choose which AI model powers your simulation.
The AI returns a multi-section structured simulation that matches the defined schema.
Browse through your simulated launch day. Laugh, cry, share it with your team.
## Credit System
Launch Simulator uses the shared credit system. Premium models cost credits, free models don't. No extra billing code needed.
## Great Starting Point For...
Despite the playful surface, this app is a genuinely useful template for:
* **Startup roast tools** -- let AI roast your pitch
* **Report generators** with multiple structured sections
* **Novelty AI products** with shareable outputs
* **Simulations and scenario generators** for any domain
* **Entertainment-first AI tools** that still need real architecture
The magic trick here: structured schemas + custom renderers = AI outputs that look and feel like a real product, not a ChatGPT screenshot.
## Files to Customize
Want to turn this into your own simulation or generator? Start here:
Edit `app/(apps)/launch-simulator/schema.ts`
Edit `app/(apps)/launch-simulator/prompt.ts`
Edit `app/(apps)/launch-simulator/form.tsx`
Edit `app/(apps)/launch-simulator/output.tsx`
Edit `app/(apps)/launch-simulator/toolConfig.ts` and `app/(apps)/launch-simulator/api/route.ts`
# Marketing Plan
Source: https://docs.anotherwrapper.com/apps/marketing-plan
Generate schema-based marketing plans with multiple AI models
Marketing Plan is the clearest example of a **schema-first business workflow** in this repo. Short form in, validated growth strategy out. No free-form AI rambling -- just clean, structured data.
## Why This App Matters
This is the pattern you want for business tooling:
1. Collect focused inputs via a form
2. Define a schema for the output
3. Generate validated data with `Output.object(...)`
4. Render the result in a product-friendly UI
That pattern works beautifully for planners, analyzers, internal ops tools, and niche micro-SaaS products.
## How It Works
The user provides key details about their product and goals. Short and focused -- not a 20-field monster.
Choose from available AI models to power the generation.
The app sends the inputs plus the schema to the AI. The response is forced to match the defined structure.
The result renders in a clean UI with two main sections: **traditional growth tactics** and **creative growth hacks**.
Each section is rendered from structured data -- not post-processed text blobs.
## Credit System
Marketing Plan uses the shared credit system. You can monetize premium generations without rebuilding billing logic. Set different prices for different models, or keep some free.
## Great Starting Point For...
This app is easy to reshape into:
* SEO planners
* Content strategy tools
* Growth consulting products
* GTM assistants
* Founder planning tools
The schema-based pattern means your outputs are always clean and predictable.
## Files to Customize
Edit `app/(apps)/marketing-plan/schema.ts` to match the data you want back from the AI.
Edit `app/(apps)/marketing-plan/prompt.ts` to tell the AI what kind of plan to generate.
Edit `app/(apps)/marketing-plan/form.tsx` to collect the right inputs from users.
Edit `app/(apps)/marketing-plan/output.tsx` to control how the result looks.
Edit `app/(apps)/marketing-plan/toolConfig.ts` for model selection, credits, and metadata.
Edit `app/(apps)/marketing-plan/api/route.ts` if you need to change the generation logic.
The fastest way to build a new schema-based AI tool? Duplicate this app, swap the schema and prompt, and you're shipping.
# Video Studio
Source: https://docs.anotherwrapper.com/apps/video-studio
Generate videos with Sora, Veo, Kling, and WAN models
Video Studio is the long-form generation counterpart to Image Studio. It lets you generate AI videos with several model families while keeping a clean gallery workflow for history, previewing, and downloads.
## What Your Users Can Do
* Generate videos from text prompts
* Use image-to-video when the selected model supports it
* Switch between multiple video model families
* Choose settings like ratio, duration, and resolution
* Browse generated videos in a gallery
* Preview completed videos
* Download or delete older generations
This makes it a strong base for creative tools, marketing workflows, and short-form video products.
## Supported Models
| Model | Type | Provider |
| ---------------------------- | ------------------------------ | -------- |
| `openai/sora-2` | Text-to-video / image-to-video | OpenAI |
| `openai/sora-2-pro` | Text-to-video / image-to-video | OpenAI |
| `google/veo-3-fast` | Text-to-video / image-to-video | Google |
| `google/veo-3` | Text-to-video / image-to-video | Google |
| `kwaivgi/kling-v2.6-pro-t2v` | Text-to-video | Kling |
| `kwaivgi/kling-v2.6-pro-i2v` | Image-to-video | Kling |
| `wan-video/wan-2.6-t2v` | Text-to-video | WAN |
| `wan-video/wan-2.6-i2v` | Image-to-video | WAN |
Some models are text-to-video only. Others support image input too -- check the "Type" column above.
## What You Need
Auth and generation history
Generated videos and source assets
Required for the current Video Studio implementation
Today, all shipped video models are submitted through the Replicate integration, so `REPLICATE_API_TOKEN` is the key that actually gates Video Studio.
## How It Works
The user picks from the available models and configures settings like ratio and duration.
Text-to-video models just need a prompt. Image-to-video models also accept a starting-frame image.
The request kicks off an async generation job.
Video generation is slower than images. The app handles polling and progress tracking for you.
Once complete, the final video output is saved to object storage.
The video appears in the user's gallery for playback, download, or deletion.
Video generation is inherently slower and more fragile than image generation. This app already handles that reality -- async queues, polling, error recovery -- so you don't have to build it from scratch.
## Great Starting Point For...
* Ad video generators
* Social content tools
* Product promo generators
* AI storyboarding tools
* Creator workflows with reusable visual styles
## Good First Customizations
* Which video models are shown
* Which settings are exposed to users
* Credit pricing per model
* Output layout and gallery presentation
* Whether image-to-video is prominent or optional
## Verification
Your setup is correct if:
* The page loads
* At least one configured model is available
* A generation completes and saves successfully
* Videos play back in the gallery
# Vision
Source: https://docs.anotherwrapper.com/apps/vision
AI calorie tracking app that analyzes meal photos and returns nutrition data
This is a **calorie tracking app** powered by vision AI. Upload a meal photo, get estimated calories and macro breakdowns. It's a clean example of the "image in, structured data out" product pattern.
## What Your Users See
From the user's perspective, this app is wonderfully simple:
Take a photo or upload one from your device.
The vision model examines the image and identifies food items.
See estimated calories, macro breakdowns, and per-item analysis.
Revisit saved results and use the dashboard shell as a starting point for day-level tracking if you want to build that out.
A practical computer-vision UX -- not just "describe this image."
## What the App Tracks
* Estimated total calories
* Macro breakdowns (protein, carbs, fat)
* Individual meal item analysis
* Meal type context
* Recent history
* A styled daily dashboard shell you can replace with real aggregation logic
It's a great example of turning raw model output into something that feels useful to a normal person.
## What Powers It
* A **vision-capable model** for image understanding
* **Schema-based structured output** -- the AI returns typed data, not free-form text
* **Object storage** for uploaded meal photos
* **Better Auth + PostgreSQL** for auth and saved analyses
* The **shared credit system**
The tool configuration brands the app as **CalorieVision** and uses a vision model from the shared AI layer.
## What You Need
Auth, saved analyses, and account data
Default vision-model path for this app
Uploaded meal photos
## How It Works Under the Hood
The user uploads a food photo.
The file goes to object storage.
The image plus a structured prompt are sent to the selected vision model.
The model returns typed nutrition data that matches the defined schema -- not random text.
The result is saved so the user can review it later.
The UI renders the analysis in a clean, product-friendly format.
## The Schema Pattern
This is one of the most reusable patterns in the repo. The app defines a schema for the expected output, and the AI is forced to return data that matches it. Here are the key files:
* `app/(apps)/vision/schema.ts` -- defines what the AI must return
* `app/(apps)/vision/prompt.ts` -- tells the AI how to analyze the image
* `app/(apps)/vision/toolConfig.ts` -- app configuration and model selection
## Great Starting Point For...
Even if you don't want to build a calorie tracker, this app gives you a reusable pattern:
**Upload image -> Run vision analysis -> Force structured output -> Store results -> Show polished UI**
That pattern works for:
* Skincare analysis
* Receipt parsing
* Product identification
* Packaging analysis
* Inspection workflows
* Any "upload image, get structured answer" product
## Files to Customize
Turn this into your own vision app by editing:
* `app/(apps)/vision/toolConfig.ts`
* `app/(apps)/vision/prompt.ts`
* `app/(apps)/vision/schema.ts`
* `app/(apps)/vision/components/*`
The prompt and schema define what the AI returns. The components define how that data looks in your product.
This app is a product demo, not medical advice. The calorie and macro estimates are approximate AI outputs, not clinically reliable nutrition measurements.
# Voice Studio
Source: https://docs.anotherwrapper.com/apps/voice
Generate speech, voice transforms, music, and sound effects
Voice Studio is one of the widest feature demos in the repo. It shows how one product can wrap a provider like ElevenLabs into **several useful user-facing tools** instead of exposing a single narrow endpoint.
## What Your Users Can Do
* Turn text into speech with multiple voice options
* Browse available voices
* Transform one voice recording into another voice
* Generate sound effects from text
* Generate music from text prompts
One product shell, five audio workflows. That's the power of this app.
## Voice Modes
Type text, pick a voice, and generate speech. The app supports multiple ElevenLabs models:
* `eleven_v3`
* `eleven_multilingual_v2`
* `eleven_flash_v2_5`
* `eleven_turbo_v2_5`
It also supports **emotional prompt tags**, which makes the output feel way more natural and productized than a raw text box.
Upload an audio recording and transform it into a different voice. Great for dubbing, content repurposing, or just having fun.
Describe the music you want in text and let ElevenLabs generate it. Useful for jingles, background music, and creative projects.
Generate sound effects from text descriptions. Think: "glass breaking," "rain on a tin roof," or "spaceship engine startup."
## API Surface
The app exposes five routes:
* `voices` -- browse available voice options
* `text-to-speech` -- generate speech from text
* `speech-to-speech` -- transform one voice into another
* `music` -- generate music from prompts
* `sound-effects` -- generate SFX from descriptions
## What You Need
Auth and generation history
Saved audio outputs
Powers the entire voice generation stack
## How It Works
The user selects a tab -- TTS, voice transform, music, or sound effects.
Depending on the mode: type text, upload audio, or describe what you want.
The app sends the request to ElevenLabs with the right parameters for the selected mode.
The generated audio file is uploaded to object storage.
The result is saved so the user can replay it, download it, or come back to it later.
## Great Starting Point For...
* Voiceover tools
* Podcast utility products
* Sound design generators
* AI jingle or music tools
* Dubbing or accessibility workflows
The boring product pieces -- auth, credits, saved outputs, playback history -- are already handled for you.
## Good First Customizations
* Which tabs are visible
* Default ElevenLabs model choices
* Credit pricing
* Prompt presets
* Branding and copy
## Verification
Your setup is working if:
* Voices load from ElevenLabs
* Text-to-speech returns playable audio
* Sound effects and music generation both save correctly
* Speech-to-speech works with an uploaded sample
# Documentation Workflow
Source: https://docs.anotherwrapper.com/codebase/documentation
How docs are organized and where different types of content belong
This repo has two documentation layers, each with a clear purpose. Here's how they work.
## Two Layers, One Repo
**`docs/public/`** -- The Mintlify docs site your users see. Setup guides, feature docs, provider docs, and troubleshooting.
**`docs/architecture/`** -- Notes for maintainers only. Implementation details, refactor plans, internal tradeoffs.
## What Goes Where
Put these here:
* Setup and getting-started guides
* Provider configuration docs
* Product feature documentation
* Customer-safe architecture explanations
* Troubleshooting and FAQ content
Put these here:
* Implementation notes and technical decisions
* Refactor plans and migration strategies
* Internal tradeoffs and design reasoning
* Maintainer-only concerns
* Anything you wouldn't want publicly visible
## Navigation
The Mintlify site structure is controlled by `docs/public/mint.json`. If you add a new page but forget to add it there, your page will exist but won't show up in the docs navigation.
## Writing Style
When writing public docs, keep these principles in mind:
* **Assume your reader isn't deeply technical** -- explain what something is before diving into env vars
* **Explain the "why"**, not just the "how" -- help readers understand what matters
* **Stay accurate to the current codebase** -- outdated docs are worse than no docs
* **Talk to the reader directly** -- use "you" and keep it conversational
This matters especially for AnotherWrapper because many buyers want to understand the product before they dig into the implementation. Meet them where they are.
# Local Development
Source: https://docs.anotherwrapper.com/codebase/local-development
Get your local dev environment up and running
Local development is fully supported and straightforward. You'll be running the app on your machine in just a few minutes.
## Quick Start
You need:
* **Node.js 20.9+**
* **pnpm** as your package manager
* A **PostgreSQL database** (local or hosted -- see below)
```bash theme={null}
pnpm bootstrap
```
This single command does a lot for you: it checks your Node version, runs `pnpm install`, walks you through setting your env vars, runs `pnpm db:migrate` as soon as your `DATABASE_URL` is confirmed, and then saves the rest of your config.
```bash theme={null}
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) and you're in business.
## Database Setup
**Supabase PostgreSQL** is the recommended default for both local dev and production. It's free to start and works great with this stack.
Other PostgreSQL hosts that work well: Neon, Railway, Render, Fly, or self-hosted Postgres. Just make sure they support the extensions your enabled features need.
### Database Workflow
Your day-to-day database commands:
```bash Apply migrations theme={null}
pnpm db:migrate
```
```bash Generate new migration (after schema edits) theme={null}
pnpm db:generate
```
* **Schema source of truth:** `lib/db/schema/*`
* **Migration history:** `drizzle/*`
The typical flow: edit your schema files in `lib/db/schema/`, then run `pnpm db:generate` to create the SQL migration, then `pnpm db:migrate` to apply it.
## What You Need for Each Feature
Not every feature works with zero config. Here's what different features need:
* `DATABASE_URL` -- Your PostgreSQL connection string
* `BETTER_AUTH_SECRET` -- For session encryption
* At least one LLM provider key (OpenAI, Anthropic, Google, etc.)
* Storage provider config (for file uploads)
* `OPENAI_API_KEY` (for embeddings)
* A Postgres host that supports `pgvector`
* `ELEVENLABS_API_TOKEN`
* Storage provider config (generated audio is persisted)
* `REPLICATE_API_TOKEN`
* Storage provider config (source images and final videos are persisted)
* Storage provider config (generated images are persisted)
* `REPLICATE_API_TOKEN` for Replicate-backed models
* `OPENAI_API_KEY` for GPT Image
* PostHog, Plausible, or DataFast credentials
* Polar, Stripe, or LemonSqueezy keys and webhook secrets
`pnpm bootstrap` can collect the storage settings too, so you do not need to hand-wire upload/media env vars up front unless you're doing a manual setup.
## The Local-First Reality
"The app boots locally" does not mean "every feature works locally." Many features depend on third-party APIs, so you'll need the right provider keys for whatever you're testing.
What runs locally:
* Next.js dev server
* Your PostgreSQL database (local or hosted)
* All UI and routing
What needs external services:
* LLM providers (OpenAI, Anthropic, Google, etc.)
* Storage (for file uploads)
* Voice, video, and some image generation
* Email sending
* Payment processing
* Analytics tracking
For most teams, the smoothest path is **local Next.js + hosted Supabase PostgreSQL + hosted provider APIs** for whatever features you're actively working on. You don't need to configure everything at once -- just add provider keys as you need them.
# Codebase Overview
Source: https://docs.anotherwrapper.com/codebase/overview
The big picture of how the repo is organized so you always edit the right layer
AnotherWrapper Premium is a **single Next.js app**, not a monorepo. This page helps you build a mental model of the codebase so you can find the right file quickly and avoid editing the wrong layer.
## The Four Main Surfaces
Your app is organized around four key areas:
`app/landing/` -- The public-facing landing page that sells your product.
`app/(apps)/` -- The AI-powered apps your users interact with after signing in.
`app/page.tsx` -- The canonical home/dashboard route with account info, credits, and navigation.
`app/api/` -- Shared backend routes for auth, avatars, OG images, and payment webhooks.
## Shared Layers
Everything that's reused across your app lives in these shared directories:
All your React components. Base primitives live in `components/ui/`, marketing components in `components/landing/`, and app-specific UI in `components/(apps)/`.
Model configuration, provider setup, and shared AI helpers. This is where you configure which models are available and how they behave.
Server-side auth helpers. The canonical entry point is `lib/auth/server.ts`.
Domain-first database modules. The Drizzle client, schema definitions, shared types, and feature-scoped queries all live here.
Server actions that sit above the database layer for form handling and orchestration.
Provider-agnostic billing logic for Polar, Stripe, and LemonSqueezy.
Product analytics, contact sync, object storage, and document chunking/embeddings/retrieval.
Small cross-domain helpers, thin vendor SDK wrappers, static registries, and Sentry/monitoring.
## Where to Edit Common Things
* `app/landing/page.tsx` -- The main landing page
* `components/landing/*` -- Landing page components
* `config.ts` -- Site-wide metadata and config
Each app has its own folder under `app/(apps)/`:
* `app/(apps)/chat` -- AI chat
* `app/(apps)/image-studio` -- Image generation
* `app/(apps)/video-studio` -- Video generation
* `app/(apps)/voice` -- Voice synthesis
* `app/(apps)/audio` -- Audio processing
* `app/(apps)/vision` -- Image analysis
* `app/(apps)/marketing-plan` -- Marketing plan generator
* `app/(apps)/launch-simulator` -- Launch simulator
* `lib/db/schema/*` -- Schema source of truth
* `lib/db/client.ts` -- Drizzle client
* `lib/db/types/*` -- Shared database-derived types
* `lib/db//*` -- Feature-scoped queries and mutations
* `lib/db/{cache,client-args,mutation,results}.ts` -- Small shared DB helpers
* `drizzle/*` -- Generated and custom SQL migrations
* `lib/actions/*` -- Server actions above the DB layer
* `docs/public/*` -- Public Mintlify docs (what you're reading now)
* `docs/architecture/*` -- Internal architecture notes
## The Golden Rule
When you want to change something, ask yourself three questions:
1. **Is this marketing-only?** Edit `app/landing/` or `components/landing/`
2. **Is this one app only?** Edit `app/(apps)/your-app/`
3. **Is this shared across apps?** Edit `lib/`
That question almost always tells you exactly where to go.
# Configuration
Source: https://docs.anotherwrapper.com/configuration
All the knobs and levers you can turn — env vars, config files, and app settings
AnotherWrapper doesn't use one giant config file. Instead, configuration is spread across four layers — each with a clear purpose. Here's the full map.
Don't try to configure everything on day one. Start with `pnpm bootstrap`, get the app running, then add features one at a time.
## The four config layers
`.env.example` is the source of truth. `pnpm bootstrap` generates a working `.env.local` from it.
This is where you configure:
* Auth (Better Auth secret, URLs)
* Database (`DATABASE_URL`)
* AI provider keys
* Payments
* Email
* Analytics
* Storage
* Meta attribution
* Sentry
Two surfaces to know:
| What | Where |
| ---------------------- | ----------------- |
| Schema source of truth | `lib/db/schema/*` |
| Migration history | `drizzle/*` |
Edit the schema files, then run `pnpm db:generate` to create a migration.
`config.ts` defines global constants:
* Site URLs and names
* Support email
* Route helpers
* Marketing and app links
This is where you rebrand the app.
Each product app has a `toolConfig.ts` with:
* App title and metadata
* Credits cost
* Default model
* Tool path and description
Edit these to customize individual apps.
## Env var reference
These are the bare minimum to run the app:
```env theme={null}
NEXT_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_SECRET=...
DATABASE_URL=postgresql://...
```
Plus at least one AI provider:
```env OpenAI theme={null}
OPENAI_API_KEY=sk-...
```
```env Google AI theme={null}
GOOGLE_GENERATIVE_AI_API_KEY=...
```
```env Anthropic theme={null}
ANTHROPIC_API_KEY=sk-ant-...
```
Email/password works with just `BETTER_AUTH_SECRET`.
For magic links and forgot password, add:
```env theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=resend
EMAIL_PROVIDER=resend
AUTH_EMAIL_FROM="YourApp "
```
`NEXT_PUBLIC_EMAIL_PROVIDER` controls whether the auth UI shows magic-link and reset-password modes. `EMAIL_PROVIDER` is the server-side provider selection.
For Google OAuth:
```env theme={null}
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true
```
For Loops auth-email templates:
```env theme={null}
LOOPS_AUTH_MAGIC_LINK_TRANSACTIONAL_ID=...
LOOPS_AUTH_RESET_PASSWORD_TRANSACTIONAL_ID=...
```
```env theme={null}
DATABASE_URL=postgresql://...
```
Best practices:
* Use `pnpm db:migrate` to apply schema changes
* Use `pnpm db:generate` to create SQL after schema edits
* Keep custom SQL migrations small and focused
You only need the providers you plan to use:
```env theme={null}
OPENAI_API_KEY=...
ANTHROPIC_API_KEY=...
GOOGLE_GENERATIVE_AI_API_KEY=...
GROQ_API_KEY=...
XAI_API_KEY=...
DEEPSEEK_API_KEY=...
REPLICATE_API_TOKEN=...
ELEVENLABS_API_TOKEN=...
```
PDF RAG requires OpenAI for embeddings. Image/video generation often needs Replicate. Voice needs ElevenLabs.
Required for uploads, file-backed workflows, and generated assets:
```env theme={null}
STORAGE_REGION=...
STORAGE_ACCESS_KEY=...
STORAGE_SECRET_KEY=...
STORAGE_ENDPOINT=...
STORAGE_BUCKET=...
STORAGE_PUBLIC_URL=...
```
Without storage, these features won't work: chat file upload, PDF flows, image/video/voice outputs, vision uploads.
```env theme={null}
NEXT_PUBLIC_PAYMENT_PROVIDER=stripe
NEXT_PUBLIC_DEFAULT_MARKETING_PURCHASE_TYPE=plan-medium
NEXT_PUBLIC_CHECKOUT_URL_TEMPLATE=...
```
Plus the secret for your chosen provider:
* `STRIPE_SECRET_KEY` + `STRIPE_WEBHOOK_SECRET`
* `LEMON_SQUEEZY_WEBHOOK_SECRET`
* `POLAR_WEBHOOK_SECRET`
```env theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=none
EMAIL_PROVIDER=none
```
Switch to `loops`, `resend`, or `brevo` when you want auth emails or contact sync. Then add the matching key:
* `LOOPS_API_KEY`
* `RESEND_API_KEY`
* `BREVO_API_KEY`
And for auth emails: `AUTH_EMAIL_FROM` (required) and `AUTH_EMAIL_REPLY_TO` (optional).
One or more providers, comma-separated:
```env theme={null}
NEXT_PUBLIC_ANALYTICS_PROVIDER=posthog,datafast
```
Then configure the specific provider env vars. See [Analytics](/services/analytics) for details.
Optional — only if you run Meta Ads:
```env theme={null}
NEXT_PUBLIC_ENABLE_META_ATTRIBUTION=false
NEXT_PUBLIC_META_PIXEL_ID=
META_ACCESS_TOKEN=
```
```env theme={null}
NEXT_PUBLIC_SENTRY_DSN=...
```
Server-side overrides and tracing are optional.
## Recommended setup order
Don't try to fill every env var at once. Go in this order:
Let the wizard handle the initial setup.
The absolute minimum to run the app.
OpenAI, Anthropic, or Google — just pick one to start.
If you want image and video generation early.
When you're ready to charge money.
When you want magic links, password resets, or contact sync.
When you want to understand user behavior.
Meta attribution, Sentry, additional AI providers — add as needed.
* Using `.env` instead of `.env.local`
* Missing `BETTER_AUTH_SECRET`
* Forgetting `DATABASE_URL`
* Enabling email auth flows without `AUTH_EMAIL_FROM`
* Enabling provider features without the matching API key
* Expecting uploads to work without storage configured
* Setting payment env vars but forgetting checkout URLs
* Editing generated SQL by hand instead of updating `lib/db/schema/*`
* Forgetting that `NEXT_PUBLIC_APP_URL` affects auth redirects
Follow the setup guide first.
Something not working? Check here.
Ready to ship? Verify everything.
# Database Overview
Source: https://docs.anotherwrapper.com/database/overview
Understand the main tables, Drizzle schema, and data responsibilities
The database isn't just there for auth. It stores chats, generations, purchases, credits, recordings, documents, embeddings, and more. It's the backbone of every app in the repo.
## The Mental Model
Here's how to think about the data layer:
* **Better Auth** owns the auth tables and browser session flow
* **PostgreSQL + Drizzle** own the application data layer
* **Supabase PostgreSQL** is the recommended managed host
Application queries run through Drizzle, while authentication lives inside the app through Better Auth -- not through provider-specific database primitives.
## Schema Workflow
Define tables, indexes, and relationships in `lib/db/schema/*`.
Run `pnpm db:generate` to produce migration files from your schema changes.
Run `pnpm db:migrate` to apply the generated SQL to your database.
The `drizzle/*` folder is the SQL history produced by this workflow. The current baseline starts with `drizzle/0000_better_auth_baseline.sql` and `drizzle/0001_rag_functions.sql`.
## Table Families
**Schema files:** `lib/db/schema/auth.ts`, `lib/db/schema/profiles.ts`
Handles:
* Better Auth users, sessions, accounts, and verifications
* User profile data
* Credit tracking
* Connecting app-owned records to the authenticated user ID
**Schema file:** `lib/db/schema/generations.ts`
Handles:
* Outputs from AI apps (images, videos, audio, structured data)
* Input/output payloads
* Reusable generation history across multiple apps
**Schema file:** `lib/db/schema/chat.ts`
Handles:
* Chat conversations
* Messages within conversations
* Chat document versioning
**Schema file:** `lib/db/schema/pdf.ts`
Handles:
* Uploaded PDFs
* Embeddings (via `pgvector`)
* Document-to-chat links
* Similarity search functions
**Schema file:** `lib/db/schema/audio.ts`
Handles:
* Recordings
* Transcripts
* AI-generated summaries
**Schema file:** `lib/db/schema/purchases.ts`
Handles:
* Purchase records
* Provider metadata
* Credit packs and plan purchases
## Important Database Behaviors
### Ownership Checks
Most user-owned data access is enforced through authenticated server helpers plus user-scoped queries inside the `lib/db/*` modules. Records are filtered or updated by the current user ID -- not by database-vendor-specific auth helpers.
### Credits
Credit logic isn't just a frontend number. The repo uses **transactional update queries** and ownership checks in the database layer to keep credit changes safe.
### Vector Search
The PDF system uses `pgvector` plus retrieval functions so the chat app can do document-aware answers with citations. The extension setup and retrieval function live in focused custom Drizzle migrations.
Important details about `pgvector`:
* It's enabled at the **database level**, not per-table
* Once the extension exists, any table can define `vector(...)` columns
* Your database host still needs to support the extension
## Good First Files to Read
* `lib/db/schema/auth.ts`
* `lib/db/schema/profiles.ts`
* `lib/db/schema/generations.ts`
* `lib/db/schema/chat.ts`
* `lib/db/schema/pdf.ts`
* `lib/db/schema/audio.ts`
* `lib/db/schema/purchases.ts`
* `drizzle/0000_better_auth_baseline.sql`
* `drizzle/0001_rag_functions.sql`
When customizing the product, understanding these table families helps you avoid a common mistake: bolting new features onto random existing tables instead of working with the repo's actual data model.
Learn how auth and persistence fit together in the default setup.
Learn how embeddings and retrieval use the database.
# Welcome
Source: https://docs.anotherwrapper.com/index
Your complete AI SaaS starter — from first clone to first customer
You just got a **complete AI product**, not a blank starter template. Marketing site, 8 AI apps, auth, payments, analytics — it's all here, wired up and ready to customize.
This guide will walk you through everything. No guesswork needed.
## Your journey
Here's the path from "just bought it" to "shipping to real users." Follow it top to bottom, or jump to wherever you are.
Clone, run the setup wizard, and open your app locally. Takes about 5 minutes.
[Start the setup](/setup/introduction)
You've got 8 production-ready AI apps. See what they do, pick the ones you want to keep, and start customizing.
[Browse the apps](/apps/index)
Configure the services that power your product — auth, storage, AI SDK, credits, email.
[Platform setup](/services/index)
Add API keys for the AI providers you want: OpenAI, Anthropic, Google, and more.
[Pick your providers](/providers/index)
Set up Stripe, LemonSqueezy, or Polar. Add checkout links and webhooks.
[Set up payments](/payments/index)
Deploy to Vercel, run the launch checklist, and go live.
[Deploy & launch](/setup/deployment)
## What's in the box
Chat with multi-model support, image studio, video studio, voice studio, audio transcription, vision analysis, marketing plan generator, and launch simulator. Not demos — real product patterns you can ship.
Auth (Better Auth), PostgreSQL + Drizzle, S3 storage, credits system, email (Loops/Resend/Brevo), analytics (PostHog/Plausible/DataFast), and three payment providers.
Landing page, pricing, blog with MDX, SEO with dynamic OG images, and optional Meta Ads attribution. Everything you need to sell your product.
OpenAI, Anthropic, Google, Groq, xAI, DeepSeek, Replicate, and ElevenLabs — all wired through the Vercel AI SDK so you can swap models without rewriting code.
## The apps you just got
This is the fun part. Here's a quick look at what ships with the repo:
Multi-model chat with streaming, PDF chat, web search, and generative UI.
Generate images with 9+ models, gallery view, and download.
Text-to-video and image-to-video with async polling.
Text-to-speech, voice cloning, music, and sound effects.
Record audio, transcribe, and get structured summaries.
Upload a photo, get structured analysis back.
AI-generated growth strategy from a simple form.
Simulate a Product Hunt launch with AI drama.
## Quick links
Get running locally right now.
All the env vars and config knobs.
Turn one of the included apps into your own product.
Something not working? Quick fixes for common issues.
Everything to verify before real users arrive.
# Launch Checklist
Source: https://docs.anotherwrapper.com/launch-checklist
Everything to verify before your first real users arrive
You're about to launch. This checklist makes sure nothing slips through the cracks. It's not about deploying code — it's about making sure the **whole product** is ready for real users.
Go through each section. If something doesn't apply to you (e.g., you don't use Meta Ads), skip it. But don't skip the final smoke test at the bottom — that's the one that catches the most issues.
## Pre-launch checklist
Make sure you've replaced all the default branding:
Logos and favicons updated
Landing page headline and CTA copy customized
Pricing copy reflects your actual plans
Support email updated
Social links point to your accounts
App names and descriptions match your brand (if rebranding)
`NEXT_PUBLIC_APP_URL` set to your production domain
`BETTER_AUTH_URL` updated if you use the override
Live site domain is configured and resolving
If the base URLs are wrong, auth and public links will quietly break. Double-check these.
Email/password login works
Magic link works (if enabled)
Forgot-password emails arrive (if enabled)
Google login works (if enabled)
Google OAuth redirect includes `/api/auth/callback/google`
Post-login navigation goes where you expect
Correct payment provider is enabled
Production checkout URLs are set (not test)
Webhook secret is production, not test
Test purchases are recorded correctly
Credits top-ups work if you sell credits
Chosen email provider is configured
Auth emails actually arrive
Sender identity and domain look correct
Contact sync behavior matches expectations
Analytics providers are receiving events
Pageviews tracked on your real domain
Meta attribution only enabled if you use Meta Ads
Don't assume everything works because one AI feature works. Test each one you've enabled:
Chat works with your configured models
Image generation works
Video generation works
Voice generation works
Structured output apps work (Marketing Plan, Launch Simulator)
Vision works
A missing API key can break one app while leaving others fine. Test each app individually.
Confirm uploads and saved assets work for:
PDFs
Images
Videos
Voice/audio files
Vision image uploads
Page titles and descriptions are customized
OG images generate correctly
Sitemap is accessible and valid
Privacy and terms pages exist
Landing page screenshots and feature claims are accurate
Mintlify points at `docs/public`
Branding in docs matches your product
Setup steps match the current repo
No misleading old feature claims remain
## Final smoke test
This is the most important part. Walk through the full user journey yourself:
Does it load? Does the copy make sense? Do the CTAs work?
Go through the real sign-up flow. Don't use an existing account.
Open your primary AI app and actually use it. Send a message, generate something, upload a file.
Complete a real (or test) purchase. Does the checkout flow work end-to-end?
Check that credits or plan state updated correctly in the dashboard.
Did analytics fire? Did any email side effects trigger? Is everything consistent?
This 6-step walkthrough catches far more issues than staring at code. If everything above works, you're ready to ship.
Need to deploy first? Here's the Vercel guide.
Something's not right? Check the common fixes.
# Payments
Source: https://docs.anotherwrapper.com/payments/index
Beginner-friendly guide to checkout links, webhooks, and credits purchases
Think of the payment system as two simple parts: a **checkout link** that sends your user to pay, and a **webhook** that tells your app "hey, the payment went through!"
That's really it. Let's break it down.
## What payments do in your app
You'll use payments for two things:
* **Selling plan tiers** -- like Small, Medium, or Large one-time purchases
* **Selling credit top-ups** -- like `credits-small` and `credits-large`
Here's the full flow, start to finish:
They hit a payment button in your app.
Stripe, LemonSqueezy, or Polar handles the actual payment.
Your app gets a secure HTTP request saying "payment succeeded."
The webhook is verified, the purchase is saved, and credits or plan state are updated.
## Pick your payment provider
The most popular choice for developers. You get full control over products, prices, and metadata. If you've used Stripe before, you'll feel right at home.
**Best for:** Teams who want the most familiar developer payment flow.
[Set up Stripe ->](/payments/stripe)
Super simple hosted product flow. Everything is product/variant oriented out of the box -- great if you want minimal setup.
**Best for:** Solo builders who want a quick, hosted checkout experience.
[Set up LemonSqueezy ->](/payments/lemonsqueezy)
Clean product-based checkout with solid metadata support. A modern alternative that's easy to work with.
**Best for:** Builders who want a clean, product-based checkout flow.
[Set up Polar ->](/payments/polar)
Don't try to set up all three providers at once. Pick one, get it fully working, then expand later if you need to.
## Key concepts
### Checkout URL
This is the payment link your user clicks. You store these as env vars so your UI can use them without hardcoding.
```text theme={null}
https://buy.stripe.com/...
https://yourstore.lemonsqueezy.com/buy/...
https://polar.sh/checkout/...
```
### Webhook
A webhook is a secure HTTP request your payment provider sends to your app after a successful payment. Each provider has its own endpoint:
* **Stripe:** `https://yourdomain.com/api/payments/stripe`
* **LemonSqueezy:** `https://yourdomain.com/api/payments/lemonsqueezy`
* **Polar:** `https://yourdomain.com/api/payments/polar`
### Purchase type
This is the internal label your app uses to know what was bought. The repo supports:
`plan-small` | `plan-medium` | `plan-large` | `credits-small` | `credits-large`
Your payment provider might call it a "product", "variant", or "price" -- but inside this app, it always maps to one of those purchase types.
## Before you start
Make sure you have these ready:
* Your app runs locally
* Better Auth and your database are set up
* You know your production domain
* You've picked which provider to use first
## Shared env vars
These env vars are used regardless of which provider you choose:
```env Required theme={null}
NEXT_PUBLIC_PAYMENT_PROVIDER=stripe
NEXT_PUBLIC_DEFAULT_MARKETING_PURCHASE_TYPE=plan-medium
NEXT_PUBLIC_CHECKOUT_URL_TEMPLATE=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_LARGE=https://...
```
```env Optional theme={null}
NEXT_PUBLIC_CHECKOUT_URL_PLAN_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_PLAN_MEDIUM=https://...
NEXT_PUBLIC_CHECKOUT_URL_PLAN_LARGE=https://...
NEXT_PUBLIC_AFFILIATES_URL=https://...
```
| Variable | What it does |
| --------------------------------------------- | -------------------------------------------- |
| `NEXT_PUBLIC_PAYMENT_PROVIDER` | Which provider the UI should use |
| `NEXT_PUBLIC_DEFAULT_MARKETING_PURCHASE_TYPE` | Which plan the main marketing CTA represents |
| `NEXT_PUBLIC_CHECKOUT_URL_TEMPLATE` | Fallback main checkout URL |
| `NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL` | Checkout link for the small credit pack |
| `NEXT_PUBLIC_CHECKOUT_URL_CREDITS_LARGE` | Checkout link for the large credit pack |
Setting the specific Small/Medium/Large plan URLs gives you cleaner pricing buttons on your marketing pages.
## How the repo handles payments
Here are the key files you should know about:
| File | Purpose |
| ------------------------------------------------------- | ---------------------------------------------------------- |
| `lib/payments/public-config.ts` | Shared checkout URLs, labels, prices, credit-pack metadata |
| `components/(ui-components)/payments/checkout-link.tsx` | Hosted checkout buttons in the UI |
| `app/api/payments/[provider]/route.ts` | Shared webhook entrypoint |
| `lib/payments/providers/*` | Provider-specific verification logic |
| `lib/payments/processor.ts` | Shared "payment succeeded" business logic |
The UI and business logic are mostly shared. Only the webhook verification differs per provider.
Real checkout URLs should live in env vars. Shared purchase display metadata lives in `lib/payments/public-config.ts`. UI components should never hardcode raw checkout links.
## Metadata vs product maps
There are two ways to tell your app what a payment means.
Store a value like `type=plan-medium` directly in the payment provider. This is the cleanest approach because the purchase explains itself -- no extra configuration needed.
Keep a JSON map in your env vars:
```env theme={null}
STRIPE_PRODUCT_MAP='{"prod_123":"plan-medium"}'
POLAR_PRODUCT_MAP='{"prod_123":"credits-small"}'
LEMON_SQUEEZY_VARIANT_MAP='{"123456":"credits-large"}'
```
This is useful if you want to change mappings without touching provider settings.
## Credits
If the purchase type is `credits-small` or `credits-large`, the repo adds credits automatically after the webhook succeeds.
If it's a plan like `plan-medium`, the repo stores the purchase and updates the user's profile state instead.
## Optional Meta Ads attribution
If you run Meta Ads, the repo can track `InitiateCheckout` and `Purchase` events. Only enable this if you actually need it:
```env theme={null}
NEXT_PUBLIC_ENABLE_META_ATTRIBUTION=true
NEXT_PUBLIC_META_PIXEL_ID=...
META_ACCESS_TOKEN=...
```
## Suggested setup order
Start with Stripe, LemonSqueezy, or Polar -- just one.
Keep it simple. One product to start.
This lets you test the credits flow too.
Paste your payment links into the right env vars.
Configure it in your provider's dashboard.
Go through the full flow yourself.
Check that a row appeared in `purchases`.
Credits added? Plan state updated? You're golden.
Double-check that the URL in your provider dashboard matches your actual domain, including the correct path (`/api/payments/stripe`, `/api/payments/lemonsqueezy`, or `/api/payments/polar`).
Each provider needs its own webhook secret env var. Without it, webhook verification fails silently.
You probably created checkout links but never connected the webhook. The webhook is what tells your app the payment happened.
Make sure your product is mapped -- either via metadata (`type=plan-medium`) or via the product/variant map in env.
You need to restart your dev server after changing env vars. Next.js doesn't hot-reload env changes.
## Provider guides
The most common developer payment flow.
Simple hosted product + variant workflow.
Clean product-based checkout with metadata support.
Optionally track checkout clicks and purchases for Meta Ads.
How credits work in the dashboard and across apps.
# LemonSqueezy
Source: https://docs.anotherwrapper.com/payments/lemonsqueezy
Step-by-step guide to setting up LemonSqueezy checkout and webhooks
LemonSqueezy is often the easiest path to accepting payments -- it's super product/variant oriented out of the box, which means less configuration for you. Let's get it running.
## What you're setting up
Four things to make LemonSqueezy work:
1. Products and variants in LemonSqueezy
2. Checkout URLs for your app buttons
3. A webhook endpoint
4. A variant-to-purchase-type mapping
## Env vars
```env Required theme={null}
NEXT_PUBLIC_PAYMENT_PROVIDER=lemonsqueezy
NEXT_PUBLIC_CHECKOUT_URL_TEMPLATE=https://...
LEMON_SQUEEZY_WEBHOOK_SECRET=...
```
```env Optional checkout URLs theme={null}
NEXT_PUBLIC_CHECKOUT_URL_PLAN_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_PLAN_MEDIUM=https://...
NEXT_PUBLIC_CHECKOUT_URL_PLAN_LARGE=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_LARGE=https://...
```
```env Optional variant map theme={null}
LEMON_SQUEEZY_VARIANT_MAP='{"123456":"plan-medium"}'
```
## Setup
Head to your [LemonSqueezy dashboard](https://app.lemonsqueezy.com/) and create the products you want to sell.
Create variants if needed, and decide which ones represent:
* Small / Medium / Large plans
* Small / Large credit packs
In LemonSqueezy, the **variant ID** is the important identifier this repo uses to map purchases. Keep track of those IDs.
Create hosted checkout links for each product you want to sell, then paste them into `.env.local`:
```env theme={null}
NEXT_PUBLIC_CHECKOUT_URL_PLAN_MEDIUM=https://yourstore.lemonsqueezy.com/buy/...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://yourstore.lemonsqueezy.com/buy/...
```
The repo resolves LemonSqueezy purchases by variant ID. Tell it what each variant means:
```env theme={null}
LEMON_SQUEEZY_VARIANT_MAP='{"123456":"plan-medium","123457":"credits-small"}'
```
This is how your app knows that variant `123456` is a medium plan purchase and variant `123457` is a small credits pack.
In your LemonSqueezy webhook settings, add this endpoint:
```text theme={null}
https://yourdomain.com/api/payments/lemonsqueezy
```
Then copy the webhook secret into your env:
```env theme={null}
LEMON_SQUEEZY_WEBHOOK_SECRET=...
```
Without the webhook configured, payments succeed in LemonSqueezy but your app will never know about them. Credits won't be added and purchases won't be stored.
## How it works under the hood
Once LemonSqueezy sends a paid order webhook:
1. The repo verifies the webhook signature
2. It reads the variant ID from the event
3. It maps that variant to a purchase type via `LEMON_SQUEEZY_VARIANT_MAP`
4. It stores the purchase in the database
5. It updates credits or purchase state
If the variant ID is unknown (not in your map), the purchase is safely ignored rather than applying the wrong result. No data gets corrupted.
## Verify your setup
**Your LemonSqueezy setup is working if all of these are true:**
* The app opens the LemonSqueezy checkout page
* LemonSqueezy shows a successful webhook delivery
* A row appears in your `purchases` table
* Credits or plan state update correctly
You probably created the product in LemonSqueezy but forgot to save the checkout URL in your `.env.local` file. The app needs those URLs to build payment buttons.
Double-check that `LEMON_SQUEEZY_WEBHOOK_SECRET` matches what's in your LemonSqueezy webhook settings. A typo or stale value will cause silent failures.
Check that the variant ID in `LEMON_SQUEEZY_VARIANT_MAP` matches the actual variant ID from LemonSqueezy. You can find this in the product settings.
Make sure your webhook URL points to the right environment. If you're testing locally, it should point to your local tunnel. In production, it should point to your production domain.
Go back to the shared payment architecture and basics.
# Polar
Source: https://docs.anotherwrapper.com/payments/polar
Step-by-step guide to setting up Polar checkout and webhooks
Polar gives you a clean, modern checkout experience with great metadata support. The setup is straightforward -- users pay on Polar's checkout page, then Polar tells your app what happened through a webhook. Let's wire it up.
## What you're setting up
Four things to make Polar work:
1. Products inside Polar
2. Checkout links your users can click
3. A webhook endpoint Polar can call
4. A way for the repo to know what each product means
## Env vars
```env Required theme={null}
NEXT_PUBLIC_PAYMENT_PROVIDER=polar
NEXT_PUBLIC_CHECKOUT_URL_TEMPLATE=https://...
POLAR_WEBHOOK_SECRET=...
```
```env Optional checkout URLs theme={null}
NEXT_PUBLIC_CHECKOUT_URL_PLAN_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_PLAN_MEDIUM=https://...
NEXT_PUBLIC_CHECKOUT_URL_PLAN_LARGE=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_LARGE=https://...
```
```env Optional product map theme={null}
POLAR_PRODUCT_MAP='{"prod_123":"plan-medium","prod_456":"credits-small"}'
```
## Setup
Head to your [Polar dashboard](https://polar.sh/) and create the products you want to sell.
For your first setup, keep it simple:
* One **Medium plan** product
* One **Small credits** product
Each product will need to map to a repo purchase type like `plan-medium` or `credits-small`.
Once your products are ready, create hosted checkout links in Polar and add them to `.env.local`:
```env theme={null}
NEXT_PUBLIC_CHECKOUT_URL_PLAN_MEDIUM=https://polar.sh/checkout/...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://polar.sh/checkout/...
```
These are the links your app will use for payment buttons.
You have two ways to do this. Pick whichever you prefer.
Add a `type` field to your Polar product or order metadata:
```text theme={null}
type=plan-medium
```
The repo reads this directly. It's the cleanest approach because the payment already says what it is.
Keep the mapping in your env vars:
```env theme={null}
POLAR_PRODUCT_MAP='{"prod_123":"plan-medium","prod_456":"credits-small"}'
```
This tells the repo which purchase type belongs to each Polar product.
In your Polar settings, add this webhook endpoint:
```text theme={null}
https://yourdomain.com/api/payments/polar
```
Then copy the webhook secret into your env:
```env theme={null}
POLAR_WEBHOOK_SECRET=...
```
If the webhook isn't configured, checkout may succeed in Polar but your app will never know about it. That means credits won't be added and purchases won't be stored.
## How it works under the hood
When Polar sends a successful webhook:
1. The repo verifies the webhook signature
2. It reads the purchase type from metadata or `POLAR_PRODUCT_MAP`
3. It stores the purchase in the `purchases` table
4. It tries to match the purchase to a user (using external customer ID if available)
5. It updates credits or purchase state
## A quick example
Say you want to sell one credits pack through Polar. Here's everything you'd set:
```env theme={null}
NEXT_PUBLIC_PAYMENT_PROVIDER=polar
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://polar.sh/checkout/...
POLAR_WEBHOOK_SECRET=...
POLAR_PRODUCT_MAP='{"prod_credits_small":"credits-small"}'
```
Now when a user buys that product:
1. The checkout button sends them to Polar
2. Polar charges them
3. Polar calls your webhook
4. The repo recognizes `credits-small`
5. The repo adds the correct number of credits
Pretty straightforward, right?
## Verify your setup
**Your Polar setup is working if all of these are true:**
* The app opens the Polar checkout page
* Polar shows a successful webhook delivery
* A row appears in your `purchases` table
* A credits purchase increases the user's credits
* A plan purchase updates the user's access state
You probably forgot to save the checkout URL in your `.env.local` file. Without it, the app can't build payment buttons.
You configured checkout but forgot the webhook. The webhook is what tells your app the payment happened -- without it, your app is in the dark.
Make sure you've mapped the Polar product to a valid purchase type, either via metadata or `POLAR_PRODUCT_MAP`. If neither is set, the repo can't determine what was bought.
Restart your dev server. Next.js doesn't hot-reload environment variable changes.
Check that the webhook URL in Polar points to your production domain, not `localhost` or a dev tunnel URL.
Go back to the shared payment architecture and basics.
# Stripe
Source: https://docs.anotherwrapper.com/payments/stripe
Step-by-step guide to setting up Stripe checkout links and webhooks
Stripe is a great default if you want the most familiar developer payment workflow with full control over products, prices, and metadata. Let's get you set up.
## What you're setting up
Four things, that's it:
1. A product or payment link in Stripe
2. A checkout URL saved in your env vars
3. A webhook endpoint in Stripe
4. A way for the repo to understand what was purchased
## Env vars
```env Required theme={null}
NEXT_PUBLIC_PAYMENT_PROVIDER=stripe
NEXT_PUBLIC_CHECKOUT_URL_TEMPLATE=https://...
STRIPE_SECRET_KEY=...
STRIPE_WEBHOOK_SECRET=...
```
```env Optional checkout URLs theme={null}
NEXT_PUBLIC_CHECKOUT_URL_PLAN_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_PLAN_MEDIUM=https://...
NEXT_PUBLIC_CHECKOUT_URL_PLAN_LARGE=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_LARGE=https://...
```
```env Optional product map theme={null}
STRIPE_PRODUCT_MAP='{"prod_123":"plan-medium"}'
```
## Setup
Head to your [Stripe Dashboard](https://dashboard.stripe.com/) and open **Product catalog**.
1. Create a product for each thing you want to sell
2. Add a price for each product
3. Create a payment link or checkout flow
For your first setup, keep it simple -- just create:
* One **Medium plan** product
* One **Small credits** product
You have two options here. Pick whichever feels more natural to you.
Add a `type` field to your product or checkout session metadata. This is the easiest to reason about because the purchase explains itself.
Examples:
* `type=plan-medium`
* `type=credits-small`
The repo will look for:
1. `session.metadata.type` first
2. `product.metadata.type` as fallback
Keep the mapping in your env vars instead:
```env theme={null}
STRIPE_PRODUCT_MAP='{"prod_123":"plan-medium","prod_456":"credits-small"}'
```
This is handy if you don't want to rely on Stripe metadata.
Paste the Stripe payment link into the matching env var in `.env.local`:
```env theme={null}
NEXT_PUBLIC_CHECKOUT_URL_PLAN_MEDIUM=https://buy.stripe.com/...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://buy.stripe.com/...
```
These are the URLs your app buttons will send users to.
Back in Stripe:
1. Go to **Developers -> Webhooks**
2. Click **Add endpoint**
3. Set the URL to:
```text theme={null}
https://yourdomain.com/api/payments/stripe
```
4. Enable at least these events:
* `checkout.session.completed`
* `checkout.session.async_payment_succeeded`
5. Copy the **signing secret** into your env:
```env theme={null}
STRIPE_WEBHOOK_SECRET=whsec_...
```
Grab your Stripe secret key and add it too:
```env theme={null}
STRIPE_SECRET_KEY=sk_...
```
The repo needs this to verify the webhook and inspect the checkout session.
## How it works under the hood
Once Stripe sends a successful webhook, here's what happens:
1. The repo verifies the signature
2. It loads the checkout session
3. It resolves the purchase type from metadata or `STRIPE_PRODUCT_MAP`
4. It stores the purchase in the database
5. It updates the user's credits or plan state
## Verify your setup
**Your Stripe setup is working if all of these are true:**
* The app opens the Stripe checkout page when you click a payment button
* Stripe shows a successful webhook delivery in the dashboard
* A row appears in your `purchases` table
* A credits purchase increases the user's credits
* A plan purchase updates the user's purchase state
`STRIPE_SECRET_KEY` needs your **secret** key (starts with `sk_`), not the publishable one (starts with `pk_`). The publishable key is for client-side Stripe.js only.
You probably added the checkout URL but forgot the webhook. Without a webhook, Stripe can't tell your app the payment succeeded.
You haven't set metadata on your product/session **and** you haven't set `STRIPE_PRODUCT_MAP`. The repo needs at least one of these to know what was purchased.
Make sure you're using the correct production domain in the webhook URL, not `localhost`.
Go back to the shared payment architecture and basics.
# Anthropic
Source: https://docs.anotherwrapper.com/providers/anthropic
Set up Anthropic & understand how it's used throughout the app
Anthropic's Claude models are known for strong reasoning, long-context handling, and thoughtful responses. If you want to offer your users a premium chat experience alongside OpenAI, Anthropic is a great pick.
## Get your API key
Head to the [Anthropic Console](https://console.anthropic.com/dashboard) and sign up or log in.
Go to the [API keys page](https://console.anthropic.com/settings/keys) and click **Create API key**.
Paste the key in your `.env.local` file:
```env theme={null}
ANTHROPIC_API_KEY=your_anthropic_api_key
```
Save your API key right away -- you won't be able to see it again after creation.
## Available models
All models are defined in the unified model registry at `lib/ai/models.ts`. Every Anthropic model supports vision and internet access.
| Model | ID | Features |
| ----------------- | ------------------- | ------------------------------------ |
| Claude Opus 4.5 | `claude-opus-4-5` | Vision, Internet |
| Claude Sonnet 4.5 | `claude-sonnet-4-5` | Vision, Internet, Thinking/Reasoning |
| Claude Haiku 4.5 | `claude-haiku-4-5` | Vision, Internet |
Claude Sonnet 4.5 supports **thinking/reasoning mode** -- the model shows its chain of thought before giving you a final answer. Great for complex tasks where you want transparency.
## Apps using Anthropic
Anthropic is integrated through Vercel AI SDK 6.0, with provider routing handled by `lib/ai/ai-utils.ts`.
Multi-provider chat app -- Anthropic is available as an LLM provider
Generate structured marketing plans using Claude models
Generate Product Hunt launch simulations using Claude models
## How it works
The codebase uses Vercel AI SDK 6.0 with a unified model registry -- no direct Anthropic API calls needed.
Here's the typical flow for an AI request:
1. You select a model from the unified registry
2. The request goes through `getModelInstance()` in `lib/ai/ai-utils.ts`
3. The provider is determined via `getProviderFromModelId()`
4. The model is instantiated with `customModel()`
5. The response is streamed back to you
6. Results are stored in PostgreSQL
Understand the full project structure of the codebase.
# DeepSeek
Source: https://docs.anotherwrapper.com/providers/deepseek
Set up DeepSeek & understand how it's used throughout the app
DeepSeek offers powerful reasoning capabilities at competitive pricing. Their chat model comes with built-in thinking/reasoning mode, making it a solid choice if you want to give your users another strong reasoning option alongside the other providers.
## Get your API key
Head to the [DeepSeek Platform](https://platform.deepseek.com/) and sign up or log in.
Navigate to the API keys section and create a new key.
Paste the key in your `.env.local` file:
```env theme={null}
DEEPSEEK_API_KEY=your_deepseek_api_key
```
Save your API key somewhere safe right away -- you won't be able to see it again.
## Available models
All models are defined in the unified model registry at `lib/ai/models.ts`.
| Model | ID | Features |
| ------------- | --------------- | ------------------ |
| DeepSeek Chat | `deepseek-chat` | Thinking/Reasoning |
DeepSeek Chat supports **thinking/reasoning mode** -- the model shows its chain of thought before giving you the final answer. This is particularly useful for complex problem-solving and analysis tasks.
## Apps using DeepSeek
DeepSeek is integrated through Vercel AI SDK 6.0, with provider routing handled by `lib/ai/ai-utils.ts`.
Multi-provider chat app -- DeepSeek is available as an LLM provider
## How it works
The codebase uses Vercel AI SDK 6.0 with a unified model registry -- no direct DeepSeek API calls needed.
Here's the typical flow for an AI request:
1. You select a model from the unified registry
2. The request goes through `getModelInstance()` in `lib/ai/ai-utils.ts`
3. The provider is determined via `getProviderFromModelId()`
4. The model is instantiated with `customModel()`
5. The response is streamed back to you
6. Results are stored in PostgreSQL
Understand the full project structure of the codebase.
# ElevenLabs
Source: https://docs.anotherwrapper.com/providers/elevenlabs
Set up ElevenLabs & understand how it's used in the Voice Studio app
ElevenLabs turns text into incredibly lifelike speech. With 1,000+ voices across 26+ languages, your Voice Studio app can offer a seriously impressive text-to-speech experience. Let's get it set up.
## Get your API key
Head to [ElevenLabs](https://elevenlabs.io/sign-up) and sign up or log in.
Go to your **Profile Settings** and locate your API key.
Paste the key in your `.env.local` file:
```env theme={null}
ELEVENLABS_API_TOKEN=your_elevenlabs_token
```
Keep your API key safe. Don't share it or commit it to version control.
## App using ElevenLabs
The Voice Studio app at `/apps/voice` uses the ElevenLabs API to generate high-quality speech from text.
Convert text to lifelike speech using 26+ languages and over 1,000 voices
## Features
Here's what you get with ElevenLabs in your app:
* **1,000+ voices** from the ElevenLabs Voice Library -- your users have plenty to choose from
* **26+ languages** supported out of the box
* **Fine-tuning controls** -- adjust stability, similarity, and style to get the perfect audio output
* **Cloud storage** -- generated audio is uploaded to Cloudflare R2 storage automatically
* **Database tracking** -- generation data is stored in the `generations` table in PostgreSQL
* **Credit usage** -- each generation reduces the user's credits (configurable in `toolConfig.ts`)
The voice fine-tuning settings (stability, similarity, style) let your users really dial in the output. Higher stability gives more consistent results, while lower stability adds more expressiveness and variation.
Understand the full project structure of the codebase.
# Google Gemini
Source: https://docs.anotherwrapper.com/providers/google
Set up Google Gemini & understand how it's used throughout the app
Google Gemini brings some unique tricks to the table -- native search grounding and strong multimodal reasoning. In the shipped apps, Gemini is especially useful for chat and the default Vision path.
## Get your API key
Head to [Google AI Studio](https://aistudio.google.com/) and sign in with your Google account.
Navigate to the API keys section and create a new key.
Paste the key in your `.env.local` file:
```env theme={null}
GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
```
Keep your API key safe and don't share it. You can always regenerate it from Google AI Studio if needed.
## Available models
All models are defined in the unified model registry at `lib/ai/models.ts`.
| Model | ID | Features |
| --------------------- | ------------------------------- | -------------------------------------------- |
| Gemini 3 Pro | `gemini-3-pro-preview` | Vision, Search Grounding |
| Gemini 3 Pro Image | `gemini-3-pro-image-preview` | Vision, Search Grounding |
| Gemini 3.1 Flash Lite | `gemini-3.1-flash-lite-preview` | Vision |
| Gemini 2.5 Flash | `gemini-2.5-flash` | Vision, Search Grounding, Thinking/Reasoning |
**Search grounding** is a standout Gemini feature -- it lets the model pull in live web results when answering questions. Your users get up-to-date info without you needing to build a separate search integration.
Gemini 2.5 Flash supports **thinking/reasoning mode**, showing its chain of thought before the final answer. Combine that with search grounding and you've got a seriously capable model.
## Apps using Google Gemini
Google Gemini is integrated through Vercel AI SDK 6.0, with provider routing handled by `lib/ai/ai-utils.ts`.
Multi-provider chat with search grounding support
Default vision-model path for the meal-analysis app
Generate structured marketing plans using Gemini models
Generate Product Hunt launch simulations using Gemini models
The shared model registry includes `gemini-3-pro-image-preview`, but the shipped Image Studio currently uses its own OpenAI and Replicate-backed model catalog.
## How it works
The codebase uses Vercel AI SDK 6.0 with a unified model registry -- no direct Google API calls needed.
Here's the typical flow for an AI request:
1. You select a model from the unified registry
2. The request goes through `getModelInstance()` in `lib/ai/ai-utils.ts`
3. The provider is determined via `getProviderFromModelId()`
4. The model is instantiated with `customModel()`
5. The response is streamed back to you
6. Results are stored in PostgreSQL
Understand the full project structure of the codebase.
# Groq
Source: https://docs.anotherwrapper.com/providers/groq
Set up Groq & understand how it's used throughout the app
Groq is all about speed. Their custom LPU hardware runs Llama models at blazing-fast inference speeds, making it perfect for features where your users don't want to wait around. If responsiveness matters to your product, Groq is a great addition.
## Get your API key
Head to the [Groq Console](https://console.groq.com/login) and sign up or log in.
Go to the [API keys page](https://console.groq.com/keys) and click **Create API key**.
Paste the key in your `.env.local` file:
```env theme={null}
GROQ_API_KEY=your_groq_api_key
```
Save your API key right away -- you won't be able to see it again after creation.
## Available models
All models are defined in the unified model registry at `lib/ai/models.ts`. Both models support vision capability.
| Model | ID | Features |
| ---------------- | ----------------------------------------------- | -------- |
| Llama 4 Scout | `meta-llama/llama-4-scout-17b-16e-instruct` | Vision |
| Llama 4 Maverick | `meta-llama/llama-4-maverick-17b-128e-instruct` | Vision |
Groq's speed advantage is most noticeable in chat and structured generation tasks. If you're building something that needs fast turnaround, these models deliver.
## Apps using Groq
Groq is integrated through Vercel AI SDK 6.0, with provider routing handled by `lib/ai/ai-utils.ts`.
Multi-provider chat app -- Groq is available as an LLM provider
Generate structured marketing plans using Groq models
Generate Product Hunt launch simulations using Groq models
The shipped Audio app does not use Groq today. It uses Replicate Whisper for transcription and OpenAI for summaries.
## How it works
The codebase uses Vercel AI SDK 6.0 with a unified model registry -- no direct Groq API calls needed.
Here's the typical flow for an AI request:
1. You select a model from the unified registry
2. The request goes through `getModelInstance()` in `lib/ai/ai-utils.ts`
3. The provider is determined via `getProviderFromModelId()`
4. The model is instantiated with `customModel()`
5. The response is streamed back to you
6. Results are stored in PostgreSQL
Understand the full project structure of the codebase.
# Providers Overview
Source: https://docs.anotherwrapper.com/providers/index
Choose the right AI providers based on the apps and features you want to enable
AnotherWrapper supports a bunch of AI providers, but here's the good news -- you don't need all of them. The real question is: **which ones do you actually need for what you're building?**
## Start small, expand later
For most projects, this is all you need to get going:
* **OpenAI** -- covers chat, vision, images, audio, and structured output
* **Storage** -- for file uploads
* **Better Auth + PostgreSQL** -- for auth and data
That's a fully functional AI app right there.
Then add more providers as your product grows:
Core chat, images, vision, audio, structured output. Your Swiss Army knife.
Claude models for chat. Great for reasoning and long-context tasks.
Gemini models with search grounding and a strong default vision path.
Lightning-fast Llama models. Great for speed-sensitive features.
Grok models with reasoning capabilities.
Additional reasoning and chat model options.
Image, video, and transcription workflows.
Text-to-speech with 1000+ voices in 26+ languages.
## Provider to feature mapping
Here's a quick cheat sheet so you know exactly what each provider unlocks:
| Provider | What it powers |
| ----------------- | ------------------------------------------------------------ |
| **OpenAI** | Core chat, structured generation, embeddings, image features |
| **Anthropic** | Claude chat support |
| **Google Gemini** | Gemini chat, search grounding, default vision path |
| **Groq** | Fast Llama models for chat and structured generation |
| **xAI** | Grok models for chat |
| **DeepSeek** | Additional reasoning/chat models |
| **Replicate** | Image generation, video generation, and audio transcription |
| **ElevenLabs** | Voice Studio (text-to-speech) |
Don't turn on every provider just because the repo supports them. Enable only what your current product actually needs. That keeps setup time, API costs, and operational complexity way down.
# OpenAI
Source: https://docs.anotherwrapper.com/providers/openai
Set up OpenAI & understand how it's used throughout the app
OpenAI is the Swiss Army knife of this codebase -- it powers chat, image generation, document embeddings, audio summaries, and several structured-output apps. If you're only going to set up one provider first, this is still the smoothest default.
## Get your API key
Head to [platform.openai.com](https://platform.openai.com/signup) and sign up or log in.
Go to the [API keys page](https://platform.openai.com/account/api-keys) and click **Create new secret key**. Give it a name you'll recognize.
Paste the key in your `.env.local` file:
```env theme={null}
OPENAI_API_KEY=your_openai_api_key
```
Save your API key somewhere safe right away -- OpenAI only shows it once. If you lose it, you'll need to create a new one.
## Available models
All models are defined in the unified model registry at `lib/ai/models.ts`. Every OpenAI model supports vision and internet access.
| Model | ID | Features |
| ---------- | ------------ | --------------------------- |
| GPT-5 | `gpt-5` | Vision, Internet |
| GPT-5 Mini | `gpt-5-mini` | Vision, Internet |
| GPT-5 Nano | `gpt-5-nano` | Vision, Internet |
| GPT-4o | `gpt-4o` | Vision, Internet |
| o3 | `o3` | Vision, Internet, Reasoning |
Here's the shape used in the shared registry:
```typescript theme={null}
{
"gpt-5": {
name: "GPT-5",
provider: "openai",
vision: true,
hasInternet: true,
}
}
```
## Apps using OpenAI
OpenAI shows up through two paths in this repo: shared text/vision models go through the Vercel AI SDK and `lib/ai/ai-utils.ts`, while Image Studio uses the direct OpenAI image API.
Multi-provider chat app -- OpenAI is a primary LLM provider
Generate images using the GPT-Image model
Record audio and summarize transcriptions using GPT-5 Mini
OpenAI embeddings power the PDF/document workflow
Generate structured marketing plans
Generate Product Hunt launch simulations
## How it works
The codebase uses Vercel AI SDK 6.0 with a unified model registry -- no direct OpenAI API calls for chat-based interactions.
Here's the typical flow for an AI request:
1. You select a model from the unified registry
2. The request goes through `getModelInstance()` in `lib/ai/ai-utils.ts`
3. The provider is determined via `getProviderFromModelId()`
4. The model is instantiated with `customModel()`
5. The response is streamed back to you
6. Results are stored in PostgreSQL
Understand the full project structure of the codebase.
# Replicate
Source: https://docs.anotherwrapper.com/providers/replicate
Set up Replicate & understand how it's used throughout the app
Replicate is your go-to for file-heavy AI workflows in this repo -- image generation, video generation, and audio transcription. Unlike the chat providers that stream text, Replicate handles longer-running jobs and model-specific workflows cleanly.
## Get your API key
Head to [Replicate](https://replicate.com/signin) and sign up or log in.
Go to the [API tokens page](https://replicate.com/account/api-tokens) and click **Create token**.
Paste the token in your `.env.local` file:
```env theme={null}
REPLICATE_API_TOKEN=your_replicate_api_key
```
Save your API token somewhere safe right away. You won't be able to see it again after creation.
## Apps using Replicate
Replicate powers several of the media and file-processing flows in the app.
Generate AI videos with progress tracking via Replicate
Generate images using Flux and other models on Replicate
Transcribe recordings with Replicate Whisper
## Features
Here's what Replicate brings to the table:
* **Async generation** -- long-running media tasks are submitted and processed with progress tracking
* **Video Studio** -- all shipped video models currently route through the Replicate integration
* **Image Studio** -- access image generation models like Flux
* **Audio** -- run Whisper transcription on uploaded recordings
* **Cloud storage** -- generated media is automatically uploaded to cloud storage
* **Database tracking** -- generation data is stored in the `generations` table in PostgreSQL
* **Credit usage** -- each generation reduces the user's credits (configurable in `toolConfig.ts`)
## How it works
Unlike chat providers that use the shared text-model registry, Replicate handles media generation and transcription workflows through dedicated integrations.
Here's the typical flow for a generation request:
1. You submit a prompt and configuration from the front-end
2. The request is sent to Replicate to start async generation
3. Progress is tracked and reported back in real-time
4. Once complete, the generated media or transcript is retrieved
5. The media is uploaded to cloud storage
6. Results are stored in PostgreSQL
The key difference from chat providers: Replicate tasks can take seconds to minutes, so everything runs asynchronously with progress updates instead of token-by-token streaming.
Understand the full project structure of the codebase.
# xAI's Grok
Source: https://docs.anotherwrapper.com/providers/xai
Set up Grok & understand how it's used throughout the app
xAI's Grok models bring strong reasoning capabilities to the table, including a fast reasoning mode that shows its chain of thought. If you want to give your users access to cutting-edge models from xAI, this is how to set it up.
## Get your API key
Head to the [xAI Console](https://console.x.ai/) and sign up or log in.
Navigate to **API Keys** and click **Create new API key**.
Paste the key in your `.env.local` file:
```env theme={null}
XAI_API_KEY=your_xai_api_key
```
Save your API key somewhere safe right away -- you won't be able to retrieve it later.
## Available models
All models are defined in the unified model registry at `lib/ai/models.ts`.
| Model | ID | Features |
| ----------------------- | ------------------------- | ------------------ |
| Grok 4 | `grok-4-latest` | -- |
| Grok 4.1 | `grok-4-1` | -- |
| Grok 4.1 Fast Reasoning | `grok-4-1-fast-reasoning` | Thinking/Reasoning |
Grok 4.1 Fast Reasoning supports **thinking/reasoning mode** -- the model shows its chain of thought before giving you the final answer. It's great for complex analysis and problem-solving tasks.
## Apps using Grok
Grok is integrated through Vercel AI SDK 6.0, with provider routing handled by `lib/ai/ai-utils.ts`.
Multi-provider chat app -- Grok is available as an LLM provider
Generate structured marketing plans using Grok models
Generate Product Hunt launch simulations using Grok models
## How it works
The codebase uses Vercel AI SDK 6.0 with a unified model registry -- no direct xAI API calls needed.
Here's the typical flow for an AI request:
1. You select a model from the unified registry
2. The request goes through `getModelInstance()` in `lib/ai/ai-utils.ts`
3. The provider is determined via `getProviderFromModelId()`
4. The model is instantiated with `customModel()`
5. The response is streamed back to you
6. Results are stored in PostgreSQL
Understand the full project structure of the codebase.
# Add a Model
Source: https://docs.anotherwrapper.com/recipes/add-a-model
How to add a new AI model or expose an existing provider model in the UI
# Add a New AI Model
Adding a model is usually quick. You're not rewriting the whole app -- you're plugging into an existing system.
## The Process
Make sure the provider is already supported in the repo (OpenAI, Anthropic, Google, Groq, xAI, DeepSeek, Replicate). If it's a new provider, you'll need to add it first.
Set the provider's API key in your `.env.local` file if you haven't already.
Not every model lives in the same file:
* `lib/ai/models.ts` -- shared chat/text model registry
* `app/(apps)/image-studio/lib/models.ts` -- Image Studio catalog
* `app/(apps)/video-studio/lib/models.ts` -- Video Studio catalog
Before you expose it in the UI, think through:
* Does it support text?
* Does it support vision (image input)?
* Does it support browsing/web search?
* Does it support thinking/reasoning controls?
* Should it be free or credit-gated?
These answers affect the user experience just as much as the model name.
The Chat app is the best place to verify a new model works. Test streaming, tool usage, and any special capabilities.
Only add the model to other apps (Image Studio, Vision, etc.) if it actually fits those workflows. Not every model belongs everywhere.
## The Main Files
* `lib/ai/models.ts` -- shared chat/text model catalog
* `app/(apps)/image-studio/lib/models.ts` -- image generation catalog
* `app/(apps)/video-studio/lib/models.ts` -- video generation catalog
Start with the catalog that matches the app surface you are changing.
## Common Mistakes
Watch out for these pitfalls when adding models:
* **Forgetting the provider key** -- you added the model name but didn't set the API key
* **Wrong capability flags** -- exposing a text-only model in a vision flow
* **Assuming shared capabilities** -- just because one model from a provider supports browsing doesn't mean they all do
* **Too many similar models** -- exposing 5 variations of the same model makes the UI harder to use, not better
When in doubt, start with fewer models and expand later. A clean model picker with 4-5 great options beats a cluttered dropdown with 15 confusing choices.
# Build Your First AI App
Source: https://docs.anotherwrapper.com/recipes/build-your-first-ai-app
Practical recipe for turning AnotherWrapper into your own niche AI product
# Build Your First AI App in 15 Minutes
The smartest move? **Don't start from zero.**
Pick the included app that's closest to what you want, then reshape it. You'll skip weeks of boilerplate and go straight to the fun part -- making it yours.
## Choose Your Starting Point
**Start with: Chat**
Best for AI assistants, knowledge tools, copilots, and anything conversational. You get streaming, multi-model support, web browsing, PDF chat, and generative UI out of the box.
[Explore the Chat app](/apps/chat)
**Start with: Marketing Plan or Launch Simulator**
Best for forms that return clean, structured AI data. Planners, analyzers, report generators, roast tools -- anything where the output has a predictable shape.
[Explore Marketing Plan](/apps/marketing-plan) | [Explore Launch Simulator](/apps/launch-simulator)
**Start with: Vision**
Best for "image in, structured data out" workflows. Upload a photo, get typed analysis back. Works for calorie tracking, receipt parsing, inspection tools, and more.
[Explore the Vision app](/apps/vision)
**Start with: Image Studio or Video Studio**
Best for AI creative tools, ad generators, social media asset tools, and anything visual. Full gallery workflow, model switching, and asset management included.
[Explore Image Studio](/apps/image-studio) | [Explore Video Studio](/apps/video-studio)
**Start with: Audio or Voice Studio**
Best for transcription tools, voice apps, podcast utilities, music generators, and sound design products.
[Explore Audio](/apps/audio) | [Explore Voice Studio](/apps/voice)
## The Workflow
Don't overthink it. Which app is 60-80% of what you want? Start there.
Update the copy, metadata, and branding to match your product.
Strip out models, features, or UI elements that don't fit your use case.
This is where your product gets its personality. Write a focused prompt and define exactly what the AI should return.
Decide which features are free and which cost credits. The credit system is already wired up.
Update your landing page and docs to match the new product. You're live.
## Example: Build a Niche Vision Tool
Say you want to turn Vision into a different visual analysis product -- like a skincare analyzer or a receipt parser.
Edit `app/(apps)/vision/toolConfig.ts` with your new app name and settings.
Edit `app/(apps)/vision/prompt.ts` to tell the AI what to analyze and how.
Edit `app/(apps)/vision/schema.ts` to define the new output structure.
Edit `app/(apps)/vision/components/*` to display your new data beautifully.
Update [Vision docs](/apps/vision) to match your new product.
That's it. You just built a custom AI vision product without wiring up uploads, storage, auth, or result pages from scratch.
## Example: Build a Niche Schema-Based Generator
Want a focused AI tool that outputs clean JSON? Like a business plan generator, SEO auditor, or pitch deck outliner?
Copy either `app/(apps)/marketing-plan` or `app/(apps)/launch-simulator`.
Define the exact shape of the data you want back from the AI.
Tell the AI what kind of output to generate.
Update the form fields and the output component to match your product.
Set the app name, model selection, and credit pricing.
This is one of the fastest ways to ship a focused AI micro-SaaS. Schema in, structured data out, beautiful UI on top.
# AI SDK
Source: https://docs.anotherwrapper.com/services/ai-sdk
How AnotherWrapper uses the Vercel AI SDK across chat, structured generation, vision, and more
Think of the AI SDK as the translation layer between your app and different model providers. It lets you talk to OpenAI, Anthropic, Google, xAI, Groq, DeepSeek, and Replicate through one consistent developer experience.
## Why This Matters
Without an abstraction layer, every provider has a different API shape, a different streaming format, and different quirks. The Vercel AI SDK means you can:
* Stream chat responses in real time
* Switch models without rebuilding your whole app
* Generate structured JSON safely
* Run tool calls inside chat
* Generate embeddings for document search
* Keep your codebase *way* cleaner than a pile of provider-specific SDK logic
## Where the Repo Uses It
The AI SDK isn't just used in one demo. It powers multiple core parts of the product:
* **Chat** -- `streamText()` + `useChat()` for streaming conversations, tool calling, browsing, and reasoning
* **Marketing Plan & Launch Simulator** -- `generateText()` with `Output.object()` for validated JSON output
* **Vision** -- `generateText()` with a schema so meal photos come back as structured nutrition data
* **Audio** -- Structured generation for summaries and action items after transcription
* **RAG** -- `embed()` and `embedMany()` to convert document text into vectors for semantic retrieval
## The Main Patterns
This is what makes the chat app feel alive instead of waiting for one big response at the end.
**The flow:**
1. The user sends a message
2. The server prepares the system prompt, tools, and optional document context
3. `streamText()` starts streaming tokens back immediately
4. `useChat()` updates the UI as the answer arrives
5. The final assistant message is stored in PostgreSQL
That's the backbone of the flagship chat app. Users get instant feedback and the conversation persists across sessions.
For things like the nutrition analyzer, marketing planner, or launch simulator, plain text isn't enough. You want fields like `title`, `summary`, `calories`, or `actionItems` in a predictable shape.
The repo uses **schema-based outputs**: the model returns data matching a Zod schema, and your app renders it cleanly. This is a huge quality-of-life upgrade compared to parsing random AI text after the fact.
If you're building a new app that needs structured data, duplicate one of the existing schema-based generation routes and tailor its prompt, schema, and UI. Much faster than starting from scratch.
AnotherWrapper ships with provider adapters for:
* OpenAI
* Anthropic
* Google
* Groq
* xAI
* DeepSeek
* Replicate
You can expose several models in one UI and let users switch between them -- without rebuilding each app from scratch. Just add the provider key and register the models.
The repo uses the AI SDK for embeddings in the document chat system:
1. Text from a PDF is split into chunks
2. Each chunk is converted into a vector via `embed()` / `embedMany()`
3. Vectors are stored in PostgreSQL with `pgvector`
4. When the user asks a question, the question is also embedded
5. The app finds the most relevant chunks and injects them into the prompt
That's how the chat app answers questions about your uploaded documents instead of only relying on the model's general knowledge.
## What You Need to Set Up
At minimum, you need **one provider key**. For most users, the easiest start is:
* [OpenAI](/providers/openai) for chat, structured generation, and embeddings
Then add more providers only if you want more model choice:
## Files to Know
* `package.json` -- installed AI SDK packages
* `lib/ai/models.ts` -- the shared model catalog
* `app/(apps)/chat/api/chat/*` -- streaming chat logic
* `app/(apps)/chat/tools/*` -- tool calling implementations
* `app/(apps)/marketing-plan/api/route.ts` -- growth plan generation
* `app/(apps)/launch-simulator/api/route.ts` -- schema-based launch simulations
* `app/(apps)/vision/api/route.ts` -- structured vision output
* `lib/rag/*` -- embeddings and retrieval pipeline
## Good First Customizations
If you're building on top of this starter, here's what people usually do first:
1. Remove providers you don't plan to support
2. Add or remove models from the shared model list
3. Change which models are free vs. credit-gated
4. Tighten prompts for your specific use case
5. Duplicate a schema-based generation app and tailor its prompt, schema, and UI
6. Add new tools to the chat app
## Common Mistakes
Watch out for these pitfalls:
* Adding provider keys but forgetting to expose the models in the UI
* Assuming every model supports browsing, vision, or thinking controls
* Treating AI SDK abstractions like magic and forgetting provider-level costs
* Using free-form text when a structured schema would be safer
* Enabling document chat without also setting up storage and embeddings
See the flagship app that uses streaming, tool calling, browsing, and RAG.
Learn how embeddings and document retrieval work in this repo.
OpenAI is the easiest provider to set up first.
# Analytics
Source: https://docs.anotherwrapper.com/services/analytics
Product analytics with PostHog, Plausible, and DataFast
Analytics tell you what people actually do in your app. In this repo, analytics are provider-agnostic -- the app sends the same events to one or multiple providers without changing any feature code.
## What You Get Out of the Box
The analytics system is already wired up with:
* An analytics provider loaded at the app root
* Shared event helpers: `trackAnalyticsEvent()`, `trackAnalyticsPageview()`, `identifyAnalyticsUser()`
* Support for one provider, multiple providers, or none at all
* A clean disabled mode with `none`
Your app code never calls PostHog, Plausible, or DataFast directly. It uses the shared helpers and the right provider gets the data.
Meta Ads attribution is separate from this analytics layer. If you need Meta Pixel + Conversions API checkout tracking, read the [Meta Ads guide](/services/meta-ads) instead.
## Pick Your Provider
**Best for:** Full product analytics, event tracking, and advanced features like session replay and feature flags.
```env theme={null}
NEXT_PUBLIC_ANALYTICS_PROVIDER=posthog
NEXT_PUBLIC_POSTHOG_KEY=your-posthog-key
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
```
PostHog is the most full-featured option. If you only want one analytics tool and you want it to grow with you, pick this.
**Best for:** Privacy-friendly, lightweight pageview tracking without the complexity.
```env Required theme={null}
NEXT_PUBLIC_ANALYTICS_PROVIDER=plausible
# Configure one of these:
NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC=https://plausible.io/js/script.js
# or NEXT_PUBLIC_PLAUSIBLE_DOMAIN=yourdomain.com
```
```env Optional theme={null}
NEXT_PUBLIC_PLAUSIBLE_HOST=https://plausible.io
NEXT_PUBLIC_PLAUSIBLE_ENDPOINT=https://plausible.io/api/event
NEXT_PUBLIC_PLAUSIBLE_CAPTURE_LOCALHOST=false
NEXT_PUBLIC_PLAUSIBLE_AUTO_CAPTURE_PAGEVIEWS=true
NEXT_PUBLIC_PLAUSIBLE_HASH_BASED_ROUTING=false
NEXT_PUBLIC_PLAUSIBLE_CUSTOM_PROPERTIES={"app":"anotherwrapper"}
```
Plausible is great if you want something simpler and more privacy-conscious than PostHog.
**Best for:** Basic pageviews and goals with minimal setup.
```env Required theme={null}
NEXT_PUBLIC_ANALYTICS_PROVIDER=datafast
NEXT_PUBLIC_DATAFAST_WEBSITE_ID=your-website-id
NEXT_PUBLIC_DATAFAST_DOMAIN=yourdomain.com
```
```env Optional theme={null}
NEXT_PUBLIC_DATAFAST_HOST=https://datafa.st
NEXT_PUBLIC_DATAFAST_ALLOW_LOCALHOST=false
```
If your app lives on a subdomain like `app.yourdomain.com` but you want all traffic to roll up into the main website, keep the same website ID and set `NEXT_PUBLIC_DATAFAST_DOMAIN=yourdomain.com`.
## Using Multiple Providers
Want more than one? Just comma-separate them:
```env theme={null}
NEXT_PUBLIC_ANALYTICS_PROVIDER=posthog,datafast
```
Want none? Set it to:
```env theme={null}
NEXT_PUBLIC_ANALYTICS_PROVIDER=none
```
If you're just getting started, pick one provider first. Get it working, then add a second one later if you need it.
## Setup
Sign up with PostHog, Plausible, or DataFast and create a project/site.
Grab the API keys, script URLs, or website IDs from your provider dashboard.
Add the provider-specific env vars to `.env.local` (see the tabs above for exact values).
Restart your dev server, open the app, and browse a few pages.
Confirm that pageviews and events appear in your provider's dashboard.
## Key Files
* `lib/analytics/head-scripts.tsx` -- script injection
* `lib/analytics/provider.tsx` -- the root analytics provider
* `lib/analytics/client.ts` -- shared event helpers
* `lib/analytics/providers/*` -- provider-specific implementations
## Verify It Works
Your analytics setup is working if:
* The provider script loads in the browser
* Pageviews appear in the dashboard
* Custom events appear after you trigger tracked actions
Make sure `NEXT_PUBLIC_ANALYTICS_PROVIDER` is exactly `posthog`, `plausible`, `datafast`, or `none`. Any typo silently disables analytics.
Next.js doesn't pick up new env vars until you restart the dev server.
Your `NEXT_PUBLIC_PLAUSIBLE_DOMAIN` must match the site you created in Plausible exactly.
Without `NEXT_PUBLIC_DATAFAST_WEBSITE_ID`, DataFast won't know which site to track.
Need checkout attribution for Meta campaigns? That's a separate system -- read the Meta Ads guide.
# Supabase PostgreSQL + Better Auth
Source: https://docs.anotherwrapper.com/services/better-auth-postgresql
Recommended auth and database setup for the boilerplate
**Recommended default:** Supabase PostgreSQL for the database host, with Better Auth inside the app for authentication and sessions. This is the fastest, most supported path.
Recommended path: run `pnpm bootstrap` first. The setup wizard writes `.env.local`, generates `BETTER_AUTH_SECRET`, runs `pnpm db:migrate`, and can also walk you through Google OAuth and email-provider config. The tabs below are the manual equivalent.
## The Mental Model
Here's how the pieces fit together:
* **PostgreSQL + Drizzle** = your data layer (schema, migrations, queries)
* **Better Auth** = your auth layer (sessions, sign-in, OAuth, magic links)
* **Supabase** = the recommended managed Postgres host
Better Auth runs *inside* your app. Supabase just hosts the database. You're not locked into Supabase -- any PostgreSQL provider that supports `pgcrypto` and `pgvector` will work.
## Quick Setup
The fastest path. No OAuth, no magic links -- just email and password.
Add these to your `.env.local`:
```env theme={null}
NEXT_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-secret-here
DATABASE_URL=postgresql://...
```
If you copy the connection string from Supabase, replace the literal `[YOUR-PASSWORD]` placeholder with your real database password before pasting.
```bash theme={null}
pnpm db:migrate
```
```bash theme={null}
pnpm dev
```
Email/password plus Google sign-in. A bit more setup, but a much better user experience.
```env theme={null}
NEXT_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-secret-here
DATABASE_URL=postgresql://...
```
In Google Cloud Console, add these callback URLs:
* `http://localhost:3000/api/auth/callback/google`
* `https://yourdomain.com/api/auth/callback/google`
Then add your Google credentials to `.env.local`:
```env theme={null}
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true
```
Set `NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true` so the Google button shows up in the UI. The backend only enables Google when `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` are also set.
If you want these flows, you'll need an email provider on both the client and server side:
```env theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=resend
EMAIL_PROVIDER=resend
AUTH_EMAIL_FROM="AnotherWrapper "
RESEND_API_KEY=...
```
`resend` is just the shortest example. `loops` and `brevo` are also supported.
```bash theme={null}
pnpm db:migrate
```
```bash theme={null}
pnpm dev
```
## What Better Auth Handles
* Sign-in and session cookies
* Email/password accounts
* Google OAuth
* Magic link and forgot password
* Profile bootstrap via database hooks
Email/password works without an email provider. Magic link and forgot password need a real email provider plus auth-email config once you enable those flows.
## What Drizzle Handles
* Schema definition in `lib/db/schema/*`
* SQL migration history in `drizzle/*`
* App-side database access in `lib/db/*`
When you change `lib/db/schema/*`, generate a new SQL migration with `pnpm db:generate`.
The RAG/document search features depend on the `pgvector` extension. The repo enables it in `drizzle/0000_better_auth_baseline.sql`, so your database host must support `CREATE EXTENSION vector`.
## Can You Use Another PostgreSQL Provider?
Yes. The main requirements are:
* `pgcrypto` support
* `pgvector` support
* Standard Drizzle/Postgres features used by the schema
Supabase is the recommended default because it gives you managed PostgreSQL plus `pgvector` out of the box.
## Auth Helpers in the Repo
The main server-side auth helpers live in `lib/auth/server.ts`. Here's the pattern:
| Helper | Use When |
| ------------------------- | ------------------------ |
| `getAuthContext()` | Auth is optional |
| `requireUser()` | Server pages and layouts |
| `requireApiUser()` | API routes |
| `signOutCurrentSession()` | Logout flows |
## Verify It Works
Your auth and database setup is correct if you can:
* Sign in on `/auth`
* Reach `/`
* See a profile row created for your user
* Use at least one authenticated app flow
Need the exact auth flow, magic link setup, forgot password, and Google OAuth steps?
Read the PostgreSQL + Drizzle layer underneath the Better Auth setup.
# Blog
Source: https://docs.anotherwrapper.com/services/blog
Add a static MDX blog to boost your SEO
Your blog is powered by [Content Collections](https://www.content-collections.dev/) and MDX. Everything is already set up -- you can start writing immediately.
## Write Your First Post
Add a new `.mdx` file in the `/blog` folder. Name it something like `my-first-post.mdx`.
Include this metadata at the top of your file:
```mdx theme={null}
---
title: Your Title
subtitle: "A short description of your post"
date: 2024-04-28
image: "your_image_url"
---
```
Write your blog post using MDX. You can use standard Markdown plus React components.
Check the `/blog` folder for an existing example that shows various MDX components in action. It's a great starting point to see what's possible.
## Project Structure
| Folder | What's There |
| ------------------------------------- | --------------------------------------------------- |
| `app/blog/*` | Front-end routing and page logic |
| `/blog/*` | Your actual blog articles (`.mdx` files) |
| `components/(ui-components)/blog/*` | Blog cards, images, and shared blog UI |
| `components/(ui-components)/alerts/*` | Callout components you can reuse inside MDX content |
Content Collections handles the content schema and transformation behind the scenes. You don't need to touch its config unless you want to add new frontmatter fields.
Blog posts automatically get added to your sitemap. Every post you write is a new SEO opportunity.
# Credits & Billing
Source: https://docs.anotherwrapper.com/services/credits-billing
How credits, purchases, and access control work
Credits are the usage meter across your apps. Payments top them up, and app routes check balances before expensive actions run. Simple as that.
## Where You'll See Credits
Credits show up in several places:
* The dashboard sidebar
* The account center
* Purchase flows
* API routes for paid generation features
## How Credits Work
When a new user signs up, they get a starting credit balance from the database setup. Enough to explore the app.
AI generation features check the user's balance before running. If they have enough, credits are deducted. If not, they're prompted to buy more.
If a generation fails, the app can refund the credits so users aren't charged for nothing.
When a user buys a credit pack, the payment webhook fires, the purchase is stored, and credits are added automatically.
## Credit Packs
The repo ships with two default packs:
| Pack | Credits |
| --------------- | ----------- |
| `credits-small` | 50 credits |
| `credits-large` | 100 credits |
Credit grants are defined in `lib/payments/types.ts`. Display metadata (labels, prices, descriptions) lives in `lib/payments/public-config.ts`.
You can easily change the credit amounts, add new packs, or adjust pricing. Just update those two files and your payment provider's catalog.
## The Billing Flow
They pick a credit pack and hit the checkout button.
The user completes payment through the hosted checkout flow.
Your payment provider sends a webhook to your app.
The app validates the webhook signature to make sure it's legit.
The purchase is recorded in the `purchases` table.
The user's profile is updated and credits are added automatically.
## Verify It Works
Your billing setup is working if:
* Checkout completes successfully
* The purchase appears in the database
* The credits number updates in the UI
# Email
Source: https://docs.anotherwrapper.com/services/email
Contact sync and auth email delivery with Loops, Resend, and Brevo
This page covers both **email contact sync** (building your mailing list) and **auth emails** (magic links, password resets). One provider handles both jobs.
## What Email Does in This Repo
The email service handles two things through a single provider:
1. **Contact sync** -- when a user signs in, they're created as a contact in your email tool
2. **Auth emails** -- magic link and password reset emails are delivered through the same provider
This means you can build a mailing list, sync user data, and handle auth emails all from one place.
If contact sync fails, sign-in still works normally. Auth email delivery is stricter because the user needs that email to finish the flow.
## Pick Your Provider
**Best for:** Simple defaults, easy contact syncing, and templated auth emails.
Loops uses transactional template IDs for auth emails, so you'll create the email templates in Loops and reference them by ID.
```env theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=loops
LOOPS_API_KEY=your-api-key
AUTH_EMAIL_FROM="YourApp "
LOOPS_AUTH_MAGIC_LINK_TRANSACTIONAL_ID=your-template-id
LOOPS_AUTH_RESET_PASSWORD_TRANSACTIONAL_ID=your-template-id
```
Don't forget the transactional template IDs. Without them, Loops won't know which email to send for magic links and password resets.
**Best for:** Developer-friendly API, simple contact management, and app-managed auth emails.
Resend sends auth emails directly from your app -- no template setup needed on their side.
```env theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=resend
RESEND_API_KEY=your-api-key
RESEND_SEGMENT_ID=your-segment-id
AUTH_EMAIL_FROM="YourApp "
```
`RESEND_SEGMENT_ID` is optional. Use it only if you want contacts automatically added to a specific segment.
**Best for:** Contact lists, CRM-like contact management, and custom purchase-state attributes.
Brevo also sends auth emails directly from your app.
```env theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=brevo
BREVO_API_KEY=your-api-key
BREVO_LIST_ID=2
BREVO_PURCHASED_ATTRIBUTE=PURCHASED
AUTH_EMAIL_FROM="YourApp "
```
Use `BREVO_PURCHASED_ATTRIBUTE` only if that attribute already exists in your Brevo account.
## Provider Selection
Set the provider with:
```env theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=loops
```
You can optionally add a server-only override that takes priority:
```env theme={null}
EMAIL_PROVIDER=loops
```
Supported values: `loops`, `resend`, `brevo`, `none`
## Setup
Choose Loops, Resend, or Brevo based on your needs.
Sign up with your chosen provider and generate an API key.
Add the provider-specific env vars from the tabs above.
If you're using Loops, create transactional email templates for magic links and password resets, then add their IDs to your env vars.
Restart the app, sign in with a test account, and request a magic link or password reset email. Confirm the contact appears in your provider's dashboard.
## Key Files
* `lib/email/contacts.ts` -- contact sync logic
* `lib/email/providers/*` -- provider-specific implementations
* `lib/email/auth-emails.ts` -- auth email delivery
## Verify It Works
Your email setup is correct if:
* A test user signs in successfully
* Auth emails arrive for magic link and forgot password
* The user appears in your chosen email provider
* The app still works even if contact sync temporarily fails
## Common Mistakes
Make sure `NEXT_PUBLIC_EMAIL_PROVIDER` is exactly `loops`, `resend`, or `brevo`. Typos will silently disable email.
Auth emails need a sender address. Without `AUTH_EMAIL_FROM`, delivery will fail.
Loops requires `LOOPS_AUTH_MAGIC_LINK_TRANSACTIONAL_ID` and `LOOPS_AUTH_RESET_PASSWORD_TRANSACTIONAL_ID`. Without them, auth emails won't send.
If you reference a segment, list, or attribute that doesn't exist in your provider, contact sync will fail silently.
`EMAIL_PROVIDER=none` disables everything. No contact sync, no auth emails.
# Platform Overview
Source: https://docs.anotherwrapper.com/services/index
The services that power your product — set them up at your own pace
Your apps look great on the surface, but underneath they're powered by a shared platform. This is where you set up auth, storage, AI, credits, email, and everything else your product needs.
You don't need to set up everything at once. Start with auth + database + one AI provider (that's the [Quick Setup](/setup/introduction)), then add more services as you need them.
## What to set up first
These are the essentials — most apps need them.
Better Auth for sign-in, sessions, and OAuth. PostgreSQL + Drizzle for your data layer. This is step one.
S3-compatible object storage for uploads, generated images, audio, video, and PDFs.
The abstraction layer that lets your app talk to OpenAI, Anthropic, Google, and more through one interface.
Monetize AI features with a built-in credit system powered by payments.
## Add when you need them
These are powerful but optional — add them when your product is ready.
Upload PDFs, index them with embeddings, and get answers with citations. Turns chat into a knowledge assistant.
Auth emails and contact sync with Loops, Resend, or Brevo.
## Recommended setup order
[Set up Better Auth + PostgreSQL](/services/better-auth-postgresql) — required for everything.
[Pick a provider](/providers/index) — OpenAI is the easiest starting point.
[Configure S3 storage](/services/storage) — needed for uploads, image/video/voice outputs.
[Set up credits](/services/credits-billing) — if you want to monetize AI features.
[Configure email](/services/email) — when you want magic links, password resets, or contact sync.
[Enable document chat](/services/vector-rag) — when you want PDF chat with citations.
Looking for analytics, SEO, landing page, or monitoring? Those are in the [Growth & Marketing](/services/analytics) and [Ship It](/setup/deployment) sections of the sidebar.
# Landing Page
Source: https://docs.anotherwrapper.com/services/landing-page
How the public marketing page is structured and where to edit it
AnotherWrapper doesn't just ship app dashboards. It includes a complete public-facing marketing page so you're not starting from a blank screen when it's time to sell your product.
## What You Get
A full marketing page that:
* Explains what the product does
* Shows the app in action
* Builds trust with testimonials and logos
* Answers common objections with an FAQ
* Presents pricing
* Pushes visitors toward checkout or signup
This isn't "just design." It's part of your product and your sales funnel.
## Page Structure
The landing page route is `app/landing/page.tsx`, which assembles reusable sections:
| Section | File |
| ------------- | -------------------------------------- |
| Navbar | `components/landing/navbar.tsx` |
| Hero | `components/landing/hero.tsx` |
| Logos | `components/landing/logos.tsx` |
| Features | `components/landing/features.tsx` |
| Apps Showcase | `components/landing/apps-showcase.tsx` |
| How It Works | `components/landing/how-it-works.tsx` |
| Testimonials | `components/landing/testimonials.tsx` |
| Pricing | `components/landing/pricing.tsx` |
| FAQ | `components/landing/faq.tsx` |
| CTA | `components/landing/cta.tsx` |
| Footer | `components/landing/footer.tsx` |
You don't need to rewrite one giant page. Just edit the section that owns the content you care about.
## Customization Guide
Update the headline, subheadline, and primary CTA in `components/landing/hero.tsx`. This is the first thing visitors see, so make it count.
Edit `components/landing/features.tsx` and `components/landing/apps-showcase.tsx` to match your actual product. Replace screenshots and descriptions.
Update copy and checkout links in `components/landing/pricing.tsx`. Make sure the links point to your real payment flows.
Swap out testimonials in `components/landing/testimonials.tsx` and logos in `components/landing/logos.tsx`.
Update `components/landing/faq.tsx` with questions your actual users ask.
Edit `components/landing/cta.tsx` for the final call to action and `components/landing/footer.tsx` for links and branding.
## Recommended Editing Order
If you're new to this, follow this order to avoid wasting time on styling before the business flow works:
Change headlines, descriptions, and feature claims first. Make sure they match your product.
Swap visuals so the page shows your actual product, not the demo.
Make sure the pricing section links to your real payment flows.
Verify that visitor tracking and email capture are working before you drive traffic.
Only now should you start tweaking layouts and styling. The business flow should already be correct.
Don't ship a beautiful page that still talks about features your product no longer has. Update copy before design.
## Connected Services
The landing page works hand-in-hand with several other systems:
Metadata, sitemap, and OG images for your landing page.
Track which sections and CTAs actually convert.
Connect pricing sections to real checkout flows.
# Meta Ads
Source: https://docs.anotherwrapper.com/services/meta-ads
Optional Meta Pixel and Conversions API checkout attribution
Meta Ads support is **completely optional**. If you don't run Meta Ads, you can skip this entirely.
## What This Does
This feature tracks the two Meta events most people care about for paid traffic:
* **`InitiateCheckout`** -- when someone clicks a checkout button
* **`Purchase`** -- when the payment webhook succeeds and the purchase is stored
That gives you clean ad performance measurement without forcing Meta code into the generic analytics layer.
## Why It's Separate From Analytics
In this repo:
* Product analytics live under `lib/analytics/*`
* Meta checkout attribution lives under `lib/attribution/meta/*`
That split is intentional. PostHog, Plausible, and DataFast are general analytics tools. Meta Ads is a paid acquisition channel with its own tracking requirements. Keeping them separate means you're never forced to enable Meta just because the feature exists.
## Setup
You'll need your Meta Pixel ID and a Conversions API access token from Meta Events Manager.
```env theme={null}
NEXT_PUBLIC_ENABLE_META_ATTRIBUTION=true
NEXT_PUBLIC_META_PIXEL_ID=your-pixel-id
META_ACCESS_TOKEN=your-access-token
```
Optional for testing:
```env theme={null}
NEXT_PUBLIC_META_TEST_EVENT_CODE=your-test-code
```
The Meta Pixel only loads when `NEXT_PUBLIC_ENABLE_META_ATTRIBUTION=true` and the required values are present.
Click a checkout button and complete a purchase. Check Meta Events Manager to confirm both events fire.
## How the Flow Works
1. A user clicks a checkout CTA rendered through `CheckoutLink`
2. The repo sends Meta `InitiateCheckout` in the browser
3. The payment provider completes checkout
4. Your webhook is verified by the shared payment route
5. The purchase is stored by the shared payment processor
6. The repo sends Meta `Purchase` through Conversions API
**Only checkout buttons that go through the shared checkout abstraction get the Meta browser event automatically.** Use `components/(ui-components)/payments/checkout-link.tsx` for your CTAs. Hardcoded raw checkout URLs in random UI files will not trigger Meta events.
## What Meta Receives
The implementation sends:
* Product/content identifiers from the shared checkout catalog
* Value and currency
* Hashed user identity data when available
* A stable event ID for purchase deduplication
The purchase event is emitted after the purchase row is inserted -- the safest place to do it.
## Key Files
* `lib/attribution/meta/provider.tsx` -- Meta provider component
* `lib/attribution/meta/browser.ts` -- browser-side pixel events
* `lib/attribution/meta/conversions-api.ts` -- server-side Conversions API
* `components/(ui-components)/payments/checkout-link.tsx` -- checkout CTA component
* `lib/payments/processor.ts` -- where purchase events fire
## Verify It Works
Your Meta Ads setup is working if:
* The Meta Pixel loads only when enabled
* Clicking a checkout CTA triggers `InitiateCheckout`
* A successful purchase webhook triggers `Purchase`
* Events appear in Meta Events Manager
For testing, set `NEXT_PUBLIC_META_TEST_EVENT_CODE` and confirm test events appear under that code in Events Manager.
Make sure your checkout buttons use the `CheckoutLink` component, not hardcoded URLs.
Check that `META_ACCESS_TOKEN` is set. The Purchase event goes through the server-side Conversions API, which needs the token.
Double-check your Pixel ID. Also verify that checkout URLs are present in your env -- the Meta feature won't fire events if there's nothing to check out.
See how hosted checkout links and payment webhooks work in the repo.
Read about the separate product analytics layer for PostHog, Plausible, and DataFast.
# Monitoring
Source: https://docs.anotherwrapper.com/services/monitoring
Optional Sentry error tracking for production visibility
Monitoring helps you see production errors after your app is live. Sentry is optional in this repo and only turns on when you provide a DSN. If you skip it, the app works perfectly fine.
## What Sentry Does for You
* See frontend and backend errors in one place
* Inspect full stack traces
* Track runtime problems after deployment
* Catch issues before your users report them (or don't report them)
## Get It Running
Sign up at [sentry.io](https://sentry.io) and create a new project for your app.
Copy your project DSN and add it to `.env.local`:
```env theme={null}
NEXT_PUBLIC_SENTRY_DSN=https://example@o0.ingest.sentry.io/0
```
You can also set the server-side DSN (falls back to `NEXT_PUBLIC_SENTRY_DSN` if missing):
```env theme={null}
SENTRY_DSN=https://example@o0.ingest.sentry.io/0
```
For nicer production stack traces, add these build-time variables:
```env theme={null}
SENTRY_AUTH_TOKEN=your-auth-token
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project
```
Control the environment label and trace sampling:
```env theme={null}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=production
SENTRY_ENVIRONMENT=production
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=0.1
SENTRY_TRACES_SAMPLE_RATE=0.1
```
Restart the app (or redeploy in production) and trigger a test error to confirm Sentry receives it.
## Verify It Works
Your Sentry setup is working if:
* The app still builds normally
* Sentry initializes when a DSN is set
* A test error appears in your Sentry project dashboard
Did you restart the app or redeploy? Sentry won't initialize until the new env vars are loaded.
You need the source map env vars (`SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, `SENTRY_PROJECT`) for readable production stack traces.
Double-check that your DSN matches the correct Sentry project. Each project has a unique DSN.
# Security
Source: https://docs.anotherwrapper.com/services/security
Auth guards, ownership checks, rate limiting, and AI cost protection
AnotherWrapper includes several security layers out of the box. Here's what's built in and what you should add before launch.
## Security Layers
The repo uses centralized helpers in `lib/auth/server.ts` for all server-side auth checks.
**For API routes**, use `requireApiUser()`:
```typescript app/api/protected/route.ts theme={null}
import { requireApiUser } from "@/lib/auth/server";
export async function GET() {
const { user, unauthorizedResponse } = await requireApiUser();
if (unauthorizedResponse) return unauthorizedResponse;
return Response.json({
message: "Protected data",
userId: user.id,
});
}
```
**For server components and pages**, use `requireUser()`:
```typescript app/protected/page.tsx theme={null}
import { requireUser } from "@/lib/auth/server";
export default async function ProtectedPage() {
const user = await requireUser();
return Welcome, {user.email}
;
}
```
Always use these helpers instead of rolling your own auth checks. They handle the edge cases for you.
The default model enforces ownership through authenticated server helpers and user-scoped queries -- not database-level RLS policies. This keeps the starter portable across PostgreSQL hosts.
**The practical rules:**
1. Fetch the current user on the server
2. Scope reads and writes by `user.id`
3. Keep that logic in `lib/db/*` instead of scattering it across UI code
```typescript theme={null}
import { and, eq } from "drizzle-orm";
import { db } from "@/lib/db/client";
import { chatDocuments } from "@/lib/db/schema/chat";
export async function getUserDocument(documentId: string, userId: string) {
return db.query.chatDocuments.findFirst({
where: and(
eq(chatDocuments.id, documentId),
eq(chatDocuments.user_id, userId)
),
});
}
```
This pattern prevents one user from reading or mutating another user's records.
If you want database-level defense in depth, you can add your own PostgreSQL RLS policies on top of this model.
Rate limiting isn't enabled by default, but it's straightforward to add. Target expensive AI routes, upload routes, and auth endpoints first.
Here's an example middleware shape using Upstash:
```typescript middleware.ts theme={null}
import { type NextRequest, NextResponse } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(3, "60 s"),
});
const urlsToRateLimit = ["/api/ai/generate", "/api/uploads"];
export async function middleware(request: NextRequest) {
if (urlsToRateLimit.some((url) => request.nextUrl.pathname.startsWith(url))) {
const ip = request.ip ?? "127.0.0.1";
const { success } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json({ error: "Rate limit exceeded" }, { status: 429 });
}
}
return NextResponse.next();
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};
```
AI services can get expensive fast. Protect yourself on two levels:
**Provider-level:** Set budget alerts and hard spending limits in every AI provider dashboard you enable (OpenAI, Anthropic, Google, etc.).
**Application-level:** The credit system acts as a usage meter. Users must have sufficient credits for paid AI features, and credits are consumed through transactional database updates to prevent race conditions.
Always set up budget alerts and hard limits in your AI service dashboards. The credit system is your app-level safeguard, but provider-level limits are your financial safety net.
Magic link and password-reset emails go through your configured provider (Resend, Loops, or Brevo), not through a bundled SMTP path.
**Before launch:**
* Verify your sender domain
* Monitor bounces and suppressions
* Rate limit auth email endpoints if abuse becomes a concern
* Test magic link and reset-password delivery
Revisit your auth, upload, and AI-abuse protections before launch. Most production security issues in products like this come from missed operational controls, not missing UI code.
# SEO
Source: https://docs.anotherwrapper.com/services/seo
Metadata, sitemaps, and dynamic OG images -- all baked in
All pages are **optimized for SEO by default.** The codebase ships with automatic metadata tags, a generated sitemap, and dynamic OG banners. You mostly just need to update the content.
## The Three SEO Pillars
The root `app/layout.tsx` exports default metadata using values from `config.ts`. Each app can override the defaults through its own `toolConfig.ts` file.
```typescript app/layout.tsx theme={null}
export const metadata = {
title: `${defaultTitle}`,
description: defaultDescription,
keywords: defaultKeywords,
icons: [{ rel: "icon", url: `${companyConfig.company.homeUrl}${favicon}` }],
openGraph: {
url: companyConfig.company.homeUrl,
title: `${defaultTitle} | ${companyConfig.company.name}`,
description: defaultDescription,
images: [
{
url: `${companyConfig.company.homeUrl}${defaultOgImage}`,
width: 800,
height: 600,
alt: `${companyConfig.company.name} logo`,
},
],
},
};
```
You can override the default meta tags by [exporting a metadata object](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) in any `page.tsx` file. The defaults in `config.ts` are your safety net.
The sitemap at `app/sitemap.ts` combines your static public routes with blog posts from Content Collections. As you customize the starter, keep that file aligned with the routes you actually expose, such as `/blog`, `/apps`, `/chat`, `/image-studio`, `/video-studio`, `/audio`, `/vision`, `/marketing-plan`, `/launch-simulator`, and `/voice`.
Every blog post you write automatically appears in the sitemap. Just add the `.mdx` file and it's indexed.
The route at `app/api/og/route.tsx` generates dynamic Open Graph images for your pages using `next/og`. These are the preview cards that show up when someone shares your link on social media.
Check out the [Vercel OG examples](https://vercel.com/docs/functions/og-image-generation) for inspiration on customizing yours.
## Where to Edit
| What | Where |
| ----------------------- | -------------------------------------------- |
| Default SEO values | `config.ts` at the project root |
| Page-specific overrides | Export a `metadata` object in any `page.tsx` |
| Sitemap entries | `app/sitemap.ts` |
| OG image generation | `app/api/og/route.tsx` |
Before launching, search for any leftover "AnotherWrapper" references in your metadata. You want your brand name, not the boilerplate's.
# Storage
Source: https://docs.anotherwrapper.com/services/storage
Object storage for uploads and generated files
Storage is where your app keeps uploaded files and generated media. If you skip this, the basic app still runs, but upload-heavy features like document chat, image generation, and audio won't work properly.
## What Uses Storage
Your app needs storage for:
* Chat document uploads (PDFs)
* Generated images
* Generated videos
* Generated audio
* User-uploaded files for vision and other tools
## Get It Running
We recommend **Cloudflare R2** because it's S3-compatible and simple to set up. Create a bucket in the R2 dashboard.
Create an API token with read/write access to your bucket. Copy the S3 endpoint from your R2 dashboard.
Set up a custom domain or use the default `r2.dev` URL so your users can access stored files in the browser.
```env .env.local (Cloudflare R2) theme={null}
STORAGE_REGION=auto
STORAGE_ACCESS_KEY=your-access-key
STORAGE_SECRET_KEY=your-secret-key
STORAGE_ENDPOINT=https://.r2.cloudflarestorage.com
STORAGE_BUCKET=anotherwrapper
STORAGE_PUBLIC_URL=https://cdn.yourdomain.com
```
```env .env.local (Generic S3-compatible) theme={null}
STORAGE_REGION=us-east-1
STORAGE_ACCESS_KEY=your-access-key
STORAGE_SECRET_KEY=your-secret-key
STORAGE_ENDPOINT=https://your-s3-endpoint.com
STORAGE_BUCKET=your-bucket-name
STORAGE_PUBLIC_URL=https://your-public-url.com
```
Restart your dev server, upload a file, and check that the stored file URL opens in the browser.
## What Each Env Var Does
| Variable | Purpose |
| -------------------- | ------------------------------------------------------ |
| `STORAGE_REGION` | Region value expected by your provider (`auto` for R2) |
| `STORAGE_ACCESS_KEY` | Access key for your bucket |
| `STORAGE_SECRET_KEY` | Secret key for your bucket |
| `STORAGE_ENDPOINT` | S3-compatible API endpoint |
| `STORAGE_BUCKET` | Bucket name |
| `STORAGE_PUBLIC_URL` | The public URL users will open in the browser |
**The most important rule:** `STORAGE_PUBLIC_URL` must point to the same bucket your app is uploading into. If your app writes to Bucket A but the public URL points to Bucket B, uploads will succeed but every link will be broken.
## Key Files
The main storage logic lives in:
* `lib/storage/object-storage.ts` -- uploads, deletes, and public URLs
* `lib/integrations/cloudflare.ts` -- Cloudflare-specific integration
## Verify It Works
Your storage setup is working if you can:
* Upload a file through the app
* See generated media save successfully
* Open the stored file URL in your browser
Double-check that `STORAGE_BUCKET` matches the exact name in your provider dashboard, and that `STORAGE_ENDPOINT` uses the correct account ID.
Your `STORAGE_PUBLIC_URL` must resolve to the same bucket you're uploading to. If you're using a CDN, make sure it's configured to serve from the right origin.
Did you restart the app? Next.js doesn't pick up new env vars until you restart the dev server.
# Vector Database & RAG
Source: https://docs.anotherwrapper.com/services/vector-rag
How document chat works with PostgreSQL, pgvector, embeddings, and citations
This isn't a fake "upload a PDF" button. It's a real document-chat pipeline. Users upload documents, the app indexes them, and they get answers with citations. Here's how it all works.
## What Is RAG?
**RAG** stands for **Retrieval-Augmented Generation**. In plain English:
1. Your app stores source material (like PDFs)
2. When a user asks a question, the app finds the most relevant pieces
3. Those pieces get passed into the model as context
4. The model answers using that context
This is how the chat app answers questions about **your documents** -- not just what the model already knew.
## The Pipeline, Step by Step
The user uploads a PDF in chat. The file is stored and tracked in the database.
The repo extracts readable text from the PDF so the AI system has something to work with.
The text is split into smaller sections. Models and vector search work much better on focused chunks than one giant blob.
Each chunk is turned into a vector embedding using OpenAI's embedding model. Think of it as converting meaning into numbers.
Those embeddings are stored in PostgreSQL in the `embeddings` table, alongside document metadata.
When the user asks a question, the question is embedded too. The app runs similarity search to find the closest matching chunks.
The best chunks are assembled into prompt-ready context and passed into the chat generation flow.
The answer includes source metadata so the UI can show citations back to the user. No more "trust me, bro."
## The Vector Database
The vector database is what makes semantic search possible. Instead of just storing raw text, the app stores:
* The original chunk of text
* Metadata (document ID, page number, etc.)
* A vector embedding for that chunk
That embedding is a numeric representation of *meaning*. It lets the app find chunks that are conceptually similar to a question, even when the wording is completely different.
This is all implemented with **PostgreSQL** + **pgvector** + **OpenAI embeddings**.
`pgvector` is a Postgres extension enabled at the database level. Your host must support `CREATE EXTENSION vector` for this to work.
## What You Need
For auth, hosted Postgres, and the documented setup path.
For uploaded documents and file-backed workflows.
For embeddings in the RAG pipeline.
Even if your users mostly chat with Claude, Gemini, Grok, or DeepSeek, the document pipeline uses **OpenAI embeddings** for indexing and retrieval. Treat [OpenAI](/providers/openai) as required for this feature.
## Key Tables
| Table | Purpose |
| --------------------- | -------------------------------------- |
| `pdf_documents` | Uploaded documents and indexing status |
| `embeddings` | Chunk vectors and metadata |
| `chat_document_links` | Links documents to chat sessions |
Documents aren't floating around as random files. They're part of a real, queryable data model.
## Key Files
| File | Purpose |
| --------------------------------------------- | ----------------------- |
| `lib/rag/pdf-extract.ts` | PDF text extraction |
| `lib/rag/chunking.ts` | Chunk creation |
| `lib/rag/embedding-ingest.ts` | Embedding + persistence |
| `lib/rag/retrieve.ts` | Semantic retrieval |
| `lib/rag/citations.ts` | Citation formatting |
| `app/(apps)/chat/api/chat/chat-generation.ts` | Injecting RAG into chat |
## What You Can Build With This
This setup is perfect for:
* PDF question answering
* Internal knowledge assistants
* Customer support bots grounded in your docs
* Contract or policy lookup
* Document-aware research workflows
It turns the chat app from "just another assistant" into something that works with user-provided knowledge.
## Common Mistakes
The upload and indexing are separate steps. Make sure the indexing pipeline completes before expecting RAG answers.
You need object storage configured so the app can store the uploaded files. See the [Storage guide](/services/storage).
The embedding pipeline uses OpenAI regardless of which chat model the user picks. Set your `OPENAI_API_KEY`.
Not every provider has `pgvector` enabled. Supabase supports it out of the box. Check your host's docs if you're using something else.
RAG improves grounded answers, but it doesn't replace good prompts, reasonable chunking, or careful product design. It gives the model better context -- it doesn't magically make every answer perfect.
# Authentication
Source: https://docs.anotherwrapper.com/setup/authentication
Set up sign-in for your app with email/password, Google OAuth, magic links, and password resets
Your app ships with a full authentication system powered by [Better Auth](https://www.better-auth.com/). It runs inside your Next.js app and talks directly to your Drizzle database -- no external auth service needed.
Recommended path: run `pnpm bootstrap`. The setup wizard generates `BETTER_AUTH_SECRET`, writes `.env.local`, and can prompt for Google OAuth and email-provider settings. The rest of this page is the manual setup path.
## What's Already Built For You
Out of the box, you get:
* A polished login page at `/auth`
* Email/password sign-in and sign-up
* Google OAuth sign-in (optional)
* Magic link sign-in (optional)
* Forgot-password / reset flow
* Session handling and protected routes
* Automatic redirects into your app after login
The key routes to know: `/auth` (sign-in page), `/auth/reset-password` (password reset),
`/api/auth/[...all]` (Better Auth handler), and `/api/auth/callback/google` (Google OAuth callback).
## Getting Started
```bash theme={null}
pnpm bootstrap
```
Choose the auth options you want, and the wizard will write the matching `.env.local` values for you.
These three variables are the absolute minimum to get auth working:
```env theme={null}
NEXT_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_SECRET=...
DATABASE_URL=postgresql://...
```
`BETTER_AUTH_URL` is optional. If you skip it, the app automatically falls back to `NEXT_PUBLIC_APP_URL`.
With just the core env vars above, **email/password auth works immediately**. For Google OAuth or magic links, keep reading -- you'll add a few more env vars depending on what you want.
After changing any env vars, restart your dev server for the changes to take effect.
```bash theme={null}
pnpm dev
```
## Auth Method Setup
This is the default and requires **zero extra configuration** beyond the core env vars above.
Your users can sign up with an email and password, and sign in the same way. That's it -- you're done!
Magic link sign-in lets users log in by clicking a link sent to their email. To enable it, you need an email provider configured.
**Supported email providers:** `loops`, `resend`, `brevo`
```env Loops theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=loops
EMAIL_PROVIDER=loops
AUTH_EMAIL_FROM="AnotherWrapper "
LOOPS_API_KEY=...
LOOPS_AUTH_MAGIC_LINK_TRANSACTIONAL_ID=...
LOOPS_AUTH_RESET_PASSWORD_TRANSACTIONAL_ID=...
```
```env Resend theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=resend
EMAIL_PROVIDER=resend
AUTH_EMAIL_FROM="AnotherWrapper "
RESEND_API_KEY=...
```
```env Brevo theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=brevo
EMAIL_PROVIDER=brevo
AUTH_EMAIL_FROM="AnotherWrapper "
BREVO_API_KEY=...
```
`NEXT_PUBLIC_EMAIL_PROVIDER` controls whether the auth UI shows magic-link and reset-password modes. `EMAIL_PROVIDER` is the server-side provider selection and falls back to the public value if you do not override it.
Google sign-in is totally optional, but it's a great way to reduce friction for your users.
Head to the [Google Cloud Console](https://console.cloud.google.com/) and create OAuth 2.0 credentials for your project.
In your Google OAuth settings, add these as allowed redirect URIs:
* `http://localhost:3000/api/auth/callback/google` (for local dev)
* `https://yourdomain.com/api/auth/callback/google` (for production)
```env theme={null}
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true
```
## What Happens When a User Signs In
Here's the flow your users go through:
They see your sign-in page with the auth methods you've enabled.
Better Auth creates or reads the user and session directly in your database.
The app automatically syncs the matching profile row for the user.
The user lands on `/` (or whatever page they originally requested).
## Verification Checklist
Run through this list to make sure everything is working:
Email/password sign-in and sign-up works
Magic link emails arrive (if you enabled magic links)
Password reset emails arrive (if you enabled email auth)
Clicking a magic link or reset link signs you in correctly
Google sign-in works (if you enabled it)
`/` loads after sign-in without bouncing back to `/auth`
## Troubleshooting
This env var is required. Generate a random string (at least 32 characters) and set it in your `.env.local` file. Without it, Better Auth can't encrypt sessions.
Double-check that you've set `AUTH_EMAIL_FROM` and your email provider credentials. If you're using Loops, make sure you've also set the transactional template IDs (`LOOPS_AUTH_MAGIC_LINK_TRANSACTIONAL_ID` and `LOOPS_AUTH_RESET_PASSWORD_TRANSACTIONAL_ID`).
The most common cause is a mismatch between your callback URL in Google Cloud and your actual app URL. Make sure your redirect URI is exactly `http://localhost:3000/api/auth/callback/google` for local dev (or your production domain equivalent).
This variable must match the domain you're actually testing on. If you're running locally, it should be `http://localhost:3000`. In production, it should be your live domain with `https://`.
You need to restart your dev server after editing `.env.local`. Kill the running process and run `pnpm dev` again.
# Deployment
Source: https://docs.anotherwrapper.com/setup/deployment
Take your app live on Vercel in minutes
You've built something cool locally. Now let's get it in front of real users. This guide walks you through deploying to Vercel -- the recommended (and easiest) target for this repo.
## Before You Deploy
Make sure you've checked off these prerequisites:
Your app runs and works locally
Better Auth env vars are configured
Drizzle migrations have been applied to your production database
You know your production domain
You have your env vars ready to paste into Vercel
## Deploy to Vercel
If you haven't already, push your code to a GitHub repository. Vercel connects directly to GitHub for automatic deployments.
Head to [vercel.com](https://vercel.com), click **Add New Project**, and import your GitHub repo. Vercel will auto-detect that it's a Next.js app.
Use these defaults:
| Setting | Value |
| --------------- | -------------- |
| Install command | `pnpm install` |
| Build command | `pnpm build` |
| Start command | `pnpm start` |
At minimum, you need these:
```env theme={null}
NEXT_PUBLIC_APP_URL=https://yourdomain.com
BETTER_AUTH_SECRET=...
DATABASE_URL=postgresql://...
```
Plus at least one LLM provider key:
```env OpenAI theme={null}
OPENAI_API_KEY=...
```
```env Google AI theme={null}
GOOGLE_GENERATIVE_AI_API_KEY=...
```
```env Anthropic theme={null}
ANTHROPIC_API_KEY=...
```
`DATABASE_URL` should point to your **production** PostgreSQL connection string. Keep it server-side only -- never use a `NEXT_PUBLIC_` prefix for it.
Click deploy and watch your app go live. Vercel will build and deploy it automatically.
## After Deployment
Once your app is live, come back and update these settings to use your production domain:
* Set `NEXT_PUBLIC_APP_URL` to your live domain (if not already)
* Update `BETTER_AUTH_URL` if you use the override
* Update your Google OAuth redirect URIs to `https://yourdomain.com/api/auth/callback/google`
* Update auth email sender/domain settings in your email provider
Point your payment webhook URLs to your live domain. Each payment provider (Polar, Stripe, LemonSqueezy) has its own webhook settings -- update them to use your production URL.
If you use any other services with callback URLs or allowlists, make sure they know about your new domain.
## Optional Env Vars
You only need to add env vars for the features you actually use. Here are the common extras:
```env theme={null}
EMAIL_PROVIDER=...
AUTH_EMAIL_FROM=...
AUTH_EMAIL_REPLY_TO=...
```
```env theme={null}
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true
```
Add keys only for what you need:
* Storage provider credentials
* Payment provider keys and webhook secrets
* Analytics (PostHog, Plausible, DataFast)
* Sentry DSN for error tracking
* Replicate, ElevenLabs, and other AI provider keys
OpenAI is still required if you want PDF RAG embeddings or GPT Image, even if you use a different LLM provider for chat.
## Post-Deploy Verification
Run through this checklist to make sure everything is healthy:
The live landing page loads
Auth works on the live domain (sign up, sign in)
`/` loads after sign-in
Your main enabled feature works in production
## Mintlify Docs
If you connect Mintlify to this repo, point the docs root to `docs/public`. Your docs will auto-deploy alongside your app.
# Get Started
Source: https://docs.anotherwrapper.com/setup/introduction
Go from zero to running AI app in about 5 minutes
You're about 5 minutes away from running your own AI product locally. Let's go.
**The fast path:** clone the repo, run `pnpm bootstrap`, then `pnpm dev`. That's it.
## What you get right away
With just the minimum setup (database + one AI provider), you can already use:
Multi-model chat with streaming
Schema-based business tool
Fun Product Hunt simulation
Full app shell with credits
Marketing site at `/landing`
Email/password sign-in ready
If your first provider is OpenAI, the GPT Image models are configured. To actually save generated media and use the full Image Studio flow, you still need storage. Adding Replicate early unlocks even more image and video models.
These features work great but need additional configuration beyond the basics:
* **Storage-backed flows** — chat documents, uploads, generated asset history
* **Video Studio** — video model credentials + storage
* **Voice Studio** — ElevenLabs + storage
* **Payments, email, analytics, Sentry** — all optional
* **PDF RAG and embeddings** — needs OpenAI + storage + pgvector
* **Extra AI providers** — beyond your first LLM
## What you need
Before you start, make sure you have:
* **Node.js 20.9+** (required for Next.js 16)
* **pnpm** (the package manager)
* **A PostgreSQL database** (Supabase recommended)
* **At least one AI provider key** — OpenAI, Google Gemini, Anthropic, Groq, xAI, or DeepSeek
## Setup
The bootstrap wizard handles the core setup for you.
```bash theme={null}
git clone https://github.com/fdarkaou/anotherwrapper-premium your-app-name
cd your-app-name
rm -rf .git
git init -b main
```
This gives you a fresh copy with clean git history.
```bash theme={null}
pnpm bootstrap
```
The wizard walks you through the core setup:
* Checks your Node version
* Runs `pnpm install`
* Sets `NEXT_PUBLIC_APP_URL`
* Auto-generates `BETTER_AUTH_SECRET`
* Configures `DATABASE_URL` and runs migrations
* Sets up your first AI provider and optional extra providers
* Optionally configures Replicate, ElevenLabs, email, OAuth, analytics, storage, payments, and Sentry
```bash theme={null}
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) and you're live.
Prefer to do it yourself? Use `.env.example` as the reference and create `.env.local` manually.
```bash theme={null}
git clone https://github.com/fdarkaou/anotherwrapper-premium your-app-name
cd your-app-name
rm -rf .git
git init -b main
pnpm install
```
```env theme={null}
NEXT_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-secret-here
DATABASE_URL=postgresql://...
```
Plus at least one AI provider:
```env OpenAI theme={null}
OPENAI_API_KEY=sk-...
```
```env Google AI theme={null}
GOOGLE_GENERATIVE_AI_API_KEY=...
```
```env Anthropic theme={null}
ANTHROPIC_API_KEY=sk-ant-...
```
Groq, xAI, and DeepSeek also work if you prefer them as your first provider.
Point `DATABASE_URL` at any PostgreSQL provider. Supabase is recommended — it supports `pgvector` and all the extensions this repo needs.
```bash theme={null}
pnpm db:migrate
```
If you copy the connection string from Supabase, replace the literal `[YOUR-PASSWORD]` placeholder with your actual database password.
For magic link and forgot password:
```env theme={null}
NEXT_PUBLIC_EMAIL_PROVIDER=resend
EMAIL_PROVIDER=resend
AUTH_EMAIL_FROM="YourApp "
```
For Google sign-in:
```env theme={null}
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true
```
Register these callbacks in Google Cloud:
* `http://localhost:3000/api/auth/callback/google`
* `https://yourdomain.com/api/auth/callback/google`
`NEXT_PUBLIC_EMAIL_PROVIDER` controls whether the auth UI exposes magic links and password reset. `EMAIL_PROVIDER` is the server-side provider selection and can override the public value when needed.
```bash theme={null}
pnpm dev
```
## Important things to know
`BETTER_AUTH_SECRET` is required in every environment. Missing it breaks session handling immediately. The bootstrap wizard generates one for you automatically.
The document/RAG features need `pgvector` support in your Postgres database. The repo enables the extension in its migration history, but your database provider must actually allow `CREATE EXTENSION vector`.
Supabase PostgreSQL is the recommended database host. It supports all required extensions out of the box. Neon, Railway, Render, Fly, and self-hosted Postgres all work too.
## Verify your first run
Your setup is working if you can:
* Open the landing page
* Sign in with email/password
* Enter the dashboard
* Use the chat app with your configured AI provider
* Use Marketing Plan or Launch Simulator
* Open the landing page at `/landing`
## What's next?
Understand the PostgreSQL + Drizzle data layer.
Add Google OAuth, magic links, and more.
Enable uploads and file-backed workflows.
Learn how the AI layer works across apps.
Start accepting money with Stripe, LemonSqueezy, or Polar.
Ship it to Vercel when you're ready.
* `BETTER_AUTH_SECRET` is missing
* `DATABASE_URL` is missing
* Email provider configured without `AUTH_EMAIL_FROM`
* Google OAuth callback URLs missing `/api/auth/callback/google`
* Copied `.env.example` to `.env` instead of `.env.local`
* Storage not configured but expecting uploads to work
* `NEXT_PUBLIC_ANALYTICS_PROVIDER` using wrong syntax for multiple providers
# Project Structure
Source: https://docs.anotherwrapper.com/setup/structure
A quick map of the codebase so you always know where to look
Here's the map of your new codebase. This is a single Next.js app (not a monorepo), and everything is organized by **what it does**, not by file type. Let's walk through it.
## Top-Level Folders
This is your Next.js App Router directory. Everything that has a URL lives here.
* `app/(apps)/` -- The main product apps (chat, image studio, voice, etc.)
* `app/auth/` -- Auth UI pages (sign-in, reset password, auth errors)
* `app/page.tsx` -- The signed-in home/dashboard route (`/`)
* `app/landing/` -- The public marketing page
* `app/api/` -- Shared API routes (Better Auth, avatars, payments, OG images)
* `app/blog/` -- Blog pages
Reusable components organized by what they belong to.
* `components/ui/` -- Base UI primitives (buttons, inputs, dialogs)
* `components/landing/` -- Marketing page components
* `components/(apps)/` -- App-specific UI components
* `components/(ui-components)/` -- Shared product UI (auth, payments, blog, alerts, account center)
The brain of your app. Cross-app behavior and integrations live here.
* `lib/ai/` -- Model configuration and AI helpers
* `lib/auth/` -- Server-side auth helpers
* `lib/db/` -- Domain-first database modules + Drizzle client
* `lib/db/schema/` -- Database schema (source of truth)
* `lib/actions/` -- Server actions
* `lib/config/` -- Static registries and app catalogs
* `lib/payments/` -- Provider-agnostic billing logic
* `lib/analytics/` -- Product analytics and event tracking
* `lib/email/` -- Contact sync
* `lib/storage/` -- Object storage
* `lib/rag/` -- Document chunking, embeddings, retrieval, and citations
* `lib/shared/` -- Small cross-domain helpers (formatting, cookies, etc.)
* `lib/integrations/` -- Thin vendor SDK wrappers
* `lib/observability/` -- Sentry and monitoring adapters
Generated and custom SQL migration files. This is the migration history for your database -- don't edit generated files by hand.
MDX blog posts that power the content collections blog pipeline.
* `docs/public/` -- The Mintlify docs site (what you're reading now!)
* `docs/architecture/` -- Internal implementation notes for maintainers
## The Marketing + Product Split
Your repo contains both the public-facing marketing site **and** the signed-in product in one place. Here's how they stay separate:
`app/landing/` and `components/landing/`
`app/(apps)/` and `components/(apps)/`
`lib/` for everything both sides need
This split is great because you can work on "sell the product" and "use the product" without the code getting tangled together.
## Product Apps Included
Your app ships with these product surfaces:
* **Chat** -- AI chat with optional PDF/document context
* **Marketing Plan** -- AI-powered marketing plan generator
* **Launch Simulator** -- Simulate a product launch
* **Image Studio** -- AI image generation
* **Video Studio** -- AI video generation
* **Voice Studio** -- AI voice synthesis
* **Audio** -- Audio transcription and processing
* **Vision** -- Image analysis and understanding
* **Dashboard** -- Account center, credits, and billing
## Where to Start Exploring
New to the codebase? Start with these folders to build your mental model:
* `/app/(apps)/chat` -- The flagship AI app, great for understanding the full pattern
* `/lib/ai` -- How models and providers are configured
* `/lib/rag` -- How document chat works
* `/components/landing` -- How the marketing site is built
* `/lib/payments` -- How billing works
# Troubleshooting
Source: https://docs.anotherwrapper.com/troubleshooting
Quick fixes for the most common issues — you'll be back on track in minutes
Something not working? Don't panic. Most issues come down to a missing env var or a wrong URL. Here's the quick debugging playbook, followed by fixes for every common problem.
## Start here
When something breaks, check in this order. It solves 90% of issues:
Missing or misspelled env vars are the #1 cause of issues. Double-check `.env.local`.
Make sure your API keys are valid and have the right permissions.
Auth callbacks, payment webhooks, and OAuth redirect URIs must match your actual domain.
Many features silently fail without properly configured S3-compatible storage.
Some features depend on more than one provider (e.g., PDF chat needs both storage AND OpenAI).
Did you restart your dev server after changing `.env.local`? Next.js doesn't hot-reload env vars — you need to restart.
## Common issues
Check these env vars first:
* `BETTER_AUTH_SECRET`
* `NEXT_PUBLIC_APP_URL`
* `BETTER_AUTH_URL` (if you set it)
* `NEXT_PUBLIC_EMAIL_PROVIDER`
* `EMAIL_PROVIDER`
* `AUTH_EMAIL_FROM`
* `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`
* `NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=true`
For Google OAuth specifically:
* Your callback URL must be `/api/auth/callback/google`
* Register both your localhost AND production domain callbacks
* The domain you're testing on must match `NEXT_PUBLIC_APP_URL`
This is almost always a **redirect mismatch**. Check:
* `NEXT_PUBLIC_APP_URL`
* `BETTER_AUTH_URL` (if you use it)
* The domain you clicked the link on
* The callback URL encoded in the email link
Better Auth verifies callback and origin handling inside the app. If the auth link points to a different origin than the one you're using, sign-in will start but never finish.
Check:
* `NEXT_PUBLIC_EMAIL_PROVIDER` — must be `loops`, `resend`, or `brevo` if you expect those auth modes in the UI
* `EMAIL_PROVIDER` — must be `loops`, `resend`, or `brevo` (not `none`)
* `AUTH_EMAIL_FROM` — required for all auth emails
* The provider API key for whichever provider you chose
Provider-specific gotchas:
* **Loops** also needs `LOOPS_AUTH_MAGIC_LINK_TRANSACTIONAL_ID` and `LOOPS_AUTH_RESET_PASSWORD_TRANSACTIONAL_ID`
* **Resend** and **Brevo** just need the provider key + `AUTH_EMAIL_FROM`
* `EMAIL_PROVIDER=none` completely disables outbound auth emails
Usually one of these:
* The provider API key is missing from `.env.local`
* The model isn't listed in `lib/ai/models.ts`
* The app doesn't use that provider path
* The model has fewer capabilities than you expected (e.g., text but not browsing, text but not vision)
PDF chat depends on multiple things working together:
* Storage must be configured (S3-compatible)
* OpenAI embeddings must be available (`OPENAI_API_KEY`)
* The document must be uploaded AND indexed
* The document must be attached to the active chat
If you can upload but don't get useful citations:
* Embeddings may not have been created
* Retrieval may have found no strong matches
* OpenAI might be missing, so the vector step can't run
See [Storage](/services/storage) and [Vector RAG](/services/vector-rag) for the full setup.
Check your storage env vars:
```env theme={null}
STORAGE_REGION=...
STORAGE_ACCESS_KEY=...
STORAGE_SECRET_KEY=...
STORAGE_ENDPOINT=...
STORAGE_BUCKET=...
STORAGE_PUBLIC_URL=...
```
Many app flows depend on file storage. If storage isn't configured, the apps will load but uploads and saved outputs will silently fail.
Check:
* `NEXT_PUBLIC_PAYMENT_PROVIDER` matches your active provider
* Provider webhook secret is set correctly
* Webhook URL is correct: `/api/payments/stripe`, `/api/payments/lemonsqueezy`, or `/api/payments/polar`
* Hosted checkout URLs are pointing to real products
* Product/variant mapping is configured (via metadata or env map)
If checkout works but nothing happens after payment, the webhook is usually the issue. The webhook is what tells your app the payment actually happened.
Check:
* `NEXT_PUBLIC_ANALYTICS_PROVIDER` — spelled correctly?
* Provider-specific env vars (PostHog key, Plausible domain, DataFast ID)
* Did you restart the dev server?
For multiple providers, use a comma-separated value:
```env theme={null}
NEXT_PUBLIC_ANALYTICS_PROVIDER=posthog,datafast
```
Voice Studio has several separate API paths: voices, text-to-speech, speech-to-speech, music, and sound effects.
If one mode works and another fails, the whole app isn't broken — check the specific mode's requirements and your ElevenLabs access level for that feature.
This is usually a branding/content issue, not a runtime bug. Check:
* `config.ts` — site name, URLs, links
* Landing page copy and pricing sections
* App descriptions and metadata
* Docs content
Since the repo includes both product and marketing surfaces, outdated copy in one place can make everything feel inconsistent.
Make sure:
* Mintlify is pointed at the `anotherwrapper-premium` repo
* The docs root is set to `docs/public`
* The nav in `docs/public/mint.json` matches the files that actually exist
Review all env vars and config surfaces.
Full pre-launch verification checklist.