presenton
Applicationpresenton/presenton
Open-source AI presentation generator with self-hosted and API options.
Overview
Presenton is an open-source AI-powered presentation generator that works with multiple AI providers (OpenAI, Gemini, Ollama, etc.). It offers both a self-hosted web version via Docker and a desktop app for Windows, Mac, and Linux. Users can generate presentations from prompts or documents, export to PPTX or PDF, and maintain full control over data and models.
README Preview
\n \n\n\n\n Quickstart ·\n Docs ·\n Youtube ·\n Discord\n\n\n\n \n \n \n\n\n# Open-Source AI Presentation Generator and API (Gamma, Beautiful AI, Decktopus Alternative)\n\n\n\n### ✨ Why Presenton\n\nNo SaaS lock-in · No forced subscriptions · Full control over models and data\n\nWhat makes Presenton different?\n\n- Use Fully **self-hosted** in Web through [Docker Package](https://docs.presenton.ai/v3/get-started/quickstart)\n- Or Download [Desktop App](https://presenton.ai/download) (Mac, Windows & Linux)\n- Works with OpenAI, Gemini, Vertex AI, Azure OpenAI, Amazon Bedrock, Fireworks, Together AI, Anthropic, LM Studio, Ollama, or custom models\n- Comes with AI Presentation Generation API\n- Fully open-source (Apache 2.0)\n- Works with your own design/templates\n- **Fully editable PPTX export**\n\n> [!TIP]\n> **Star us!** A ⭐ shows your support and encourages us to keep building! 😇\n\n\n \n\n\n#\n\n### 🎛 Features\n\n\n \n\n\n#\n\n### 💻 Presenton Desktop\n\nCreate AI-powered presentations using your own model provider (BYOK) or run everything locally on your own machine for full control and data privacy.\n\n\n \n \n \n\n\n**Available Platforms**\n\n\n\nPlatform\nArchitecture\nPackage\nDownload\n\n\n\nmacOS\nApple Silicon / Intel\n.dmg\nDownload ↗\n\n\n\nWindows\nx64\n.exe\nDownload ↗\n\n\n\nLinux\nx64\n .deb\nDownload ↗\n\n\n\n\n\n**Deploy to Cloud Providers**\n\n\n \n \n \n \n \n \n\n\n#\n\nPresenton gives you complete control over your AI presentation workflow. Choose your models, customize your experience, and keep your data private.\n\n- Custom Templates & Themes — Create unlimited presentation designs with HTML and Tailwind CSS\n- AI Template Generation — Create presentation templates from existing Powerpoint documents.\n- Flexible Generation — Build presentations from prompts or uploaded documents\n- Export Ready — Save as PowerPoint (PPTX) and PDF with professional formatting\n- Built-In MCP Server — Generate pr
FAQ (5)
TroubleshootingWhy does slides_markdown generation in Presenton always return a 500 error with 'to_string() got unexpected keyword argument with_schema'?
The error occurs because get_messages_for_slides_markdown() passes with_schema=True to PresentationLayoutModel.to_string(), which does not accept that argument. Immediate workaround: edit servers/fastapi/templates/presentation_layout.py and change the method signature to def to_string(self, with_schema: bool = False) -> str:. This suppresses the crash and allows slide generation. Alternatively, remove with_schema=True from the call in generate_presentation_structure.py line 129. A permanent fix should either implement schema output or align the calls.
TroubleshootingHow to get real-time UI updates when AI agent edits a presentation?
Upgrade to the latest version of Presenton. Recent releases automatically refresh slides after tool actions, stream chat status, highlight the edited slide with follow mode, and include a thumbnail panel to select slides. No manual reload required.
TroubleshootingHow to open an app on macOS that shows 'Apple could not verify ... is free of malware'?
This happens because the app is not notarized by Apple. To bypass Gatekeeper, install the app from the .dmg (drag to Applications). Then, in Finder → Applications, right-click (or Control-click) the app and select Open. Confirm Open in the dialog. You only need to do this once; subsequent launches work normally. Alternatively, go to System Settings → Privacy & Security and click 'Open Anyway'.
TroubleshootingWhy does Presenton's operation executor receipt always return empty changedSlideIds for slide content updates?
This is a self-comparison diff bug in _apply_operations() (services/operation_executor.py). The executor mutates the same ORM SlideModel instances that current_slides references, so the post-apply diff compares each slide object to itself and always yields changedSlideIds: [] — even though the mutation applied correctly (revision bumps, DB and snapshot updated). Structural ops (insert/duplicate/move) are unaffected because they change slide ids/count. Fix: snapshot the baseline slide values BEFORE applying operations, then diff staged slides against the snapshot instead of re-reading the mutated ORM objects:
current = document_snapshot(presentation, current_slides)
baseline_slide_values = {str(slide.id): _slide_value(slide) for slide in current_slides}
# ... apply operations to staged_slides ...
changed_slide_ids = [
slide_id for slide_id in desired_slide_ids
if slide_id in existing_slide_ids
and _slide_value(next(s for s in staged_slides if str(s.id) == slide_id))
!= baseline_slide_values.get(slide_id)
]
After the fix, in-place mutations (e.g. UpdateSlide setting speaker_note) correctly return the mutated slide's id in both the operation result and the persisted receipt. Verified fix is available in PR #913 (branch audit/r0-persistence-executor).
TroubleshootingHow to fix MCP client rejecting Presenton task status timestamps with RFC3339 date-time schema validation error?
Presenton (v0.9.11-beta) returns tz-naive timestamps like 2026-09-25T14:02:03.123456 from get_job_status and template list endpoints because SQLite does not preserve timezone info despite DateTime(timezone=True) columns, so strict MCP clients fail validation against the declared format: date-time schema. Fix: add a UTC-aware field serializer to both AsyncTaskModel (servers/fastapi/models/sql/async_task.py) and TemplateListItem (servers/fastapi/api/v1/ppt/endpoints/template.py):
from datetime import datetime, timezone
from pydantic import field_serializer
@field_serializer("created_at", "updated_at")
def _serialize_timestamps(self, value: datetime | str) -> str:
if isinstance(value, str):
return value
if value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
return value.isoformat()
This makes timestamps RFC3339-compliant (e.g. 2026-09-25T14:02:03.123456+00:00) and fixes both REST/OpenAPI and MCP tool output in one place.