●DEPRECATION — The two image preview models shut down today, June 25; automations using them must migrate immediately●GA — In their place, gemini-3.1-flash-image and gemini-3-pro-image are now the generally available native image models●MEDIA — Video-to-image generation arrives: pass a video as context to create high-quality thumbnails (3.1 flash image only)●AUDIO — Gemini 3.1 Flash TTS preview lands: a low-cost, expressive, steerable text-to-speech model●MODEL — Gemini 3.5 Flash is GA, beating 3.1 Pro on nearly every benchmark while running about 4x faster●SEARCH — File Search now supports multimodal search, embedding and searching images natively via gemini-embedding-2●DEPRECATION — The two image preview models shut down today, June 25; automations using them must migrate immediately●GA — In their place, gemini-3.1-flash-image and gemini-3-pro-image are now the generally available native image models●MEDIA — Video-to-image generation arrives: pass a video as context to create high-quality thumbnails (3.1 flash image only)●AUDIO — Gemini 3.1 Flash TTS preview lands: a low-cost, expressive, steerable text-to-speech model●MODEL — Gemini 3.5 Flash is GA, beating 3.1 Pro on nearly every benchmark while running about 4x faster●SEARCH — File Search now supports multimodal search, embedding and searching images natively via gemini-embedding-2
The Morning a Preview Image Model Went Dark — Migrating to GA Gemini Image Models and Building a Deprecation-Resilient Pipeline
With gemini-3.1-flash-image-preview and gemini-3-pro-image-preview retired, here is how to migrate to the GA models and design an image pipeline that no longer gets caught off guard by deprecation dates — with code and cost math, plus video-to-image thumbnail automation.
An unattended image job starts returning 404 NOT_FOUND one morning, and the cause turns out to be not your code but an external model-retirement schedule. If you have lived through that, you know the particular frustration of it.
On June 25, 2026, two preview image models — gemini-3.1-flash-image-preview and gemini-3-pro-image-preview — were retired. In their place, the native image models graduated to general availability as gemini-3.1-flash-image (Flash Image) and gemini-3-pro-image (Pro Image).
I run several jobs that auto-generate artwork and thumbnails for wallpaper and wellness apps. Back when I hardcoded preview model IDs straight into the code, every retirement meant hunting down the affected lines and rewriting them — always reactive, always after the breakage. Learning from that, I want to share both the migration steps and, more importantly, a pipeline design that stops you from being whipped around by deprecation dates.
What actually changes between preview and GA
The first thing to internalize is that this is not a mere rename. Dropping the -preview suffix carries operational meaning.
Aspect
Preview
GA
Model ID
gemini-3.1-flash-image-preview
gemini-3.1-flash-image
Stability guarantee
None (can be pulled with little notice)
Yes (migration window on retirement)
Pricing
Provisional, may shift
Finalized
Production use
Discouraged (for evaluation)
Recommended
Video-to-image
Partial
Supported on Flash Image
Preview models always carried the assumption that they could disappear at any time. They are handy for evaluation and prototyping, but hardcoding one into an unattended production job was, in hindsight, a poorly considered design. GA models come with a migration window, so the same accident is far less likely.
That said, swapping in the GA model is not the end of the story. The day another model is retired will surely come. What matters is using this migration as the moment to bake in a structure that anticipates the next retirement.
The minimal migration: get the broken job running
Here is the minimal path when you need something working right now. For most code, swapping the model ID is enough.
from google import genaiclient = genai.Client(api_key="YOUR_API_KEY")# Before (retired):# model = "gemini-3.1-flash-image-preview"# After (GA):model = "gemini-3.1-flash-image"response = client.models.generate_content( model=model, contents="A calm minimalist wallpaper, soft gradient, muted teal",)# Generated images come back as partsfor part in response.candidates[0].content.parts: if part.inline_data is not None: with open("wallpaper.png", "wb") as f: f.write(part.inline_data.data)
One caution at this stage: even when the response shape is identical between preview and GA, the tone of the generated output can shift subtly. Reusing prompts verbatim may produce slightly different color or composition. Before sending anything to production, I recommend eyeballing the output for a handful of representative prompts.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Get the configuration-layer code that centralizes model IDs so you can switch from preview to GA (gemini-3.1-flash-image / gemini-3-pro-image) safely and in one place
✦Implement a model-lifecycle check with advance alerts that stops unattended jobs from silently breaking when a model is retired
✦Learn the video-to-image implementation for thumbnail generation and the cost math for folding it into a daily automation
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
A deprecation-resilient design: centralize model IDs
This is the real point. Rewriting a hardcoded ID for the emergency only sets you up to repeat the same chore on the next retirement. Instead of scattering model IDs across the codebase, collect them into a single configuration layer.
# model_registry.pyfrom dataclasses import dataclassfrom datetime import date@dataclass(frozen=True)class ModelSpec: id: str role: str # purpose label deprecate_on: date | None # known retirement date, if officially announced fallback: str | None # replacement model ID on retirement# Map logical names to concrete model IDs, by purposeREGISTRY: dict[str, ModelSpec] = { "image_fast": ModelSpec( id="gemini-3.1-flash-image", role="thumbnails / high-volume artwork", deprecate_on=None, fallback="gemini-3-pro-image", ), "image_quality": ModelSpec( id="gemini-3-pro-image", role="high-quality hero images", deprecate_on=None, fallback="gemini-3.1-flash-image", ),}def resolve(logical_name: str) -> str: spec = REGISTRY[logical_name] return spec.id
Application code references models by logical name — resolve("image_fast") — rather than by concrete ID. With this in place, the next retirement touches exactly one file: model_registry.py. You are freed from the thankless task of grepping hardcoded IDs across the whole codebase.
Since adopting this "reference by logical name" pattern, the effort of moving between model generations has dropped noticeably for me. When the surface you have to touch collapses to a single file, the psychological friction of migrating shrinks with it.
Never miss a retirement date: advance alerts
GA models, too, will eventually reach their retirement day. The problem is humans missing the announcement — and the more unattended the job, the later you notice. Use the deprecate_on field to build a small sentinel that checks every day for models nearing retirement.
# deprecation_guard.pyfrom datetime import date, timedeltafrom model_registry import REGISTRYALERT_WINDOW_DAYS = 30def check_deprecations(today: date | None = None) -> list[str]: today = today or date.today() warnings: list[str] = [] for name, spec in REGISTRY.items(): if spec.deprecate_on is None: continue days_left = (spec.deprecate_on - today).days if days_left < 0: warnings.append( f"RETIRED: {name} ({spec.id}). Migrate to fallback={spec.fallback} now." ) elif days_left <= ALERT_WINDOW_DAYS: warnings.append( f"WARN: {name} ({spec.id}) retires in {days_left} days. " f"Start validating {spec.fallback}." ) return warningsif __name__ == "__main__": for w in check_deprecations(): print(w)
Wire this into a daily cron or Cloud Scheduler and pipe the output to Slack or email, and a notice lands in your hands a full 30 days before retirement. When the official changelog announces a date, you add one line — the date — to deprecate_on in REGISTRY. That is the entire operational burden.
In plain numbers, the difference between "noticing on the day of retirement" and "noticing 30 days out" is a literal order of magnitude in the time you have to validate and switch. The reliability of unattended jobs hinges, in my experience, on exactly these unglamorous sentinels.
Generating images from video: wiring up thumbnail automation
Alongside this GA promotion, gemini-3.1-flash-image gained the ability to take a video file as context and generate an image from it. That maps directly to producing video thumbnails or summary artwork automatically.
from google import genaiclient = genai.Client(api_key="YOUR_API_KEY")# Upload the video and pass it as contextvideo_file = client.files.upload(file="clip.mp4")response = client.models.generate_content( model="gemini-3.1-flash-image", contents=[ video_file, "Create a clean thumbnail capturing the calmest moment of this clip. " "16:9, soft lighting, no text overlay.", ],)for part in response.candidates[0].content.parts: if part.inline_data is not None: with open("thumbnail.png", "wb") as f: f.write(part.inline_data.data)
Previously this took two stages: extract a representative frame from the video, then process that frame separately. Being able to pass the video itself as context lets you generate a single content-aware image in one shot. For an indie developer like me who works through artwork and thumbnails daily, folding one stage out of the process is a genuinely welcome change.
Cost math: a daily-operations baseline
When building thumbnail automation, estimating a rough monthly cost from your volume saves surprises. Flash Image is the fast, inexpensive model built for volume; Pro Image costs more for quality. Splitting by purpose is the realistic approach.
Scenario
Model
Per day
Per month
High-volume article thumbnails
image_fast (Flash Image)
30 images
~900 images
Hero / announcement images
image_quality (Pro Image)
3 images
~90 images
The official pricing page is the source of truth for exact unit costs, so confirm it before going live (Gemini API pricing). The design insight is to encode the "volume on Flash, the decisive shots on Pro" split into the logical names in model_registry.py. With image_fast and image_quality separated, you can tune the cost-versus-quality balance from a single point of configuration.
After the migration, what to verify
Finally, here is a checklist for after the GA migration and deprecation guard are in, in the order I work through it.
First, verify output before going to production: eyeball representative prompts to confirm color and composition have not drifted from preview to GA. Second, make sure no model ID is hardcoded anywhere outside model_registry.py — grep -rn "gemini-3" . flushes them out. Third, put deprecation_guard.py on a daily schedule and send a test notification to confirm alerts actually arrive.
Retirement as an event is unavoidable. But you can turn it from a "same-day accident" into a "scheduled task you saw coming 30 days out." I hope this retirement becomes a good occasion to put that structure in place. Thank you for reading.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.