cd /news/developer-tools/release-v1-0-0-loco-is-stable · home topics developer-tools article
[ARTICLE · art-78630] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Release v1.0.0 – Loco is stable

Loco released v1.0.0, its first stable version, featuring Sea-ORM 2.0, first-class LLM/agent support, priority queues, and a major dependency modernization. The release introduces breaking changes including 64-bit primary keys, a Redis backend switch to Sorted Sets for priority queues, and the merger of MirrorStrategy and BackupStrategy into ReplicatedStrategy.

read12 min views1 publishedJul 29, 2026
Release v1.0.0 – Loco is stable
Image: source

1.0.0 - 2026-07-25 #

1.0.0 is the first stable Loco release — a single, intentionally-breaking

milestone. Its headline is the move to Sea-ORM 2.0, alongside first-class

LLM/agent support, priority queues, a broad dependency modernization, and a deep

hardening pass across the queue, storage, config, error, remote-IP, and

middleware subsystems. Follow the step-by-step

0.16 → 1.0 upgrade guide.

Breaking Changes

Sea-ORM 2.0 + sqlx 0.9. Bumpsea-orm

/sea-orm-migration to2.0

(app +migration

crate), directsqlx

to0.9

, update the Sea-ORM CLI, and

regenerate entities. Raw-Statement

calls gain a_raw

suffix; runtime SQL

strings needAssertSqlSafe

