Library / SDK
CodeDredd/pinia-orm avatar
CodeDredd/pinia-orm

pinia-orm puts a query builder inside your Pinia stores

The Pinia plugin to enable Object-Relational Mapping access to the Pinia Store.

528 stars45 forksTypeScriptMIT

At a glance

What is it?
pinia-orm is a Pinia plugin that turns store state into a set of model classes with relations, a Laravel-style query builder and an axios plugin, so a front end can hold normalized relational data and query it. The 2.x line raises the floor to Pinia 3, Node 22.12 and TypeScript 5.2, and the model layer is built on standard ECMAScript decorators.
Who is it for?
pinia-orm fits a Vue team that keeps relational data in the browser, wants type safe models and relations, and has already decided the server is not the only source of truth for it. It does not fit a project whose data must be validated or enforced on the server, and it does not fit a team still on Pinia 2 or Node below 22.12.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a front end would want an ORM at all

The pitch in the README is that pinia-orm is an intuitive, type safe and flexible ORM for Pinia, one that normalizes relational data in your stores so you can query it. That last clause is the point, because normalized data and a query builder are normally two different layers of an application, and a front end team rarely has the luxury of both.

The problem shows up as soon as an API returns nested objects. A user with a list of todos, each carrying an author, produces a tree of duplicated data in a store, and every component that shows a piece of it has to know the shape. The alternative is to normalize the data into flat tables and let a layer above the store handle the joins. Writing that layer by hand is the work pinia-orm takes over.

The audience is Vue developers using Pinia who want that layer without leaving the client, and who want the API to look like something they already know. The README lists a Laravel-style query builder with where, whereLike, whereHas, orderBy, groupBy, aggregates, updateOrCreate and firstOrCreate, and the full set of relations from hasOne and belongsTo through to the morph variants.

The design decision to note is that this is a plugin, not a store of its own. The plugin installs itself on a Pinia instance, and your models live alongside your ordinary stores rather than replacing them. A team can therefore adopt it for one domain, the entity list that the rest of the app queries, and leave everything else as plain Pinia state.

Models are classes with decorators

The model layer is where the type safety comes from. A model extends Model and declares a static entity name, and the fields are described with standard ECMAScript decorators imported from the decorators entry point.

ts
import { Model, useRepo } from 'pinia-orm'
import { Attr, HasMany, Str, Uid } from 'pinia-orm/decorators'

class Todo extends Model {
  static entity = 'todos'

  @Uid() id!: string
  @Str('') text!: string
  @Attr(null) userId!: string | null
}

The decorators carry the defaults and the types in one place: a Uid field for the primary key, a Str field with a default empty string, an Attr field whose value is nullable. A second model declares the relation to the first.

ts
class User extends Model {
  static entity = 'users'

  @Uid() id!: string
  @Str('') name!: string
  @HasMany(() => Todo, 'userId') todos!: Todo[]
}

The relation is expressed as a function returning the related model and the foreign key, and the property type carries the array type, so a component that reads user.todos gets a typed array rather than an any.

The features list adds a lot on top of that skeleton: single table inheritance with nested discriminators, lifecycle hooks, mutators, casts, hidden fields and query caching. Those are the features that decide whether pinia-orm can carry a domain model or only a list of records, and they are the reason the project is more than a query helper.

One constraint follows from the design: the decorator syntax means a TypeScript project with a build step. The README links a TypeScript getting started page specifically for this, which suggests there is configuration involved rather than a zero-setup import.

Repositories are where the querying happens

Models describe data; repositories do the work. You get one with useRepo, passing the model class.

ts
const userRepo = useRepo(User)

userRepo.save({ id: '1', name: 'John', todos: [{ text: 'Read the docs' }] })
const usersWithTodos = userRepo.with('todos').get()

The save call takes a plain object and writes it into the store, and because the todos are nested under the user, the save also normalizes them into their own entity table with the foreign key filled in. The retrieval then uses with to pull in the relation and get to read the result.

