As Valentine's Day approaches, we want to share a fun project that you can build using Transloadit and Uppy. We'll be using the /image/resize Robot to create a custom Valentine's Day card generator. Add a name to the card too, and you'll have a personalized card to send to your loved one.

A cute cartoony banner with several love hearts, love letters and arrows around the text 'Valentine Card Generator'.

Our Assembly Instructions

Let's first take a look at the Assembly Instructions we'll be using.

  "steps": {
    "watermark": {
      "use": ":original",
      "robot": "/image/resize",
      "width": "1921",
      "height": "1921",
      "resize_strategy": "fillcrop",
      "watermark_url": "",
      "watermark_size": "100%",
      "watermark_position": "center",
      "imagemagick_stack": "v3.0.0"
    "add_text": {
      "use": "watermark",
      "robot": "/image/resize",
      "text": [
          "text": "Happy Valentines Day!",
          "size": 110,
          "color": "#ffffff",
          "valign": "bottom",
          "y_offset": -90,
          "x_offset": -100
      "imagemagick_stack": "v3.0.0"

Here we need to use two separate /image/resize Steps. Our first Step adds a decorative frame around our original image, while also cropping and resizing it to fit. The second Step then adds text on top of our watermarked image, which we'll be modifying later with JavaScript. If we did this in the same Step, our image frame would be placed in front of the text – obscuring it in our final result.


Sadly, we have nowhere to show off our new Template. Let's fix that! Copy the below HTML into a new HTML file.

One thing to take note of is our input element. We'll be using this in the next section to allow the user to change what name to add to their card.

    <title>Transloadit Valentine</title>
    <link href="" rel="stylesheet" />


    <h1>Transloadit Valentine</h1>
    <p>Upload a picture of your Valentine and we'll make it a Valentine's Day card!</p>

    <div class="input-container">
      <label for="name">Name:</label>
      <input id="name" name="name" maxlength="12" />

    <div id="drag-drop-area"></div>

    <img id="result" alt="Your result image" src="" />

Now, let's add some nice styling to our page. Inside of the style tag, add the following CSS.

:root {
  --primary: #7f626a;
  --secondary: #f8edeb;
  --accent: #ff4d6d;
  --accent-highlight: #f9ccd2;

body {
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: var(--secondary);
  color: var(--primary);
  font-family: sans-serif;

p {
  margin: 0 0 0.5rem;

#result {
  border-radius: 10px;
  max-width: 40vw;
  border: none;
  contain: content;
  aspect-ratio: 1/1;
  margin: 2rem;

.input-container {
  margin: 1rem;
  color: var(--primary);

input {
  border: none;
  border-bottom: 2px var(--primary) dashed;
  background-color: transparent;
  text-align: start;
  font: inherit;
  color: var(--accent);

input:focus {
  outline: none;

.uppy-Dashboard {
  max-width: 90vw;

.uppy-Dashboard-innerWrap {
  background-color: var(--secondary);
  border: var(--primary) 2px dashed;

.uppy-Dashboard-AddFiles {
  border: 0 !important;

.uppy-Dashboard-poweredByIcon {
  color: var(--primary) !important;
  stroke: var(--primary);

.uppy-DashboardTab-name {
  color: var(--primary);

.uppy-DashboardTab-btn:hover {
  background-color: var(--accent-highlight) !important;

.uppy-StatusBar-actionBtn {
  color: var(--accent);
  fill: var(--accent);

.uppy-StatusBar::before {
  border: none !important;
  background-color: transparent !important;

.uppy-StatusBar-actionBtn--upload {
  background-color: var(--accent) !important;
  color: var(--secondary) !important;
  border: none;
  border-radius: 5px;

While it may look intimidating, the majority of our CSS here exists only to restyle our Uppy Dashboard component. Other than that, we're just using a color palette generated from Coolors.

Here's what our page should now look like:

Our website so far, without the Uppy Dashboard.


Now onto the fun part! Inside of a script tag, we need to add the following JavaScript to our website.

import {
} from ''

const baseParams = {
  auth: { key: 'AUTH_KEY' },
  steps: {
    watermark: {
      use: ':original',
      robot: '/image/resize',
      width: '1921',
      height: '1921',
      resize_strategy: 'fillcrop',
      watermark_url: '',
      watermark_size: '100%',
      watermark_position: 'center',
      imagemagick_stack: 'v3.0.0',
    add_text: {
      use: 'watermark',
      robot: '/image/resize',
      text: [
          text: 'Happy Valentines Day!',
          size: 110,
          color: '#ffffff',
          valign: 'bottom',
          y_offset: -90,
          x_offset: -100,
      imagemagick_stack: 'v3.0.0',

function updateName(val) {
  if (val) {
    const params = baseParams
    params.steps.add_text.text[0].text = `Happy Valentines ${val}!`

const uppy = new Uppy({
  restrictions: {
    maxNumberOfFiles: 1,
    allowedFileTypes: ['image/*'],
  .use(Dashboard, {
    inline: true,
    target: '#drag-drop-area',
  .use(ImageEditor, {
    target: Dashboard,
    quality: 0.8,
    cropperOptions: {
      aspectRatio: 1 / 1, // Same aspect ratio as our overlay
  .use(Transloadit, {
    waitForEncoding: true,
    params: baseParams,

uppy.on('complete', (result) => {
  const resultImg = document.getElementById('result')
  resultImg.src = result.transloadit[0].results.add_text[0].ssl_url

document.getElementById('name').addEventListener('change', function () {

We leverage the Uppy Dashboard, ImageEditor and Transloadit components here, and luckily we can mostly use the default settings. Notably however, we set the aspect ratio of the ImageEditor crop tool to the same as our overlay image. In our case, it's 1:1.

We also add an event listener to update the text that we add to the image whenever the value in our input element changes.

Wrapping up

If you want to tinker around with the website without having to implement it yourself – try out the CodePen below!

See the Pen Untitled by MSSNG (@missing-tech) on CodePen.

That's all from us for now. Happy Valentine's Day, and make sure to send your special someone a beautiful, personalized card. And if you managed to take the tools used in this blog post even further, we would love to hear about it on Twitter!