The Problem with Slide Decks
We needed marketing slides. The kind you send to potential partners, drop into a pitch deck, or post on social media. Ten slides covering the product, features, integrations, use cases, and a CTA.
The obvious path: open Figma, drag boxes around, export PNGs. Update copy? Back to Figma. New product launched? Back to Figma. Font change? Back to Figma for every slide.
We already had an API that composites images from JSON layers. We already had layout layers that arrange children with gap, alignment, padding, and border radius. The slides were just images with text and colored boxes.
So we built them with our own API.
What We Built
Ten slides, generated from code, pixel-identical every time:
Each slide is a single API call. The canvas is 2540x1520 (2x resolution for crisp output on retina screens). The whole deck generates in under 2 seconds.
Layout Layers Are the Key
Most of the slides use layout layers — a layer type that arranges children horizontally or vertically, like CSS flexbox. You set direction, gap, alignment, padding, border radius, and background color. Children auto-size or use fixed dimensions.
This is what makes the slides possible without manual pixel positioning. A trust badge pill is a horizontal layout with an icon and auto-width text:
curl -X POST https://api.iterationlayer.com/image-generation/v1/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dimensions": { "width": 2540, "height": 1520 },
"layers": [
{
"index": 0,
"type": "solid-color",
"hex_color": "#0ea5e9"
},
{
"index": 1,
"type": "layout",
"direction": "horizontal",
"gap": 8,
"vertical_alignment": "center",
"background_color": "#38bdf8",
"padding_top": 10,
"padding_bottom": 10,
"padding_left": 18,
"padding_right": 22,
"border_radius": 20,
"position": { "x": 112.0, "y": 430.0 },
"layers": [
{
"type": "image",
"file": {
"type": "base64",
"name": "shield.svg",
"base64": "<shield-icon-svg-base64>"
},
"dimensions": { "width": 18, "height": 18 }
},
{
"type": "text",
"text": "Zero data retention",
"font_name": "PlusJakartaSans",
"font_size_in_px": 15,
"font_weight": "Bold",
"text_color": "#FFFFFF",
"dimensions": { "width": "auto", "height": 22 }
}
]
}
]
}'import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });
const trustPill = (iconBase64: string, label: string) => ({
type: "layout" as const,
direction: "horizontal" as const,
gap: 8,
vertical_alignment: "center" as const,
background_color: "#38bdf8",
padding_top: 10,
padding_bottom: 10,
padding_left: 18,
padding_right: 22,
border_radius: 20,
layers: [
{
type: "image" as const,
file: { type: "base64", name: "icon.svg", base64: iconBase64 },
dimensions: { width_in_px: 18, height_in_px: 18 },
},
{
type: "text" as const,
text: label,
font_name: "PlusJakartaSans",
font_size_in_px: 15,
font_weight: "Bold",
text_color: "#FFFFFF",
dimensions: { width_in_px: "auto", height_in_px: 22 },
},
],
});
const result = await client.generateImage({
dimensions: { width_in_px: 2540, height_in_px: 1520 },
layers: [
{ index: 0, type: "solid-color", hex_color: "#0ea5e9" },
{
index: 1,
type: "layout",
direction: "horizontal",
gap: 24,
position: { x_in_px: 112, y_in_px: 430 },
layers: [
trustPill(shieldIconBase64, "Zero data retention"),
trustPill(flagIconBase64, "Made & hosted in the EU"),
trustPill(cardIconBase64, "No credit card required"),
],
},
],
});from iterationlayer import IterationLayer
client = IterationLayer(api_key="YOUR_API_KEY")
def trust_pill(icon_base64, label):
return {
"type": "layout",
"direction": "horizontal",
"gap": 8,
"vertical_alignment": "center",
"background_color": "#38bdf8",
"padding_top": 10,
"padding_bottom": 10,
"padding_left": 18,
"padding_right": 22,
"border_radius": 20,
"layers": [
{
"type": "image",
"file": {"type": "base64", "name": "icon.svg", "base64": icon_base64},
"dimensions": {"width_in_px": 18, "height_in_px": 18},
},
{
"type": "text",
"text": label,
"font_name": "PlusJakartaSans",
"font_size_in_px": 15,
"font_weight": "Bold",
"text_color": "#FFFFFF",
"dimensions": {"width_in_px": "auto", "height_in_px": 22},
},
],
}
result = client.generate_image(
dimensions={"width_in_px": 2540, "height_in_px": 1520},
layers=[
{"index": 0, "type": "solid-color", "hex_color": "#0ea5e9"},
{
"index": 1,
"type": "layout",
"direction": "horizontal",
"gap": 24,
"position": {"x_in_px": 112, "y_in_px": 430},
"layers": [
trust_pill(shield_icon_base64, "Zero data retention"),
trust_pill(flag_icon_base64, "Made & hosted in the EU"),
trust_pill(card_icon_base64, "No credit card required"),
],
},
],
)package main
import il "github.com/iterationlayer/sdk-go"
func trustPill(iconBase64, label string) il.LayoutLayer {
padding10 := 10
padding18 := 18
padding22 := 22
radius20 := 20
gap8 := 8
return il.LayoutLayer{
Type: "layout",
Direction: "horizontal",
Gap: &gap8,
VerticalAlignment: "center",
BackgroundColor: "#38bdf8",
PaddingTop: &padding10,
PaddingBottom: &padding10,
PaddingLeft: &padding18,
PaddingRight: &padding22,
BorderRadius: &radius20,
Layers: []il.Layer{
il.ImageLayer{
Type: "image",
File: il.FileInput{Type: "base64", Name: "icon.svg", Base64: iconBase64},
Dimensions: &il.Dimensions{WidthInPx: 18, HeightInPx: 18},
},
il.TextLayer{
Type: "text", Text: label, FontName: "PlusJakartaSans",
FontSizeInPx: 15, FontWeight: "Bold", TextColor: "#FFFFFF",
Dimensions: &il.Dimensions{HeightInPx: 22},
},
},
}
}
func main() {
client := il.NewClient("YOUR_API_KEY")
gap24 := 24
result, _ := client.GenerateImage(il.GenerateImageRequest{
Dimensions: il.Dimensions{WidthInPx: 2540, HeightInPx: 1520},
Layers: []il.Layer{
il.NewSolidColorBackgroundLayer(0, "#0ea5e9"),
il.LayoutLayer{
Type: "layout", Index: 1, Direction: "horizontal",
Gap: &gap24, Position: &il.Position{X: 112, Y: 430},
Layers: []il.Layer{
trustPill(shieldIconBase64, "Zero data retention"),
trustPill(flagIconBase64, "Made & hosted in the EU"),
trustPill(cardIconBase64, "No credit card required"),
},
},
},
})
_ = result
}
The "auto" width on the text layer is what makes this work. The text renders at its natural width, then the layout wraps it with padding and a background. No measuring, no calculating pixel offsets.
Feature Cards with Background Layers
The product slides show feature cards in a 2x3 grid. Each card is a vertical layout with an icon, title, and description — positioned on the canvas with fixed dimensions:
curl -X POST https://api.iterationlayer.com/image-generation/v1/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dimensions": { "width": 2540, "height": 1520 },
"layers": [
{
"index": 0,
"type": "solid-color",
"hex_color": "#0ea5e9"
},
{
"index": 1,
"type": "layout",
"direction": "vertical",
"gap": 32,
"background_color": "#0284c7",
"padding": 56,
"border_radius": 32,
"dimensions": { "width": 780, "height": 480 },
"position": { "x": 112.0, "y": 480.0 },
"layers": [
{
"type": "image",
"file": { "type": "base64", "name": "icon.svg", "base64": "<icon-base64>" },
"dimensions": { "width": 56, "height": 56 }
},
{
"type": "text",
"text": "Schema-Driven Extraction",
"font_name": "PlusJakartaSans",
"font_size_in_px": 44,
"font_weight": "Bold",
"text_color": "#FFFFFF",
"dimensions": { "width": 668, "height": 68 }
},
{
"type": "text",
"text": "Define 17 typed fields and get structured JSON back.",
"font_name": "PlusJakartaSans",
"font_size_in_px": 36,
"text_color": "#e0f2fe",
"dimensions": { "width": 668, "height": 140 }
}
]
}
]
}'import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });
const featureCard = (iconBase64: string, title: string, description: string) => ({
type: "layout" as const,
direction: "vertical" as const,
gap: 32,
background_color: "#0284c7",
padding: 56,
border_radius: 32,
layers: [
{
type: "image" as const,
file: { type: "base64", name: "icon.svg", base64: iconBase64 },
dimensions: { width_in_px: 56, height_in_px: 56 },
},
{
type: "text" as const,
text: title,
font_name: "PlusJakartaSans",
font_size_in_px: 44,
font_weight: "Bold",
text_color: "#FFFFFF",
dimensions: { width_in_px: 668, height_in_px: 68 },
},
{
type: "text" as const,
text: description,
font_name: "PlusJakartaSans",
font_size_in_px: 36,
text_color: "#e0f2fe",
dimensions: { width_in_px: 668, height_in_px: 140 },
},
],
});
const result = await client.generateImage({
dimensions: { width_in_px: 2540, height_in_px: 1520 },
layers: [
{ index: 0, type: "solid-color", hex_color: "#0ea5e9" },
{
index: 1,
...featureCard(iconBase64, "Schema-Driven Extraction", "Define 17 typed fields and get structured JSON back."),
dimensions: { width_in_px: 780, height_in_px: 480 },
position: { x_in_px: 112, y_in_px: 480 },
},
],
});from iterationlayer import IterationLayer
client = IterationLayer(api_key="YOUR_API_KEY")
def feature_card(icon_base64, title, description):
return {
"type": "layout",
"direction": "vertical",
"gap": 32,
"background_color": "#0284c7",
"padding": 56,
"border_radius": 32,
"layers": [
{
"type": "image",
"file": {"type": "base64", "name": "icon.svg", "base64": icon_base64},
"dimensions": {"width_in_px": 56, "height_in_px": 56},
},
{
"type": "text",
"text": title,
"font_name": "PlusJakartaSans",
"font_size_in_px": 44,
"font_weight": "Bold",
"text_color": "#FFFFFF",
"dimensions": {"width_in_px": 668, "height_in_px": 68},
},
{
"type": "text",
"text": description,
"font_name": "PlusJakartaSans",
"font_size_in_px": 36,
"text_color": "#e0f2fe",
"dimensions": {"width_in_px": 668, "height_in_px": 140},
},
],
}
result = client.generate_image(
dimensions={"width_in_px": 2540, "height_in_px": 1520},
layers=[
{"index": 0, "type": "solid-color", "hex_color": "#0ea5e9"},
{
"index": 1,
**feature_card(icon_base64, "Schema-Driven Extraction", "Define 17 typed fields and get structured JSON back."),
"dimensions": {"width_in_px": 780, "height_in_px": 480},
"position": {"x_in_px": 112, "y_in_px": 480},
},
],
)package main
import il "github.com/iterationlayer/sdk-go"
func featureCard(iconBase64, title, description string) il.LayoutLayer {
gap := 32
padding := 56
radius := 32
return il.LayoutLayer{
Type: "layout",
Direction: "vertical",
Gap: &gap,
BackgroundColor: "#0284c7",
Padding: &padding,
BorderRadius: &radius,
Layers: []il.Layer{
il.ImageLayer{
Type: "image",
File: il.FileInput{Type: "base64", Name: "icon.svg", Base64: iconBase64},
Dimensions: &il.Dimensions{WidthInPx: 56, HeightInPx: 56},
},
il.TextLayer{
Type: "text", Text: title, FontName: "PlusJakartaSans",
FontSizeInPx: 44, FontWeight: "Bold", TextColor: "#FFFFFF",
Dimensions: &il.Dimensions{WidthInPx: 668, HeightInPx: 68},
},
il.TextLayer{
Type: "text", Text: description, FontName: "PlusJakartaSans",
FontSizeInPx: 36, TextColor: "#e0f2fe",
Dimensions: &il.Dimensions{WidthInPx: 668, HeightInPx: 140},
},
},
}
}
func main() {
client := il.NewClient("YOUR_API_KEY")
card := featureCard(iconBase64, "Schema-Driven Extraction", "Define 17 typed fields and get structured JSON back.")
card.Index = 1
card.Dimensions = &il.Dimensions{WidthInPx: 780, HeightInPx: 480}
card.Position = &il.Position{X: 112, Y: 480}
result, _ := client.GenerateImage(il.GenerateImageRequest{
Dimensions: il.Dimensions{WidthInPx: 2540, HeightInPx: 1520},
Layers: []il.Layer{
il.NewSolidColorBackgroundLayer(0, "#0ea5e9"),
card,
},
})
_ = result
}
When a card has dimensions set, the layout clips to that size and the border_radius is applied to the fixed container — not the auto-sized content. This means cards in a grid all have identical dimensions regardless of content length.
Centering with Fixed Dimensions
The CTA slide centers the trust badges horizontally. Layout layers don’t center children by default — they stack from the start. But when you set dimensions to the full slide width and horizontal_alignment to "center", the layout positions the auto-sized content in the middle:
curl -X POST https://api.iterationlayer.com/image-generation/v1/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dimensions": { "width": 2540, "height": 1520 },
"layers": [
{
"index": 0,
"type": "solid-color",
"hex_color": "#0ea5e9"
},
{
"index": 1,
"type": "layout",
"direction": "horizontal",
"horizontal_alignment": "center",
"gap": 24,
"dimensions": { "width": 2540, "height": 100 },
"position": { "x": 0.0, "y": 860.0 },
"layers": [
{
"type": "layout",
"direction": "horizontal",
"gap": 8,
"vertical_alignment": "center",
"background_color": "#38bdf8",
"padding": 10,
"border_radius": 20,
"layers": [
{
"type": "text",
"text": "Zero data retention",
"font_name": "PlusJakartaSans",
"font_size_in_px": 15,
"font_weight": "Bold",
"text_color": "#FFFFFF",
"dimensions": { "width": "auto", "height": 22 }
}
]
}
]
}
]
}'import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });
const result = await client.generateImage({
dimensions: { width_in_px: 2540, height_in_px: 1520 },
layers: [
{ index: 0, type: "solid-color", hex_color: "#0ea5e9" },
{
index: 1,
type: "layout",
direction: "horizontal",
horizontal_alignment: "center",
gap: 24,
dimensions: { width_in_px: 2540, height_in_px: 100 },
position: { x_in_px: 0, y_in_px: 860 },
layers: [
trustPill(shieldBase64, "Zero data retention"),
trustPill(flagBase64, "Made & hosted in the EU"),
trustPill(cardBase64, "No credit card required"),
],
},
],
});from iterationlayer import IterationLayer
client = IterationLayer(api_key="YOUR_API_KEY")
result = client.generate_image(
dimensions={"width_in_px": 2540, "height_in_px": 1520},
layers=[
{"index": 0, "type": "solid-color", "hex_color": "#0ea5e9"},
{
"index": 1,
"type": "layout",
"direction": "horizontal",
"horizontal_alignment": "center",
"gap": 24,
"dimensions": {"width_in_px": 2540, "height_in_px": 100},
"position": {"x_in_px": 0, "y_in_px": 860},
"layers": [
trust_pill(shield_base64, "Zero data retention"),
trust_pill(flag_base64, "Made & hosted in the EU"),
trust_pill(card_base64, "No credit card required"),
],
},
],
)package main
import il "github.com/iterationlayer/sdk-go"
func main() {
client := il.NewClient("YOUR_API_KEY")
gap24 := 24
result, _ := client.GenerateImage(il.GenerateImageRequest{
Dimensions: il.Dimensions{WidthInPx: 2540, HeightInPx: 1520},
Layers: []il.Layer{
il.NewSolidColorBackgroundLayer(0, "#0ea5e9"),
il.LayoutLayer{
Type: "layout", Index: 1, Direction: "horizontal",
HorizontalAlignment: "center", Gap: &gap24,
Dimensions: &il.Dimensions{WidthInPx: 2540, HeightInPx: 100},
Position: &il.Position{X: 0, Y: 860},
Layers: []il.Layer{
trustPill(shieldBase64, "Zero data retention"),
trustPill(flagBase64, "Made & hosted in the EU"),
trustPill(cardBase64, "No credit card required"),
},
},
},
})
_ = result
}
This is the same pattern you’d use for centering a button, a logo, or any group of elements on a slide.
The Generative Wave Background
Every slide shares the same wave background — a deterministic SVG pattern seeded by a fixed string. The algorithm stacks wave bands with varying amplitude and color, rendered as Catmull-Rom spline paths. We covered the wave generator in How We Generate OG Images with Our Own API.
The wave SVG is rendered as a low-opacity image layer on top of a solid sky-blue background. These two layers appear at the bottom of every slide:
curl -X POST https://api.iterationlayer.com/image-generation/v1/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dimensions": { "width": 2540, "height": 1520 },
"layers": [
{
"index": 0,
"type": "solid-color",
"hex_color": "#0ea5e9"
},
{
"index": 1,
"type": "image",
"file": { "type": "base64", "name": "waves.svg", "base64": "<wave-svg-base64>" },
"position": { "x": 0.0, "y": 0.0 },
"dimensions": { "width": 2540, "height": 1520 },
"opacity": 10
}
]
}'import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });
const waveSvgBase64 = btoa(generateWaveSvg("marketing-slides", 2540, 1520));
const result = await client.generateImage({
dimensions: { width_in_px: 2540, height_in_px: 1520 },
layers: [
{ index: 0, type: "solid-color", hex_color: "#0ea5e9" },
{
index: 1,
type: "image",
file: { type: "base64", name: "waves.svg", base64: waveSvgBase64 },
position: { x_in_px: 0, y_in_px: 0 },
dimensions: { width_in_px: 2540, height_in_px: 1520 },
opacity: 10,
},
],
});import base64
from iterationlayer import IterationLayer
client = IterationLayer(api_key="YOUR_API_KEY")
wave_svg_base64 = base64.b64encode(generate_wave_svg("marketing-slides", 2540, 1520)).decode()
result = client.generate_image(
dimensions={"width_in_px": 2540, "height_in_px": 1520},
layers=[
{"index": 0, "type": "solid-color", "hex_color": "#0ea5e9"},
{
"index": 1,
"type": "image",
"file": {"type": "base64", "name": "waves.svg", "base64": wave_svg_base64},
"position": {"x_in_px": 0, "y_in_px": 0},
"dimensions": {"width_in_px": 2540, "height_in_px": 1520},
"opacity": 10,
},
],
)package main
import (
"encoding/base64"
il "github.com/iterationlayer/sdk-go"
)
func main() {
client := il.NewClient("YOUR_API_KEY")
waveSvgBase64 := base64.StdEncoding.EncodeToString(generateWaveSvg("marketing-slides", 2540, 1520))
opacity := 10
result, _ := client.GenerateImage(il.GenerateImageRequest{
Dimensions: il.Dimensions{WidthInPx: 2540, HeightInPx: 1520},
Layers: []il.Layer{
il.NewSolidColorBackgroundLayer(0, "#0ea5e9"),
il.ImageLayer{
Type: "image",
Index: 1,
File: il.FileInput{Type: "base64", Name: "waves.svg", Base64: waveSvgBase64},
Position: &il.Position{X: 0, Y: 0},
Dimensions: &il.Dimensions{WidthInPx: 2540, HeightInPx: 1520},
Opacity: &opacity,
},
},
})
_ = result
}Same input, same output. The slides are byte-identical across runs.
Why Not Figma
A few reasons:
- Copy changes are code changes. A headline update is a string edit, not a design tool session. It goes through code review like everything else.
- New products are automatic. When we launched Sheet Generation, the slide appeared in the deck without anyone opening a design tool. The product data already existed — the slide just rendered it.
- Brand consistency is enforced by code. Every slide uses the same padding, the same font sizes, the same color tokens. There’s no “someone forgot to update the font on slide 7” problem.
- It’s version controlled. Every slide change is a git commit with a diff you can read.
The tradeoff is that design iteration is slower. You can’t drag a box and see the result instantly. But for slides that follow a template — which most marketing slides do — the code-first approach is faster after the initial setup.
Try It
The Image Generation API docs cover all layer types including layouts. The Generate Product Slide recipe shows a complete slide with pills, feature lists, and a CTA button.
If you’re generating images that follow a pattern — slides, social cards, certificates, tickets — layout layers replace the pixel math with structured composition. Define the structure once, swap the data.