Bulk Personalized Image & GIF Generator from Lottie Template avatar

Bulk Personalized Image & GIF Generator from Lottie Template

Pricing

from $5.00 / 1,000 image generateds

Go to Apify Store
Bulk Personalized Image & GIF Generator from Lottie Template

Bulk Personalized Image & GIF Generator from Lottie Template

Returns a personalized PNG and/or GIF for every row of your data, rendered from one Lottie template (Bodymovin JSON or .lottie): dynamic text, colors and images per row, from JSON, a CSV URL or an Apify dataset. Template-based, not AI. No browser, no MP4. $0.005 per image; failed rows are free.

Pricing

from $5.00 / 1,000 image generateds

Rating

0.0

(0)

Developer

Djam

Djam

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

19 days ago

Last modified

Share

Returns a personalized PNG and/or GIF for every row of your data, rendered from one Lottie animation template, in bulk, with no browser and no After Effects. Each row replaces the dynamic text, the colors and the images of the template. $0.005 per image, and nothing for a row that fails. This is template rendering, not AI image generation: you get your own design with your data in it, the same way every time.

Give it a Lottie template (Bodymovin JSON exported from After Effects or Lottie Creator, or a .lottie file) and your rows: a JSON list, the URL of a CSV (a Google Sheet export for example), or an Apify dataset, such as the output of another Actor. For every row you get a PNG (one still frame) and/or a GIF (the whole animation), stored in the key-value store, with one dataset row of links.

Typical uses

  • Certificates and name badges: one PNG per attendee from a CSV (name, course, date). PNG only, there is no PDF output.
  • Greeting cards and invitations: a card per contact, with the name, a message and a color.
  • Social media visuals: one 1080 x 1080 image per product, city or offer of a catalog, for example from the dataset of a product or Google Maps scraper.
  • A personalized GIF for an e-mailing: the animation with the name of each recipient, 480 px and light by default.

It is an alternative to template-based image APIs (Bannerbear, Placid, Creatomate) for people who already design in Lottie.

Not included: no MP4 or WebM (PNG and GIF only), no PDF, right-to-left and complex scripts (Arabic, Hebrew, Indic, Thai) are refused for free, an embedded SVG image is refused, no font given by URL, at most 1000 rows per run. See Limits.

Quick start

The input form is prefilled with a small greeting card (a public domain Lottie that is embedded in the form) and three rows. Run it as is: you get three PNG images in under 30 seconds, for $0.015.

{
"templateJson": { "...": "the Lottie animation, see the form" },
"rows": [
{"Title": "Hello Zoé", "Subtitle": "Welcome to the team", "Badge": "#E4572E"},
{"Title": "Hello Sam", "Subtitle": "Happy birthday", "Badge": "#17BEBB"},
{"Title": "Hello Bartholomew", "Subtitle": "Thanks for your order", "Badge": "#FFC914"}
]
}

