Skip to content

Blog · Guides

Add Text to Images by URL: Font, Color, Position

Add text to any image by changing its URL: font, size, color, position, wrapping, URL-encoding and ready-to-copy recipes for captions and share cards.

By the Frametile team at FairShare IT Services · · 4 min read

How do you add text to an image by URL?

Add a text layer to the image's URL. l_text:Roboto_64_bold:WILD,co_white,g_south,y_50 draws the word WILD in white, bold 64 pixel Roboto, along the bottom edge and 50 pixels up from it. l_text starts the layer, then come the font, size and weight, and then the text itself. co_ sets the colour, g_ says where the text sits and y_ moves it away from that edge.

Your original photo is never changed. The URL returns a new image with the text drawn on it, and the same URL returns the same image every time, so you can use it anywhere an image address works.

Sample photo of a lioness transformed with l_text:Roboto_72_bold:Fresh,co_white,g_south,y_40

What does the text overlay syntax look like?

l_text:Font_Size_Weight_Align:Your%20text,co_white,g_south,y_40
PartWhat it isExample
Font A font family, written without spaces. Bundled fonts include Roboto, Noto Sans, Noto Serif, Noto Sans Mono and Noto Sans CJK JP for Japanese text. Roboto
Size Font size in pixels. 64
Weight (optional) bold, italic, normal, light, thin, medium, semibold, extrabold or black. bold
Align (optional) left, center or right, for text that wraps onto several lines. center
Text Your words, URL-encoded, after the second colon. Fresh%20Pear
co_ Text colour: a name such as white or a hex value without the hash. The default is black. co_white
g_ · x_ · y_ Where the text sits, and how far it is moved away from that edge. g_south,y_40

The text layer is its own step, separated from other steps by a slash, and it is drawn on top of whatever the steps before it produced. That ordering matters, and most of the mistakes further down come from forgetting it.

How do you position the text?

Use gravity, g_, to pick an edge or corner: north, south, east, west, center and the four corners such as north_west. Text sits at the bottom, south, if you do not say otherwise. Then use x_ and y_ to move it away from that edge so it does not touch the border.

https://frametile.com/api/YOUR_API_KEY/images/fruit/pear/w_600,h_600,c_fill/l_text:Roboto_48_bold:Summer,co_white,g_north_west,x_24,y_24

That puts the word in the top-left corner with a 24 pixel margin. A caption along the bottom needs only g_south and a y_ value. A centred headline uses g_center and no offsets.

Sample photo of a lioness transformed with w_600,h_600,c_fill,g_center/l_text:Roboto_64_bold:WILD,co_FFFFFF,g_south,y_50

Notice that the example above resizes first, in its own step, and adds the text second. That is the order to use, and the mistakes section below explains why.

How do you wrap long text so it fits?

Give the layer a width and ask it to fit: w_480,c_fit wraps the text inside a box 480 pixels wide and shrinks it until it fits. Without c_fit, w_ only shrinks the font until a single line is as wide as you asked for, and the text does not wrap.

https://frametile.com/api/YOUR_API_KEY/images/fruit/pear/w_600,h_600,c_fill/l_text:Roboto_40_bold_center:Fresh%20pears%20picked%20this%20morning,co_white,w_480,c_fit,g_center

Add center as the align part, as in Roboto_40_bold_center, so a wrapped headline is centred on every line, not only the first.

Why does the text have to be URL-encoded?

Because several characters mean something in a URL. A space ends the address, a # starts a fragment, a ? starts a query, and % and & have jobs of their own. Encoding turns each of them into a % code, so they reach Frametile as plain text.

CharacterEncoded
space %20
% %25
& %26
# %23
? %3F

Do not encode by hand. Let your language do it, and encode only the text, not the whole URL:

const text = encodeURIComponent("Save 50% today");
const url = `https://frametile.com/api/YOUR_API_KEY/images/fruit/pear/w_600,h_600,c_fill/l_text:Roboto_48_bold:${text},co_white,g_south,y_40`;

Commas, slashes and colons need more care. In a transformation, a comma separates parameters, a slash separates steps and a colon separates the parts of the text layer, so even when encoded they may still be read as syntax. Keep them out of overlay text where you can, and when you cannot, test that exact URL before you rely on it.

Text overlay recipes

Each recipe is one URL, built from parameters described in the overlay docs. Steps run left to right.

GoalTransformationNotes
Caption along the bottom w_600,h_600,c_fill/l_text:Roboto_48_bold:Summer%20Sale,co_white,g_south,y_40 Resize in the first step, text in the second.
Label in a corner w_600,h_600,c_fill/l_text:Roboto_36_bold:NEW,co_white,g_north_east,x_24,y_24 Gravity plus equal offsets gives an even margin.
Readable text on a bright photo w_600,h_600,c_fill/e_brightness:-30/l_text:Roboto_56_bold:WILD,co_white,g_center Darken in its own step first, so white text stands out.
Social share card w_1200,h_630,c_fill/e_brightness:-35/l_text:Roboto_64_bold_center:Your%20title,co_white,w_1000,c_fit,g_center 1200 by 630 is the size most social networks show for link previews.
Text over a cutout e_background_removal/l_text:Roboto_64_bold:SALE,co_white,g_south See background removal for how the cutout step works.

For a logo or badge instead of words, use an image layer: l_badge,w_110,g_north_east,x_16,y_16 places another image you own in Frametile in the corner. It follows the same position rules as text.

Can you personalise images with dynamic text?

Yes. Build the URL in your code for each person, product or order, and put the changing words in the text part. A name on a welcome banner, a price on a product tile or a title on a share card all work the same way: one base photo, a different URL for each.

const name = encodeURIComponent(user.firstName);
const banner = `https://frametile.com/api/YOUR_API_KEY/images/fruit/pear/w_1200,h_400,c_fill/l_text:Roboto_72_bold:Welcome%20${name},co_white,g_center`;

Two things to know before you do this at scale:

  • Every different text is a different image. Frametile stores each generated image by its full URL, so a repeat request for the same text is served from storage, while a new name creates and stores a new file. A few thousand personalised banners is fine, but it is worth knowing before you generate millions.
  • Your API key is part of the URL. Anyone who receives an image URL can see it. Treat these URLs like a password: generate them on your server, and think twice before putting them in public pages or mass emails.

What are the most common text overlay mistakes?

  1. Putting an effect in the same step as the text. Inside a step that has a layer, only the layer is applied, so e_sepia,l_text:... silently drops the sepia. Write e_sepia/l_text:... instead.
  2. Adding text before resizing. Steps run in order, so text drawn first is scaled along with the photo. Resize first, then add the text at the size you actually want.
  3. Forgetting to encode. A raw space or # in the text breaks the URL. Encode the text with your language's own function, and keep commas, slashes and colons out of it where you can.
  4. White text on a pale area. Pick a gravity that lands on a plain part of the photo, or darken the image first, as in the recipe above.
  5. Setting w_ without c_fit. You get a smaller font, not wrapped lines.
  6. Leaving the words only in the picture. Text drawn into an image cannot be selected, translated or read by a search engine. Put the same words in the page's HTML or in the image's alt text.

Resizing comes first in nearly every recipe here, and it has its own guide: how to resize and crop images by URL. Once the picture looks right, shrink it with the right output format: WebP vs AVIF vs JPEG. More finished text examples are on the examples page.

Try it on your own images

Upload a photo, copy its URL and start editing the URL. Every parameter above works the same way.

Get started Read the docs