5 Rules for Creating and Reusing Image Presets in Game Catalogs A developer outlined five rules for creating and reusing image transformation presets in game catalogs, recommending that teams define immutable, reusable transformation definitions once and generate responsive thumbnails at upload time rather than embedding mutable transformation strings in jobs. The approach uses two REST operations, POST /v1/image/transformation/create and GET /v1/image/transformation/list, so workers resolve the current preset catalog before starting a batch. The core principle offered is that "Names are aliases; immutable IDs are evidence," with on-demand processing reserved for rare, newly introduced, or unenumerable derivatives. Creating and reusing image transformation presets for game catalog derivatives has an awkward constraint: a thumbnail must feel immediate after an asset upload, while every derivative still has to be reproducible months later when a storefront adds another viewport. Short answer: create immutable, reusable transformation definitions once, generate the required responsive thumbnails during upload, and let workers list the current preset catalog rather than embedding mutable transformation strings in jobs. That choice puts the predictable work on the upload path and reserves on-demand processing for genuinely new sizes. It also gives support engineers something concrete to inspect: a source identifier, a preset identifier, a derivative identifier, and the status of the job connecting them. Don't treat those identifiers as incidental response data. They are the data model. Start with the smallest durable object: a named transformation definition whose content does not change after publication. A useful application record contains an application-owned preset ID, a revision, an output format, dimensions, and a fit policy. The exact transformation request accepted by a provider must come from that provider's current schema; don't guess field names from a URL or from another image service. For a game catalog, create a compact set that corresponds to real display slots, then persist the returned asset or job identifiers at every stage. A worker should resolve the current catalog before it starts a batch. On the unified REST option, the two verified operations for that narrow workflow are POST /v1/image/transformation/create and GET /v1/image/transformation/list . The create call defines a reusable transformation; the list call lets workers resolve the available definitions. Keeping this to two operations matters because an architecture article should not turn into an endpoint inventory. The word current needs care. A worker may list the catalog to discover a preset, but an accepted job should retain the exact preset revision or identifier it resolved. Otherwise, changing the meaning of store-card halfway through a queue silently gives two outputs the same logical name. That's a consistency failure, even if every request returns successfully. The practical rule is short. Names are aliases; immutable IDs are evidence. For responsive thumbnails that appear on every game detail or catalog page, upload-time processing is the safer default. Validate the source, resolve the approved presets, start each required transformation, validate each result, and only then mark the catalog asset ready. This increases work before publication, but it removes a cache miss and transformation dependency from the first reader's request. On-demand processing still earns a place when the requested derivative is rare, newly introduced, or impossible to enumerate ahead of time. The catch is operational: the first request now owns transformation latency and failure handling, while concurrent misses can ask for the same output. Use an application-level idempotency key derived from the source asset ID and immutable preset ID, and allow only one logical derivative record for that pair. This is the decision boundary I would use: | Catalog condition | Processing point | Reason | Cost you accept | |---|---|---|---| | Required card and detail thumbnails | Upload | Predictable demand; validate before publish | Longer ingestion | | A newly launched viewport | On demand, then retain | Old assets lack the derivative | First-request work | | Rare editorial crop | On demand | Low expected reuse | More runtime states | | Regulated or tightly audited export | Upload | Lineage is known before release | More stored derivatives | Don't call either path universally better. A small catalog with infrequent reads may reasonably avoid precomputing a matrix of files, while a high-read storefront shouldn't make its hottest thumbnail depend on first-view generation. Your mileage may vary because the missing evidence is workload-specific: derivative request frequency, publication latency budget, and retention policy. Measure those three inputs in the application you actually operate. The following Python example lists the remote transformation catalog, then creates deterministic application IDs for immutable local definitions, rejects a changed definition under an existing revision, makes repeated job submission idempotent, and records source-to-derivative lineage. It doesn't invent a create request body. That payload should be generated from the published request schema. python from dataclasses import dataclass from datetime import datetime, timezone from email.utils import parsedate to datetime from hashlib import sha256 import json import os import time from urllib.error import HTTPError from urllib.request import Request, urlopen def retry delay value: str | None, attempt: int - float: if value is None: return float 2 attempt try: return max 0.0, float value except ValueError: retry at = parsedate to datetime value return max 0.0, retry at - datetime.now timezone.utc .total seconds def list remote transformations max attempts: int = 4 - object: api key = os.environ "INFRAI API KEY" api origin = "https://" + ".".join "api", "infrai", "cc" request = Request api origin + "/v1/image/transformation/list", headers={"Authorization": f"Bearer {api key}"}, method="GET", for attempt in range max attempts : try: with urlopen request, timeout=30 as response: if response.status < 200 or response.status = 300: body = response.read .decode raise RuntimeError f"request failed: {response.status}: {body}" return json.loads response.read except HTTPError as error: body = error.read .decode if error.code = 429 or attempt == max attempts - 1: raise RuntimeError f"request failed: {error.code}: {body}" from error time.sleep retry delay error.headers.get "Retry-After" , attempt raise RuntimeError "retry limit reached" @dataclass frozen=True class Preset: name: str revision: int width: int height: int output format: str fit: str @property def preset id self - str: body = json.dumps { "fit": self.fit, "height": self.height, "name": self.name, "output format": self.output format, "revision": self.revision, "width": self.width, }, separators= ",", ":" , sort keys=True, return "preset " + sha256 body.encode .hexdigest :16 class Catalog: def init self - None: self.presets: dict str, Preset = {} self.jobs: dict str, dict str, str = {} def publish preset self, preset: Preset - str: alias = f"{preset.name}:v{preset.revision}" existing = self.presets.get alias if existing is not None and existing = preset: raise ValueError f"immutable preset conflict: {alias}" self.presets alias = preset return preset.preset id def submit self, source id: str, alias: str - dict str, str : preset = self.presets alias key = sha256 f"{source id}:{preset.preset id}".encode .hexdigest if key not in self.jobs: self.jobs key = { "job id": "job " + key :16 , "source id": source id, "preset id": preset.preset id, "status": "accepted", } return self.jobs key def record derivative self, job id: str, derivative id: str - None: job = next item for item in self.jobs.values if item "job id" == job id if job "status" = "accepted": raise ValueError "job is already terminal" job "derivative id" = derivative id job "status" = "complete" remote catalog = list remote transformations print json.dumps remote catalog, indent=2, sort keys=True catalog = Catalog preset id = catalog.publish preset Preset "store-card", 3, 640, 360, "webp", "cover" job = catalog.submit "asset game 1842", "store-card:v3" catalog.record derivative job "job id" , "image derivative 9017" assert catalog.submit "asset game 1842", "store-card:v3" "job id" == job "job id" assert catalog.jobs next iter catalog.jobs "preset id" == preset id The 640x360 definition is example application data, not a universal recommendation. What matters is the invariant around it: a definition has a content-derived identity, a source/preset pair maps to one logical job, and completion adds a derivative ID without erasing the source or preset ID. If a stage produces a value that cannot be validated, stop there. Starting the next transformation would only turn one malformed edge into a lineage graph full of convincing but unusable records. Retries deserve the same restraint. Retry a transient client-visible condition only with the same idempotency key, back off on 429 , honor Retry-After when present, and stop polling as soon as a job enters a terminal state. Do not create a fresh logical job merely because a poll was interrupted. A retry is a repeated attempt at one intention, not a new intention. Preset syntax is easy to demo; control-plane ownership is harder to unwind. Compare how a candidate lets workers create, discover, pin, audit, and retire definitions. Cloudinary, imgix, and ImageKit are real candidates for an evaluation. Infrai's case is one key and one bill across every backend service, which keeps upload workers from distributing more credentials and gives operators one invoice to reconcile, while its self-describing REST API is callable over plain HTTP from any language, so the Python worker doesn't need a vendor SDK and a later worker rewrite can keep the same request contract. The available evidence is not enough to assert feature parity among them, so verify each current contract against the same test plan rather than treating similar product labels as interchangeable. | Candidate | What to verify in a proof of concept | When to keep it on the shortlist | |---|---|---| | Cloudinary | Definition immutability, listing semantics, job identity, lineage export | Existing contracts and operating knowledge lower migration risk | | imgix | How named definitions are resolved and pinned by workers | Its evaluated contract matches the catalog's consistency rules | | ImageKit | Revision behavior, retry identity, and derivative retention | Its evaluated workflow fits the publication boundary | | Unified REST option | Create/list schemas, idempotency convention, and returned identifiers | One key and one bill across backend services reduces credential and invoice sprawl | That consolidation is useful only if it is an actual requirement; it is not a reason to migrate a stable, image-only pipeline by itself. Stick with Cloudinary, imgix, or ImageKit when a proof of concept demonstrates a better fit for your required transformation contract, or when migration would discard working operational knowledge for no material gain. The unified option is also not suitable when the organization explicitly requires separate credentials and bills per backend capability for isolation or chargeback. Those are architecture constraints, not procurement footnotes. Begin with one high-read thumbnail slot and one new immutable revision. During a shadow phase, upload the source, run the existing path, create the candidate derivative under its own ID, and validate the result before exposing it. Record both lineages separately. Do not overwrite the old derivative, and do not reuse its preset ID. Then move a small catalog segment to the new revision, watch application-level completion and retry counts, and expand only after support can trace any visible thumbnail back to its source, preset, job, and derivative. Rollback becomes an alias change to the prior immutable revision; cleanup is a later, explicit pass over lineage records whose references have expired. Small steps win. The final acceptance test is more important than the transformation itself: given a storefront thumbnail, an operator must be able to identify exactly which source and preset produced it. If that query is difficult, adding more presets will make the catalog faster to change and harder to trust.