By default, a key of a row that has the name of a text layer replaces its text, the id of an image asset (or the name of an image layer) replaces the image with the picture at that URL, and the name of a shape layer replaces its fill color (#RRGGBB or #RGB). Replace the example rows with yours, or empty the list to inspect your template (see Inspection mode). Use templateUrl instead of templateJson for a template that is online.

One real output row (same template, with a GIF as well; the links are shortened):

{
"index": 0,
"id": "#FFC914",
"status": "ok",
"error": null,
"values": {"Title": "Hello Bartholomew", "Subtitle": "Thanks for your order", "Badge": "#FFC914"},
"png": {"url": "https://api.apify.com/v2/key-value-stores/<id>/records/row-0.png", "width": 1080, "height": 1080, "frame": 59.0, "timeSeconds": 1.967},
"gif": {"url": "https://api.apify.com/v2/key-value-stores/<id>/records/row-0.gif", "width": 480, "height": 480, "frames": 30, "fps": 15.0, "delayMs": 70},
"textFit": [{"layer": "Title", "fontScale": 0.604, "overflow": false}, {"layer": "Subtitle", "fontScale": 1.0, "overflow": false}],
"warnings": [],
"imagesCharged": 2
}

id comes from the idColumn input (here Badge, only to show the field). textFit says the title was reduced to 60.4 % of its size to fit the width of the template text. The files are row-<index>.png and row-<index>.gif in the default key-value store, and the output tab shows the PNG in the overview table.

From a CSV with your own column names

Your spreadsheet columns do not have to be named like the layers of the template. Say a CSV (csvUrl) has the header Name;Offer;Color: add mappings to tell the Actor where each column goes (this run was tested on the greeting card of the form):

{
"csvUrl": "https://docs.google.com/spreadsheets/d/<sheet id>/export?format=csv",
"mappings": [
{"column": "Name", "target": "text", "layer": "Title"},
{"column": "Offer", "target": "text", "layer": "Subtitle"},
{"column": "Color", "target": "color", "layer": "Badge"}
],
"idColumn": "Name"
}

(Together with templateJson or templateUrl.) Without mappings and with columns that match nothing, the run stops at once, free, and says which columns it read and which layers the template has. A cell with a wrong color (teal instead of #17BEBB) fails only its row, free, and the message names the column. For a Google Sheet, see the note in Limits.

Where do I get a template?

  • Design it: in After Effects, export with the Bodymovin plugin (tick Glyphs for your font); or in Lottie Creator. Name the text layers Title, Subtitle and so on, and a shape layer Badge.
  • Start from the example: the prefilled card of the input form is a public domain (CC0) Lottie with two texts and two colors. Copy it from the form and change it.
  • A Lottie downloaded from a library usually has no text layers to replace. Run the Actor once with no rows (see Inspection mode): TEMPLATE_INFO lists exactly which texts, colors and images can be replaced in it (empty lists mean nothing can). Check the license of the animation before you publish images made from it.

Use it from an AI agent (MCP)

Through the Apify MCP server, call the Actor with call-actor, then read the rows with get-dataset-items. The agent has to send the template itself (templateJson, up to 5 MB, or templateUrl). Keep to 100 rows for PNG or 20 rows for GIF so that the run ends within the 45 seconds of call-actor; in our local test 50 PNG rows took 4 seconds. Set maxTotalChargeUsd to cap the cost ($0.005 per image, so 50 images are $0.25). Each dataset row is small (status, values, links, no image data), and status tells which rows to fix. For a first call with no rows, TEMPLATE_INFO tells the agent which keys the rows may use.

{
"templateUrl": "https://example.com/card.json",
"rows": [{"Title": "Hello Zoé", "Subtitle": "Welcome", "Badge": "#E4572E"}],
"png": true,
"pngSize": 1080
}

From code (HTTP API): one call that waits for the run and returns the dataset rows (the 300 s limit of this endpoint applies):

curl -X POST "https://api.apify.com/v2/acts/lakadev~lottie-template-bulk-png-gif/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"templateUrl": "https://example.com/card.json", "rows": [{"Title": "Hello Zoé"}]}'

Each returned row has png.url (and gif.url): download it with a plain GET.

Prepare your template

The Actor changes the JSON of the template, so the template must give it handles:

  1. Name your layers in After Effects (or in the Lottie editor): a text layer named Title, a shape layer named Badge, an image layer named Photo. Names are case sensitive in mappings, and case-insensitive in the automatic matching.
  2. Write the longest expected value as the placeholder text of a text layer. The Actor reduces a longer text to the width of the placeholder (see Text that is too long). Or give maxWidth in a mapping.
  3. Embed your font when you export (the Glyphs option of the Bodymovin export, or an embedded font). Otherwise the text is drawn with DejaVu Sans, a fallback font: the picture is correct but not in your font. The Actor never downloads fonts. Only the characters that the template font contains can be used (see Unsupported scripts). Fills and strokes drawn inside font glyphs (chars) are ignored, because the renderer crashes on them: the text color comes from the text layer, and TEMPLATE_INFO.warnings then has GLYPH_STYLE_IGNORED.
  4. Embed images in the file (base64, PNG, JPEG, WebP or GIF; an embedded SVG image is refused). An image that is not embedded is never downloaded and stays empty, unless you replace it with an image mapping. It is reported in TEMPLATE_INFO.warnings.
  5. Keep replaceable images at 4 megapixels at most (and 4096 px on a side): the picture of each row is scaled to the size of the asset.

Not supported in a template: expressions and some effects (the engine is ThorVG, close to lottie-web but not identical to the pixel). To check a template before a bulk run, use the companion Actor Lottie Animation Preview & Validator (lottie-animation-preview-validator): it renders key frames and lists the features that other players ignore. This Actor also renders your template once before it starts, and refuses it, for free, if the engine cannot draw it.

Inspection mode

Run with a template and no rows (empty rows, no csvUrl, no datasetId). Nothing is rendered and nothing is billed except the start of the run. The key-value store gets a TEMPLATE_INFO record that lists what can be replaced:

{
"width": 1080, "height": 1080, "frameRate": 30, "durationSeconds": 2.0,
"texts": [{"layer": "Title", "text": "Hello Name", "font": "Greeting-Regular", "fontSource": "fallback", "box": false}],
"colors": [{"layer": "Badge", "kind": "shape", "color": "#3366E6"}],
"images": [{"asset": "photo", "width": 400, "height": 400, "layers": ["Photo"]}],
"mapping": [],
"warnings": []
}

(Shortened.) fontSource is embedded, glyphs or fallback; box is true for a text in a box (paragraph text). colors lists the shape layers that have a fill, and the solids. The status message says, for example, "Template inspected: 2 text layers, 2 color layers, 0 image assets. See TEMPLATE_INFO; add rows to render." In a normal run, TEMPLATE_INFO.mapping shows which column was matched to which element.

Mappings

With the default (empty mappings), columns are matched to template elements by name, in this order, ignoring case and spaces at the ends: text layer names, image asset ids, image layer names, shape layer names that have a fill. The first kind found wins; other columns are ignored. For a CSV the columns are those of the header line; for JSON rows and datasets, the keys of the first 100 rows. If no column matches anything, the run is refused with the list of columns and of template elements.

When your column names differ from the layer names, give mappings, a list of objects:

[
{"column": "name", "target": "text", "layer": "Title", "fit": "shrink", "maxWidth": 900},
{"column": "brand_color", "target": "color", "layer": "Badge"},
{"column": "location.city", "target": "text", "layer": "Subtitle"},
{"column": "photo_url", "target": "image", "asset": "photo", "imageFit": "cover"}
]
  • column: a key of the row. A path with dots follows nested objects (location.city) and array positions (images.0).
  • target: text, color or image.
  • layer: the exact name (nm) of the layer, for text and color. All layers with that name are changed, including those inside precompositions. A color applies to every fill of a shape layer (groups included; gradients and strokes are not changed), to the color of a text layer, or to a solid.
  • asset or layer for image: the id of an image asset, or the name of an image layer. The value must be the public URL of a PNG, JPEG, WebP or GIF (first frame). It is scaled to the size of the asset: imageFit cover (default) fills it and crops, contain fits it whole on a transparent background. An image smaller than the asset is enlarged, with the warning IMAGE_UPSCALED.
  • fit and maxWidth, for text only (see below).

A missing or null value leaves the element of the template as it is. An empty string empties a text, and leaves a color or an image as it is. Numbers are written as text (42), booleans as true or false, objects and lists as compact JSON. Text values are normalized (Unicode NFC, line breaks \n become a line break of the animation, tabs become spaces) and limited to 1000 characters. Values are never interpreted: no expression, no template syntax, no HTML.

Colors must be #RRGGBB or #RGB. red or rgb(0,0,0) give an invalid_row (free).

Text that is too long

By default (fit: shrink) the Actor reduces the font size, never the text, down to 50 % of the size of the template. The rule is measured with the rendering engine itself, in the units of the layer:

  • Point text (the usual case): the reference width is the width of the placeholder text of the template, or maxWidth if you give it. A longer value is scaled by reference / width. A shorter value keeps its size.
  • Text in a box (paragraph text with a fixed size): the engine wraps the words in the box, and the Actor takes the largest factor from 1 down to 0.5 for which the height of the wrapped text fits the height of the box.
  • If the value does not fit even at 50 %, it is drawn at 50 %, the row stays ok and billed, and has the warning TEXT_OVERFLOW and overflow: true in textFit.
  • fit: none never measures and never changes the size.

textFit in each row gives the fontScale used per text layer.

Unsupported scripts

A row is text_unsupported (free, no image) when its text contains:

  • a character that the template font does not have (the error lists the first ten as U+XXXX, for example "Characters not available in the template font "Brand-Regular": U+6771 U+4EAC"). Without this check, the image would contain empty boxes and you would pay for it;
  • a character of a right-to-left or complex script: Arabic, Hebrew, Indic scripts, Thai, Khmer and so on. The engine does not shape them (letters would not be joined and the order would be wrong). Emoji in color are not supported either.

Latin, Cyrillic, Greek, accented letters and Vietnamese work with the fallback font. To use another script, embed a font that has it in your template.

After another Actor

Choose the dataset of another run in the Apify dataset field: its items become the rows, read page by page (100 at a time), and the Actor only reads that dataset. Through the API or MCP, pass the id of the dataset as the string datasetId. Item fields become columns, nested fields with dot paths, for example location.city for a Google Maps place or images.0 for the first image of a product. Only the columns you map are kept in memory.

Use the idColumn input to copy a key of each item (a place id, a SKU) into the id field of the output row. The output rows are in the same order as the source.

We have not verified that an integration which starts this Actor when another finishes can read the dataset on every account: if a run says that the dataset is not found or not readable, pass the id through the API with your own token.

Output

  • Dataset: one row per data row, in order. status is ok (all requested files written, billed), invalid_row, image_failed, text_unsupported or render_failed, all free. error is one English sentence that names the column. values shows what was applied, also for failed rows, so you can fix the data.
  • Key-value store: row-<index>.png, row-<index>.gif, TEMPLATE_INFO, and TEMPLATE_PROGRESS (the run resumes where it stopped after a migration, with no duplicate and no double billing). To download all the files as a ZIP, use the key-value store API: GET https://api.apify.com/v2/key-value-stores/<store id>/records (it answers with a ZIP; add ?prefix=row- to leave out the other records) with your token. The store id is in the links of the rows.
  • The PNG is the frame given by pngFrame (percent of the duration, 100 = the last frame, 0 = the first), at pngSize pixels on the longest side (default: the size of the template, at most 2048). background is transparent (PNG with alpha) or #RRGGBB.
  • The GIF covers the whole animation at gifFps images per second (5 to 25, default 15), at most 150 images and 30 seconds, gifSize pixels on the longest side (64 to 720, default 480), looping forever. A GIF has no alpha: a transparent background becomes white. A longer animation lowers its own frame rate to stay at 150 images. The 15 fps GIF of a 3 second template has 45 images and 70 ms between two images.
  • The links are share links: anyone who has a link can open the file. They stay as long as the run's storages exist (storages of runs are deleted after a time that depends on your Apify plan; save what you keep).

Limits

  • 1000 rows per run (maxRows); beyond that the status message says "1000 of 2400 rows processed (maxRows)".
  • Template: 10 MB by URL, 5 MB inline, 5000 layers, GIF at most 30 s. PNG at most 2048 px, GIF at most 720 px.
  • A row may use 1000 characters of text per value. An image to download is 10 MB at most; PNG, WebP and GIF at most 16 megapixels, JPEG 40 megapixels. A download that does not answer within 15 s fails the row (free).
  • CSV: UTF-8, 10 MB, 200 columns, comma, semicolon or tab separators, quotes as in RFC 4180. A CSV that is not UTF-8 or has an unclosed quote is refused. Google Sheets: the export?format=csv link redirects to a Google host whose robots.txt this Actor respects, and the answer varies from one host to another; if a run says that a googleusercontent.com host disallows the download, run again or use File > Share > Publish to web > CSV and give that link. We do not promise that every Google Sheets link works.
  • Memory: 1024 MB (default) to 4096 MB. More memory does not make a run faster.
  • The rendering of a row has 30 s. After 3 time-outs or 5 crashes of the renderer in one run, the following rows are render_failed without rendering. After 5 network failures on one image host, or 20 time-outs in the run, the images that remain are image_failed without a request.
  • Not included: MP4 and WebM, a font given by URL, scripts that need shaping, colors of strokes and gradients, position and size of layers, Lottie slots and dotLottie themes, file names from a column, more than 1000 rows.

Pricing

Pay per event: $0.005 per image (image-generated): one event per file of a row with status ok, so a row that gets a PNG and a GIF is $0.01. Plus $0.00005 when the run starts. Failed rows, refused inputs and inspections cost nothing. 100 PNG images are $0.50. You can set a maximum cost per run: the Actor stops before a row that would exceed it, and exits normally with what it has written.

Networking and personal data

Every URL (template, CSV, images) is checked: http and https only, no credentials, public addresses only (also after each redirect), at most 5 redirects, robots.txt read once per site and respected (the user agent is LakadevLottieBot/1.0 (+https://apify.com/lakadev/lottie-template-bulk-png-gif)), no cookies, no proxy. A dataset is read with the token of the run, read only.

Rows often contain names. The Actor keeps nothing outside the storages of the run, and does not send your data anywhere except to fetch the URLs you give. The links of the files are share links (see Output); you remain responsible for the processing of the personal data in your rows and for the licenses of your template, fonts and images. No LottieFiles API is used.

Licenses

The rendering engine is ThorVG (MIT), used through the thorvg-python binding (LGPL-2.1, used unmodified, source at https://github.com/laggykiller/thorvg-python). Other components: Pillow, fonttools (MIT, reads the character tables of fonts), DejaVu Sans (fallback font), aiohttp, Protego. The full notices are in THIRD_PARTY_NOTICES.md. The example greeting card is dedicated to the public domain (CC0 1.0).