That is the whole loop, and it is the familiar one: save a graph, query a graph, get back plain data. The query builder is where the Laravel resemblance shows up most clearly, with where for conditions, whereLike for pattern matching, whereHas for existence checks across a relation, orderBy with sorting that can use Intl.Collator for locale aware ordering, groupBy and aggregates for reporting, and the two creation helpers that either update a matching record or insert a new one.

The relation set is broad enough to cover most schemas: hasOne, hasMany, belongsTo, belongsToMany, hasManyThrough, and the four morph relations, morphOne, morphTo, morphMany and morphToMany, plus morphedByMany. The morph relations are the interesting ones for a front end, because a comment or a notification that can belong to different kinds of parent is a shape a plain nested store handles badly.

The repository pattern also gives a natural place for the query caching the features list mentions, since the cache belongs to the repository rather than to the component that happens to trigger the query.

Installing the plugin and storing the first model

Installation is one npm command, since pinia-orm is a plugin for Pinia and needs Pinia itself.

bash
npm install pinia pinia-orm

The plugin is then registered on the Pinia instance, which is the same shape as any other Pinia plugin.

ts
import { createPinia } from 'pinia'
import { createORM } from 'pinia-orm'

const pinia = createPinia().use(createORM())

After that, the models, the repository and the save call shown earlier are the whole first application. The entity name in the model, such as todos, is the key the normalized data is stored under, and the README's example saves a user together with a nested todo and then reads the user back with its relation loaded.

The package ships separate entry points, which is why the import in the example is split in two. The core API, Model and useRepo, comes from pinia-orm, and the field decorators come from pinia-orm/decorators. If your build does not resolve the subpath, that is the thing to check first, and the TypeScript getting started page in the documentation is where the configuration details live.

The documentation also points to a comparison with vuex-orm and a migration guide, both listed in the README, so a team arriving from the older library or from its Vuex era has somewhere to go before writing code.

The 2.x requirements table is the adoption gate

The requirements are given as a table, and the 2.x row is the one to read first.

markdown
| pinia-orm | pinia   | Node     | TypeScript |
|-----------|---------|----------|------------|
| 2.x       | >= 3    | >= 22.12 | >= 5.2     |
| 1.x       | 2.x     | >= 14    | >= 4.x     |

Two floors jump at once in the 2.x line. Pinia has to be version 3 or newer, and Node has to be at least 22.12, with TypeScript 5.2 as the minimum. The older line stays compatible with Pinia 2, Node 14 and TypeScript 4.

That Node requirement is the practical blocker for a lot of teams. A Node floor of 22.12 means a build pipeline, a container base image or a CI runner that is on a current LTS, and it also means the type definitions and the standard decorator syntax have to work with a modern TypeScript configuration, which is exactly what the linked TypeScript guide is about.

The release history shows a long 2.0.0 run. The stable 2.0.0 tag was published on 2026-07-20, and it was preceded by 2.0.0-alpha.1 in February 2025 and 2.0.0-alpha.2 in April 2025, which is a sign of a major rewrite that took a while to settle.

The README also states that the project is on its second major line and points to a migration guide, so anyone on the 1.x line should read that before upgrading rather than after.

A monorepo, a Nuxt module and an axios plugin

The repository layout explains how the package is built and who it is aimed at. It is a pnpm workspace with a packages/ directory, playgrounds/, a docs/ folder, a docs-playground/ folder, pnpm-workspace.yaml, pnpm-lock.yaml, lerna.json and a packageManager field pinning pnpm at 9.4.0. Renovate is configured for dependency updates, and vercel.json is there for the documentation site.

The workspace script tells you which packages are published.

json
    "build:ci": "pnpm --filter './packages/{pinia-orm,normalizr,axios}' build",

So there is a core package, a normalizr package and an axios package, and the axios one is the plugin that hydrates store state from HTTP responses rather than leaving you to write the fetch and the normalization by hand. That is the piece that closes the loop with a backend API, and it is the reason the features list mentions an axios plugin alongside the ORM itself.

The Nuxt side is a first class module covering Nuxt 3 and Nuxt 4, so a Nuxt project gets the plugin registered and the models available without the manual wiring shown earlier.