. MSRV is1.94(sea-orm 2.0.0 declares it). (Adopted from the SeaQL fork and

#1698.)Generated primary/foreign keys are now 64-bit (BIGINT / Also thei64 ).

int

/unsigned

field types generate 64-bit columns. Only affects newly

generated code; existing tables are untouched.Priority queues — Redis backend change. The Redis worker moved from Lists

to Sorted Sets (ZSET) to support priority;drain existing Redis queues before. Postgres/SQLite auto-migrate a

upgradingpriority

column (no action).

([#1693](https://github.com/loco-rs/loco/pull/1693))(`Worker::perform_later()`

returns the job IDResult<String>

), and

`Queue::enqueue()`

returns`Result<Option<String>>`

. Existing

`perform_later(...).await?;`

keeps working. ([#1624](https://github.com/loco-rs/loco/pull/1624), fixes[#1623](https://github.com/loco-rs/loco/issues/1623))instead of flat`PageResponse<T>`

exposesmeta: PagerMeta

total_pages

/total_items

(also carriespage

/page_size

). (#1685, fixes#1683)View engine: useengines::TeraView::build_with_post_process(...) instead

ofTeraView::build()?.post_process(...) inafter_routes

.Dependency majors:thiserror

1→2,tower

0.4→0.5,heck

→0.5, byte-unit

4→5,ipnetwork

0.20→0.21,strum

→0.27,redis

0.31→1, bb8-redis

→0.26,opendal

0.54→0.57;serde_yaml

serde_yaml_ng

.

Transitive for most apps.- Removed the dead loco-cli

crate (superseded byloco-new , the published

loco

binary). TheExtraDbInitializer

removed; useMultiDbInitializer

.

single-extra-connection initializer (initializers.extra_db

, which layered a

bareExtension<DatabaseConnection>

) is gone. UseMultiDbInitializer

with a

one-entryinitializers.multi_db

map instead, and extract the connection with

Extension<MultiDb>

+multi_db.get("<name>") . This collapses two

near-identical initializers into one named-connections abstraction.Construct it withAppContext

is now#[non_exhaustive] .

`AppContext::builder(environment, db, config)`

(or`builder(environment, config)`

without thewith-db

feature) followed by optional

`.queue_provider(..)`

/`.mailer(..)`

/`.storage(..)`

/`.cache(..)`

/`.shared_store(..)`

and`.build()`

. Direct struct-literal construction and exhaustive pattern

matches onAppContext

from outside the crate no longer compile; field access (ctx.db

,ctx.config

,State

/FromRef

extraction) is unchanged.

This makes future context fields non-breaking to add.StorageMirrorStrategy

andBackupStrategy

merged intoReplicatedStrategy

.

The two strategies were the same primary-plus-secondaries replication engine;

they are now onestorage::strategies::replicated::ReplicatedStrategy with a

singleFailurePolicy

enum. Migrate:MirrorStrategy::new(p, s, MirrorAll)

ReplicatedStrategy::mirror(p, s, FailurePolicy::FailIfAny) ;

`BackupStrategy::new(p, s, BackupAll)`

→`ReplicatedStrategy::backup(p, s, FailurePolicy::FailIfAny)`

. OldFailureMode

maps:AllowMirrorFailure

/

AllowBackupFailure

AllowAll

,AtLeastOneFailure

AllowSingleFailure

,

`CountFailure(n)`

→`FailAtFailures(n)`

. Secondary writes for the former

backup strategy now run concurrently (previously sequential); the collected

errors and failure decision are unchanged.Local storage driver no longer defaults its root to/

.

storage::drivers::local::new() previously rooted the filesystem store at

/

, so any key — including one derived from user input — resolved against the

whole disk (e.g. down keyetc/passwd

read/etc/passwd

). It now roots

at the current working directory. Apps that relied on absolute-path keys should

switch tolocal::new_with_prefix("/your/root") to opt back into an explicit

absolute root.Background queue reworked into a TheQueueProvider

adapter interface.

`bgworker::Queue`

enum (`Postgres`

/Sqlite

/Redis

/None

) is now a newtype

overArc<dyn QueueProvider>

, so backends are pluggable (implement

QueueProvider

and wrap withQueue::from_provider ). All existing methods

(enqueue

,register

,run

,ping

,cancel_jobs

,clear_by_status

,

requeue

, …) keep the same signatures and behavior. Only two source-level

changes affect callers: construct a no-op queue withQueue::empty() instead

ofQueue::None , and code that pattern-matched the enum variants (e.g.

Queue::Postgres(pool, ..) to reach the raw pool) no longer compiles — use the

provider methods instead.Fallback middleware defaults to When the built-in fallback is404

.

enabled without an explicitcode

, it now returns404 Not Found

(matching

its docs and the bundled not-found page) instead of200 OK

. Apps that relied

on the enabled fallback returning200

must setcode: 200

explicitly. The

file-based fallback is unaffected (ServeFile

reports its own status).Previously the first{env}.local.yaml

now deep-merges over{env}.yaml .

existing file won and the other was ignored, so a.local.yaml

had to restate

the whole config. Both files are now layered with local precedence: mappings

merge recursively; scalars and sequences in local replace the base value

(sequences are not concatenated). Base keys now persist unless explicitly

overridden.More accurate HTTP status codes for errors.IntoResponse for Error

previously collapsed ~28 of 35 variants to500

.Model(EntityNotFound) now

returns404

,Model(EntityAlreadyExists) returns409

, model validation

and form-body rejections return4xx

(matching JSON rejections) instead of 500

. Genuinely-internal errors still return a generic500

. Handlers that

asserted on the old500

s will observe the corrected codes.It now takes a newJWT::algorithm() restricted to the HMAC family.

`loco_rs::auth::jwt::JWTAlgorithm`

enum (`HS256`

/HS384

/HS512

) instead of

jsonwebtoken::Algorithm . Asymmetric algorithms — which could never work with

Loco's shared base64 secret and silently produced broken tokens — are no longer

representable.Previously this middleware walkedremote_ip

middleware rebuilt onaxum-client-ip ;trusted_proxies

removed.`X-Forwarded-For`

right-to-left,

skipping any address in a configurabletrusted_proxies

CIDR list (or a

built-in RFC-1918 + loopback list), so it could see through a chain of one or

more trusted proxies. It now trusts exactlyone configured source

(source: ClientIpSource , defaultRightmostXForwardedFor

) and doesno

CIDR filtering — for the default it takes the last comma-separated value of the

lastX-Forwarded-For header verbatim, private or not. Single reverse-proxy

deployments are unaffected.Multi-hop topologies (CDN → LB → ingress) must

now configure their innermost hop to set the client IP (e.g. nginx

set_real_ip_from

/real_ip_recursive

), or pointsource

at a provider header

(CfConnectingIp

,CloudFrontViewerAddress

,XRealIp

,ConnectInfo

, …).

Note: an old config'strusted_proxies: key is silently ignored (unknown

field), so reviewremote_ip

before upgrading — this is a silent

security-relevant behavior change, not a load error. TheRemoteIP

extractor

and itsDisplay

output are unchanged.Updateauth_jwt

feature renamed toauth

.`features = ["auth_jwt"]`

→`["auth"]`

(it gates JWT auth and theApiToken

extractor, as before).Background-queue features collapsed.bg_pg

/bg_sqlt

worker

(Postgres+SQLite; free oncesqlx is compiled),bg_redis

worker_redis

(addsdep:redis ).default

now hasworker

(not Redis). A Redis queue needs worker_redis

; the queue backend is selected at runtime byqueue.kind

.Mailer(callTemplate::new(dir) now returnsResult

Template::new(dir)? ).

Email templates render through a full Tera instance so they support inheritance

and shared templates. Standard usage viaMailer::mail_template

is unchanged.

(#1694)(wasVars::cli_arg returnsResult<&str>

Result<&String>

). Callers that

relied on&String

(e.g..clone() into aString

) should use.to_owned() .

(#1732)

Added

First-class LLM / agent support. RootAGENTS.md

teaches agents to build

Loco apps;llms.txt

/llms-full.txt

are served from the site

(llmstxt.org). Everyloco new app ships an app-levelAGENTS.md

.Priority queues withWorker::perform_later_with_priority(...) ; mailer

jobs default to priority100

. ([#1693](https://github.com/loco-rs/loco/pull/1693))**Mailer implicit TLS (SMTPS / port 465)** via`mailer.smtp.tls`

. ([#1774](https://github.com/loco-rs/loco/pull/1774), fixes[#1773](https://github.com/loco-rs/loco/issues/1773))**Run the scheduler without a worker**—`--scheduler`

flag +

StartMode::ServerAndScheduler /WorkerAndScheduler

. ([#1742](https://github.com/loco-rs/loco/pull/1742), fixes[#1737](https://github.com/loco-rs/loco/issues/1737))- Email headers support in the mailer (
[#1700](https://github.com/loco-rs/loco/pull/1700)). **Multi-recipient emails.**`Mailer::mail_multi`

/mail_template_multi

and the

MultiEmail

/MultiArgs

types send one email to multiple To/CC/BCC

recipients (processed by a dedicatedMultiMailerWorker

). ([#1764](https://github.com/loco-rs/loco/pull/1764))**Email template inheritance & shared templates.** Mailer templates support

Tera`{% extends %}`

/`{% block %}`

and can share a common layout via

`Mailer::mail_template_with_shared`

/`Template::new_with_shared`

.loco generate mailer

now scaffolds asrc/mailers/shared/

base layout that the welcome

template extends. ([#1694](https://github.com/loco-rs/loco/pull/1694))- "Create user" task (
[#1670](https://github.com/loco-rs/loco/pull/1670)). `UuidUniqWithDefault`

andUuidWithDefault

types ([#1642](https://github.com/loco-rs/loco/pull/1642)).- Allow overriding a secure header (
[#1659](https://github.com/loco-rs/loco/pull/1659)). `Mailer::deliver_now`

/mail_template_now

for synchronous sends.

Complements`Mailer::mail`

/mail_template

(which enqueue via the background

worker, like Railsdeliver_later

) with an inline send that bypasses the

queue (Railsdeliver_now

).InsideMiddlewareStackExt

for surgical middleware-stack edits.

`Hooks::middlewares`

, tweak the default stack instead of rebuilding it:

`stack.insert_before("cors", ..)`

,`.insert_after(..)`

,`.replace(..)`

, and

.delete("logger") — matched by middleware name (Rails'

config.middleware.insert_before

/delete

). Available via the prelude.Optional JWT extraction.JWT

now implementsOptionalFromRequestParts

,

so a handler can takeOption<JWT>

to serve authenticated and anonymous

callers from one endpoint (Some

when a valid token is present,None

otherwise).Ergonomic verb-explicit route methods.Routes

now hasget

/post

/

put

/delete

/patch

/head

/options

/trace

builder methods —

Routes::new().get("/ping", ping) alongside the existing

.add("/ping", get(ping)) . They record the HTTP verb directly (exact

cargo loco routes

output without relying on the debug-format regex).

Purely additive;add

is unchanged.Opt-in background-job reaper (visibility timeout). Each queue backend's

config accepts areaper: { age_minutes, interval_seconds } block. When set,

the worker periodically requeues jobs stranded inProcessing

(e.g. by a

crashed worker) back toQueued

, instead of requiring a manual

cargo loco jobs requeue

. Disabled by default — existing behavior is unchanged.TLS to managed Postgres and Redis. Postgres TLS works via the connection

URL (sslmode=require ,sslrootcert=...

) with no feature flag, and a new

redis_tls

feature enablesrediss://

for both the queue and cache Redis

backends (webpki roots, pure-Rust`ring`

provider — no C toolchain). New

how-to: "Connect to Postgres and Redis over TLS". ([#1191](https://github.com/loco-rs/loco/issues/1191),[#1341](https://github.com/loco-rs/loco/issues/1341))**Typed, streaming**— counterpart to`db::dump::<A>()`

`db::seed::<A>()`

that

streams rows through their entityModel

straight to disk (memory bounded to

a single row) with full type fidelity. NewHooks::dump (default dumps every

table; override it to call`db::dump`

per entity) backs`cargo loco db seed --dump`

. ([#1691](https://github.com/loco-rs/loco/issues/1691))building`logger::init_layer`

/`logger::init_env_filter`

are now public

blocks, so an app overridingHooks::init_logger

can reuse Loco's formatting

and filter policy while adding its own layers (e.g.tracing-flame

, OTLP).

(#1753)

Changed

  • Wrap TeraView

inArc

to reduce runtime memory usage (#1703). - Refactor users model to reuse find_by_api_key

inAuthenticable

([#1706](https://github.com/loco-rs/loco/pull/1706)). - Split error detail generic parameters (
[#1709](https://github.com/loco-rs/loco/pull/1709)). - Update

loco-new

for the new Rhai version (#1704). - Replaced hand-rolled Cargo.lock

parsing with thecargo-lock

crate; retired

duct_sh

. TheError

→ HTTP-status mapping is now exhaustive.IntoResponse for Error

match dropped its trailing`_ => 500`

wildcard: every variant (and every

nestedModelError

variant) is now classified explicitly, so adding a new

error variant is a compile error until its status is chosen — it can no longer

silently default to500

. TheError

enum is also reorganized into client-facing vs internal/infra regions. Behavior is unchanged (all infra

errors still map to500

); no variant was renamed, so existing code is

unaffected.Rust edition 2024.loco-rs

,loco-gen

,xtask

, and theloco

new-app

generator now compile on edition 2024 (MSRV floor unchanged at 1.94; edition

2024 needs ≥ 1.85). Editions are per-crate, so apps depending on Loco need not

change. Newly generated apps stay on edition 2021 for now.- Deduplicated the Postgres and SQLite background-queue providers: the shared

Job

/JobRegistry

/RunOpts

now live in one module behind aDriver

trait

(internal refactor, no behavior or API-path change). - In-memory cache now uses
`moka::future::Cache`

instead of wrapping the

synchronous cache behind#[async_trait]

(removes a sync-behind-async smell; no API change). - Cookie token extraction now uses axum_extra

'sCookie::value() instead of

hand-parsing the cookie string (byte-identical behavior). - Internal de-duplication pass (no public-API-path or behavior change unless

noted): the response helpers in`format`

are now single-sourced through

RenderBuilder

; theJWT

/JWTWithUser

extractors share one validate/decode

helper; the six validate extractors are generated from shared decoder fns +

two error-tier macros; the byte-identical`Job`

struct is shared across the

SQL and Redis queue backends; the twin`cli::main`

functions share one

dispatch_common

; and duplicate env-var name constants were removed. format

's two response paths were converged onto axum's canonical behavior:

RenderBuilder::json andRenderBuilder::redirect_with_header_key

are now

infallible with respect to bad input (they return a500

response, matching

`axum::Json`

/`axum::response::Redirect`

) instead of returningErr

.

Fixed

Loco's timestamptz columnsdb seed --dump

datetime round-trip on SQLite.

default toCURRENT_TIMESTAMP

, which SQLite stores as space-separated text

("YYYY-MM-DD HH:MM:SS" ); dumps captured that verbatim and then failed

chrono's RFC3339 parse on re-seed (Json("premature end of input") ). Dumps now

normalize such datetimes to RFC3339 (already-RFC3339 text is untouched).

(#1736,#1691)cargo fmt error inloco-new

([#1669](https://github.com/loco-rs/loco/pull/1669)).- UUID pattern in form field generation (
[#1665](https://github.com/loco-rs/loco/pull/1665)). - Clippy warnings for recent Rust (
[#1705](https://github.com/loco-rs/loco/pull/1705)). - Add tests for the auth extractor (
[#1671](https://github.com/loco-rs/loco/pull/1671)). **Postgres/SQLite queue backends now behave consistently.** Two divergences

between the Postgres and SQLite job backends are fixed: (1)enqueue

on

Postgres previouslyswalloweda tag-serialization error (storingtags = null

); it now propagates the error like SQLite. (2)complete_job without a

repeat interval on Postgres stampedrun_at = NOW() on the completed row while

SQLite left it untouched; Postgres now leavesrun_at

as-is, matching SQLite

(the interval path still reschedulesrun_at

on both). The sharedto_job

row

mapper is now single-sourced across both backends.Storage mirror fan-out no longer stops at the first failing secondary.

rename

,copy

, andupload_stream

checked the failure modeinsidethe

secondary loop and returned early on the first failure, silently leaving later

mirrors stale (upload

/delete

were already correct). All five mutating

methods now share one helper that attempts every secondary (concurrently) and

applies the failure mode once.Postgres TheBOOLEAN

columns are no longer dropped fromdump_tables

.

decode probe chain had nobool

arm, so PG booleans (which don't fall back to

the numeric arms like SQLite's integer-backed booleans) were silently omitted.Hooks::on_shutdown

now runs in worker-only start modes.WorkerOnly

and

WorkerAndScheduler

bypassed`H::serve`

(the hook's only caller); the shutdown

hook is now invoked on their shutdown path too.Postgres admin/maintenance URI is derived with the Building iturl

crate.

viadb_uri.replace(db_name, "/postgres")

corrupted the URI when the database

name also appeared in the host or credentials.Foreign-key names are normalized consistently.reference_id

received a

normalized table name increate_table

but raw names in

add_reference

/remove_reference

, so irregular plurals produced mismatched FK

column/constraint names between creation and later add/remove.ViewEngine

extractor now rejects gracefully (HTTP500

) when the opt-in Tera layer is absent, instead of declaringInfallible

and then panicking.cargo loco routes

lists every HTTP verb of a multi-method route (route

introspection previously reported only the first).- Removed a fossilized 3-second sleep

on every Redis queue boot (a leaked

test-isolation artifact; Postgres/SQLite had no equivalent). - Password redaction in test snapshots ( cleanup_user_model

) now targets the

quoted value precisely; the previous pattern had a degenerate quantifier that

swallowed the field followingpassword

. - Postgres test-database cleanup now completes synchronously (a joined worker

thread) instead of a fire-and-forget task, so parallel test runs no longer

leak databases;PostgresTest

also builds its connection strings with the

url

crate rather than a corruption-prone substring replace. RenderBuilder::template now threads the builder's chainedstatus

/header

/

etag

/cookies

through to the response; it previously delegated to the free

html() and silently dropped them.llms.txt

: twoCore concepts

links pointed at doc pages that don't exist

(the-app/configuration/ ,the-app/testing/

); repointed to the sections that actually document them. A newcargo xtask llms-check

CI step now verifies the

curated LLM docs against the docs tree so these links can't drift silently.

Removed

Removed four low-value/dependency-leaking variantsError

enum narrowing. that all mapped to HTTP 500 and were never matched:Error::EnvVar

,

`Error::SemVer`

,`Error::TaskJoinError`

, and`Error::Hash`

(hashing errors now

surface asError::Message ).Error

remains#[non_exhaustive] , so exhaustive

matches already require a wildcard arm and are unaffected.- Deleted shipped-but-dead code: the never-compiled

controller/middleware/_archive/content_etag.rs

module and a commented-out block of backtrace-blocklist regexes.

── more in #developer-tools 4 stories · sorted by recency
── more on @loco 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/release-v1-0-0-loco-…] indexed:0 read:12min 2026-07-29 ·