Image Carousel
Build multi-slide image posts for Instagram carousels and TikTok photo mode. Agent-driven, like every other Montaj project type.
Image Carousel
A carousel project is a deck of static slides — not a video. Use it for multi-image Instagram posts or TikTok photo mode. If you want stitched video, use one of the video project types (editing, music_video, ai_video) instead.
Like every other Montaj project type, carousels are agent-driven. You describe what you want, the agent builds the slides, you tweak in the canvas if needed.
Aspect Ratios
Pick one at project creation. The ratio is locked — it applies to every slide and cannot be changed mid-project. Instagram and TikTok crop or reject slides that don't match the declared ratio.
| Preset | Dimensions | Best for |
|---|---|---|
square | 1080 × 1080 (1:1) | Instagram square |
portrait | 1080 × 1350 (4:5) | Instagram portrait — dominant carousel format |
vertical | 1080 × 1920 (9:16) | TikTok photo mode, IG/Threads stories |
Creating a Carousel Project
From the UI
/projects/new → pick Carousel as the workflow → fill in:
- Project name (optional).
- Profile (optional) — attaches a creator profile so the agent gets your asset library and style notes.
- Prompt (required) — describe the carousel: topic, vibe, what each slide should cover.
- Aspect ratio — square, portrait, or vertical.
- Assets — drop reference images the agent can use as backgrounds or style refs. They're copied into the project workspace and surface in
project.assets[].
Hit "Create carousel" and you land on the pending screen — a copy-this-to-your-agent panel pointing at the root SKILL. Send the message to your agent and it'll start building slides.
CLI
montaj init \
--workflow carousel \
--carousel-aspect portrait \
--prompt "5-slide skincare routine, soft pastels, minimalist" \
--asset ./refs/product1.jpg \
--asset ./refs/lifestyle.jpg--carousel-aspect accepts square, portrait, or vertical. Defaults to square. --prompt is required. --asset (repeatable) attaches reference images at intake.
HTTP API
POST /api/run
{
"workflow": "carousel",
"carouselAspect": "portrait",
"prompt": "5-slide skincare routine, soft pastels, minimalist",
"assets": ["/abs/path/refs/product1.jpg", "/abs/path/refs/lifestyle.jpg"]
}How the Agent Builds Slides
New carousels start at status: "pending". The pending screen tells you to "Send this to your agent" with a copy panel that contains exactly:
"There is a new project pending:
"<your name>". Please see@<root SKILL>and start. Talk to me if you run into questions."
The agent reads the root SKILL → resolves the carousel workflow → loads skills/carousel/SKILL.md (the callable carousel skill). That skill teaches it the slide schema, how to generate images, how to author overlays, and how to render. The agent then:
- Calls
generate_imageto produce slide backgrounds at the project's resolution. - Authors overlays as JSX modules (per the
write-overlayskill) — text, callouts, badges, anything. - Builds the
project.slides[]array with image and overlay elements. - PUTs the updated project via
PUT /api/projects/{id}. The pending screen flips into a "live status" mode showing the agent's progress. - When done, sets
status: "final"and you see the finished slides in the canvas.
While the agent is working, you stay on the pending screen — you'll see streaming log lines from the agent (POST /api/projects/{id}/log) and the slide thumbnails in the left grid populate live as project.json updates.
Editing on the Canvas
Once status flips off pending, the canvas opens.
[Slide grid] | [Canvas + hint] | [Property panel] + [Asset library]- Slide grid (left) — drag thumbnails to reorder, hover for duplicate/delete.
- Canvas (center) — the active slide at native resolution, scaled to fit. Click any element to select. Drag to reposition with snap guides at the slide's center axes and at all four edges (pink lines appear when you're within 2.5% of a snap target). Resize via the eight handles around a selected element. Rotate via the handle above; rotation snaps to 0°/90°/180°/270° within ±5°. Double-click an overlay's text to edit in place.
- Property panel (right) — exact
x/y/w/h/rotationfields for the selected element, base color picker for the slide, per-prop editors for overlay props (text, color, accent, etc.). - Asset library (right rail) — the same
AssetsPanelyou used at intake. Drop more reference images mid-edit; the agent can use them in subsequent runs.
A small hint sits below the canvas:
"Drag elements to reposition. Ask the agent for any other changes."
The canvas is for polish. For structural edits (new slide, different copy, regenerate the background image), tell the agent.
Overlay frame field
Carousels have no time axis. When an overlay element is rendered for a slide, it renders at a single fixed frame instead of animating. Each overlay element has a frame property the agent sets at authoring time. Default: the overlay's declared staticFrame export, or duration - 1 if it doesn't declare one. Adjust it via the property panel if the default lands mid-animation and looks wrong as a still.
Refresh + Render buttons
In the top-left corner of the editing area: a Refresh button that re-fetches project.json from disk (useful if you bypassed SSE — e.g. an external script edited the file). In the top-right: a Render button (more on that below).
Rendering
Click Render in the top-right of the editing area, or run from CLI:
montaj render <project>Either way, the renderer (render-carousel.js) launches Puppeteer, bundles each slide's JSX via esbuild, screenshots at the project's native resolution, and writes:
<project>/render/
slide_01.png
slide_02.png
slide_03.png
...
manifest.jsonPNGs are zero-padded, 1-based. manifest.json lists each file with its dimensions and slide index — useful for downstream tooling (uploaders, schedulers) but not required for the user-facing flow.
The render modal in the UI streams the renderer's log lines. When done, it opens a full-screen overlay showing every slide as a clickable thumbnail and a Download all (.zip) button that bundles every PNG into a single archive (<project-name>-slides.zip). Drop the zip onto your phone for upload.
POST /api/projects/{id}/render dispatches by projectType — carousel projects run render-carousel.js; everything else runs render.js.
What changes vs. video projects
The pieces that don't apply to carousels:
- No
tracks,clips,audio, orfps— carousels have no time axis. - No live preview / scrubber — the canvas is the preview.
- No
compose.js/ segment encoder / ffmpeg — Puppeteer screenshots are the entire render path.
The pieces that do apply (unchanged):
- Project workspace layout, git versioning,
project.json+ SSE updates. - Profile attach:
--profile <name>snapshots the profile's asset library andstyle_profile.mdinto the project, same as for video. - The same overlay system. Overlays you author for carousel slides are JSX modules; the agent writes them per the
write-overlayskill. - Reference assets via
project.assets[]. The agent can pass these togenerate_imageas--ref-imagefor style consistency.
Deleting manifest.json from your download
The Render modal's "Download all (.zip)" excludes manifest.json — it's a renderer-side output meant for agents and CLI tooling, not what a human downloading the archive cares about. The file still exists on disk in <project>/render/manifest.json if you want it.