The development workflow is documented for contributors: enable Corepack, install with pnpm install, build the packages with pnpm build:stub and pnpm build:ci, and run the tests with pnpm test, with an interactive UI variant under packages/pinia-orm. The test script runs across the workspace packages, and a separate script type checks the whole project.

The related projects the README lists are Vuex ORM and Vuex ORM Next, which is the clearest sign of where this library came from and which audience it inherited.

Where a client side ORM is the wrong answer

The honest limitation is the one every client side ORM has, and pinia-orm does nothing about it. The data is in the browser, so the invariants it can help you with are the ones the browser can enforce. A relation that matters for money, for permissions or for inventory has to be enforced where the data is written, on the server, and a normalized client cache does not change that. If your problem is that the front end makes bad requests, pinia-orm makes the responses easier to consume, which is a different problem.

The second limitation is the floor it sets. Pinia 3, Node 22.12 and TypeScript 5.2 is a modern stack, and the decorator based model definitions mean a build step and a TypeScript configuration that supports standard ECMAScript decorators. A team on an older toolchain has to stay on the 1.x line, which is a different feature set, and a team that has not standardised on decorators at all will find the model layer the least comfortable part of the setup.

The alternative is the one the README itself points at. vuex-orm is the predecessor for the Vuex generation, and the documentation carries a comparison with it, so migrating rather than starting fresh is a documented path for teams coming from that library. The other alternative is to keep the server as the only copy of the data, expose a typed API and let the front end treat responses as read only views, which removes the whole class of cache invalidation problems that a client side ORM creates.

On upkeep, the repository is not archived, the last push was on 2026-10-03, and the licence is MIT. The single-maintainer shape is visible in the sponsors and donation links and in the fact that the README asks for help keeping the project going, which is a reasonable thing to weigh when you are choosing a dependency for the data layer of your application.

Editorial conclusion

pinia-orm fits a Vue team that keeps relational data in the browser, wants type safe models and relations, and has already decided the server is not the only source of truth for it. It does not fit a project whose data must be validated or enforced on the server, and it does not fit a team still on Pinia 2 or Node below 22.12. Before adopting it, run the README quick start against one existing store, then check the requirements table and the migration guide linked from the README, since 2.0.0 shipped on 2026-07-20 after two alpha releases. The last push was on 2026-10-03 and the licence is MIT.

Frequently asked questions

How do I install and set up pinia-orm?

Install both packages with npm install pinia pinia-orm, then register the plugin on the Pinia instance with createPinia().use(createORM()). Models are defined as classes extending Model, and you reach them through useRepo(ModelName).

Which versions of Pinia, Node and TypeScript does pinia-orm 2.x need?

The README requirements table lists pinia-orm 2.x as needing pinia 3 or newer, Node 22.12 or newer and TypeScript 5.2 or newer. The 1.x line is the one compatible with pinia 2.x, Node 14 and TypeScript 4.x.

How do I define relations between models in pinia-orm?

With the decorators from pinia-orm/decorators. The example uses @HasMany(() => Todo, 'userId') on the User model, with the related model class and the foreign key, and the property typed as an array. The features list also covers hasOne, belongsTo, belongsToMany, hasManyThrough and the four morph relations.

Does pinia-orm work with Nuxt and with API responses?

The README lists a first class Nuxt module covering Nuxt 3 and Nuxt 4, and an axios plugin for hydrating the store from HTTP responses. The repository workspace builds a pinia-orm package, a normalizr package and an axios package.

Is pinia-orm actively maintained?

The repository is not archived and the last push was on 2026-10-03. The stable 2.0.0 release was published on 2026-07-20, after 2.0.0-alpha.1 in February 2025 and 2.0.0-alpha.2 in April 2025, and the README asks for sponsors and donations to keep the work going.

Official sources

  1. CodeDredd/pinia-orm on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/codedredd-pinia-orm.svg)](https://hysenlabs.com/projects/codedredd-pinia-orm)

Was this useful?