# New IndexedDB package (based on elm-concurrent-task)

**URL:** https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559
**Category:** Request Feedback
**Created:** [February 17, 2026, 1:29pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559 "2026-02-17T13:29:06Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 17, 2026, 1:29pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/1 "2026-02-17T13:29:06Z")

</div>

Hi, I’m about to publish a package to make it easier to use IndexedDB from elm apps. Would love some feedback before hitting the publish button of version 1.0.0.

- Source code here: [GitHub - mpizenberg/elm-indexeddb: IndexedDB support for Elm via elm-concurrent-task](https://github.com/mpizenberg/elm-indexeddb)
- Based on @andrewMacmurray elm-concurrent-task
- Two parts, an IndexedDb elm module and a JS companion package
- Relatively simple and type-safe API. Preview here: [Elm Doc Preview](https://elm-doc-preview.netlify.app/?repo=mpizenberg/elm-indexeddb&version=elm-doc-preview)
- Example TODO app exercising almost all of the API.
  - Code here: [elm-indexeddb/examples/todo-app at main · mpizenberg/elm-indexeddb · GitHub](https://github.com/mpizenberg/elm-indexeddb/tree/main/examples/todo-app)
  - Running here: [Elm IndexedDB Example](https://mpizenberg.github.io/elm-indexeddb/)

---

<div class="post-metadata">

### Author: ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)
#### Post date: [February 17, 2026, 3:33pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/2 "2026-02-17T15:33:57Z")

</div>

Love that you’re doing this! I don’t have a concrete use case right now, but definitely something I’ve wanted in the past. `elm-concurrent-task` wasn’t around when I last looked at this and it definitely seems to be a key piece of the puzzle. Again, thank you!

---

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 17, 2026, 5:04pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/3 "2026-02-17T17:04:33Z")

</div>

With all the new LLM powers we have, I’ve been doing a lot of vibecoding for small utility mobile apps, local-first. And it pains me that I’ve been using SolidJS instead of Elm for it (even though solid is quite nice). So I’m trying to fill the gaps to be able to unleash claude with elm powers ^^. This means better support of a few things I need. And for local-first PWAs, IndexedDB is a must have!

So this IndexedDB package is a first step in my larger goal 🙂

---

<div class="post-metadata">

### Author: ![andrewMacmurray](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/andrewmacmurray/32/2797_2.png) [@andrewMacmurray](https://discourse.elm-lang.org/u/andrewMacmurray)
#### Post date: [February 17, 2026, 6:38pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/4 "2026-02-17T18:38:40Z")

</div>

Had a leaf through the Api and example and it looks great! I think you’re the first to publish a lib based on concurrent-task 🙂

---

<div class="post-metadata">

### Author: ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)
#### Post date: [February 17, 2026, 7:13pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/5 "2026-02-17T19:13:56Z")

</div>

Don’t want to diverge too much from the topic, happy to discuss in a separate thread/space as well. I’m curious if you’ve hit any issues with your PWA work and iOS or Android? Either IndexedDB related or anything else.

---

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 17, 2026, 7:46pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/6 "2026-02-17T19:46:31Z")

</div>

Mostly, I’ve had trouble with notifications. But yeah let’s move that to the slack.

---

<div class="post-metadata">

### Author: ![john\_s](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/john_s/32/4425_2.png) [@john\_s](https://discourse.elm-lang.org/u/john_s)
#### Post date: [February 18, 2026, 5:48pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/7 "2026-02-18T17:48:22Z")

</div>

Thank you for building this!!!

I have a couple of suggestions to improve type safety and ergonomics but I am pretty sure you intentionally decided against what I’ll suggest. Some (maybe all) of my suggestions may be based on ignorance of IndexedDb or the intended use cases.

The `Store` type has a phantom type argument to protect portions of the Api related to the three different key categories of `Explicit`, `Inline`, and `Generated` which is really great. However

1. When using `get` one can pass an Int, Float or compound key to a store that has a `String` key and that seems like an outright run-time error.
2. When using `get` or `putAt` one is limited to simple data types like string, int, etc. so one cannot use a semantically rich id type like `type PersonId = PersonId String` or `type PetId = PetId String`.
3. Users have to include the decoder for every `get` and provide a `Value` for write operations which feels type-unsafe and a little tedious. Though perhaps this is a feature and not a bug in that it allows for flexibility? If so then discussing the use cases in the documentation might be nice.

What if we enhanced the type signature of store a wee bit?

```elm
type alias KeyCodec key =
    { encode : key -> Key -- this makes it so that you can only produce appropriate keys
    , decode : Key -> Result String key
    }

-- keyCategory would be your marker types ExplicitKey, InineKey, GeneratedKey
type Store keyCategory key model
    = Store
        { config : StoreConfig -- holds the name, key path and auto increment 
        , keyCodec : KeyCodec key
        , modelCodec : 
             { encoder : model -> Value
             , decoder : Decoder model
             }
        }

```

Now there would be enough type information in the store to rigorously type the `get` and `putAt` operations with respect to the key and users wouldn’t have to pass the encoder/decoder every time.

Of course, this makes a couple of assumptions

1. This is to be a high level library that is used by the application code directly. If this is intended as a lower level library put behind an opaque data type (PetStoreDb or TodoDb) then you might want the flexibility and both the type safety and ergonomic would be moot because 96% of the code would go through the explicitly defined / strict application or domain Api.
2. I am assuming that all keys in a store should have a homogeneous type.

---

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 18, 2026, 7:43pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/8 "2026-02-18T19:43:00Z")

</div>

Thanks for the feedback!

> When using get one can pass an Int, Float or compound key to a store that has a String key

That’s actually not forbidden by IndexedDB, and though I thought about limiting it, I eventually decided on not doing it. So you can use multiple types of keys for the same store.

> When using get or putAt one is limited to simple data types like string, int, etc.

Indeed. For keys, IndexedDB support numbers, dates, strings, binary, and arrays. Among those, it was easy to support numbers, strings, and arrays. Adding more complex keys would require you first converting these keys into one of the supported types. So it felt like it was simpler and versatile enough to do as currently.

> Users have to include the decoder for every get and provide a Value for write operations which feels type-unsafe and a little tedious.

Indeed, it’s on purpose. I often have stores that are generic “stuff” stores where I put things of different nature in it. Thus it is required to be able to use different decoders for each of the read into the DB. I suppose we could make that more typesafe by defining at the store creation if a store has a fixed “shape” or is “multi-shape”. In the former, we could provide the codecs at creation time. In the latter, use Value and codecs at execution time.

---

<div class="post-metadata">

### Author: ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)
#### Post date: [February 18, 2026, 9:15pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/9 "2026-02-18T21:15:06Z")

</div>

Regarding keys/phantom types, I’ve been thinking about how I’m going to use this in a project I’m working on and I’ve been thinking that I could have my own API on top of this IndexedDB package. Similar to how I might have a `IdDict value` module on top of `Dict key value` where internally it’s

```elm
type Id key = Id String

type IdDict key value = IdDict (Dict String value)

```

This separation would let me play a little more lose with IndexedDB during my initial exploration phase and then harden it later when I know what I want. Not sure if that’s a good idea or not.

---

<div class="post-metadata">

### Author: ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)
#### Post date: [February 19, 2026, 2:23am UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/10 "2026-02-19T02:23:06Z")

</div>

I love that 36 hrs ago I didn’t have a use for this and right now I’m looking at vendoring it so I can keep working on this new app 😂

---

<div class="post-metadata">

### Author: ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)
#### Post date: [February 19, 2026, 2:59pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/11 "2026-02-19T14:59:19Z")

</div>

I’ve been playing around a bit the raw package and was wondering about the `GeneratedKey` stores. If possible I’d prefer the DB does the ID/key generation but I don’t see a way with the current API of being able to do a put/upsert on a store that uses `GeneratedKey`s. Am I missing something or is this currently unsupported?

---

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 19, 2026, 3:04pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/12 "2026-02-19T15:04:40Z")

</div>

Ah, it’s missing indeed. I’ve mostly used indexeddb as an immutable store in my apps, so I didn’t think of it. How would you suggest to handle that? Different function, making the phantom type more advanced with capabilities (extensible records) seems a bit overkill for a small number of functions.

---

<div class="post-metadata">

### Author: ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)
#### Post date: [February 19, 2026, 3:47pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/13 "2026-02-19T15:47:15Z")

</div>

I’m not sure I know enough about the differences with the existing `put` vs `putAt` functions beyond them operating on different kinds of stores. I can take a look later this evening maybe.

My initial thought was that `autoIncrement` means the key is an Int so we should be able to assume it’s an `IntKey`, but then we can’t use the existing `put` or `putAt` because they’re restricted to only a single type of store.

---

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 19, 2026, 4:13pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/14 "2026-02-19T16:13:50Z")

</div>

Yeah, the auto-generated keys are numbers. In theory, you can use something like put or add even on stores that have auto-generated keys, so where it’s not needed to provide keys.

Doing that separation of store types in the package was a design decision to make it a clear split between stores were you manage keys VS stores that you don’t. But I forgot about edits on the auto-increment store.

Maybe a function named `replace` would make sense.

```elm
replace : Db -> Store GeneratedKey -> Key -> Value -> ConcurrentTask Error ()

```

People could abuse it to also just put stuff in a store with auto-increment with a custom key. Internally this would essentially be the same JS implementation as `putAt` anyway. But at least the naming of this function conveys better the intent. With `Store GeneratedKey` you use either `insert` or `replace`.

---

<div class="post-metadata">

### Author: ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)
#### Post date: [February 19, 2026, 4:21pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/15 "2026-02-19T16:21:10Z")

</div>

`replace` sounds nice. There’s definitely a chance for abuse, but that’s true of many things

---

<div class="post-metadata">

### Author: ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)
#### Post date: [February 20, 2026, 1:39am UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/16 "2026-02-20T01:39:00Z")

</div>

Another thought occurred to me. When I’m loading a store, is it possible to load both the data and the key for the data? Right now I think it’d be 2 separate requests. 1 to get all of the keys and another to load their respective values. The downside to this is it would mean N + 1 requests to load all data.

Another option would be to store the keys in the object I’m saving. This feels a little wrong as I’m storing the key twice essentially. Also not sure how to handle the case where I’m saving a new instance of an object. I’d need to have a placeholder key to store in the object and then immediately overwrite if after saving? Something like

```elm
{ obj | key = fake }
|> store
|> andThen (\realKey -> { obj | key = realKey })

```

---

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 20, 2026, 9:57am UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/17 "2026-02-20T09:57:35Z")

</div>

Right, it’s another gap in the API.

So first, this isn’t an issue for `Store InlineKey` since the key is literally in the JS data, and I suppose also in the equivalent Elm data. So this is mostly a problem for `ExplicitKey` and `GeneratedKey`.

IndexedDB will answer with the keys and with the values in the same order. So in JS this isn’t a problem because we can make both these store calls in the same transaction to guaranty consistency when mapping the keys with the values. Here however, for simplicity I’ve made each function be executed on its own transaction. This may cause issues since the keys and values retrievals are done in two different transactions.

The simplest fix seems to change the `getAll` function to have the following signature:

```elm
getAll : Db -> Store k -> Decoder a -> ConcurrentTask Error (List (Key, a))

```

---

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 20, 2026, 9:58am UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/18 "2026-02-20T09:58:02Z")

</div>

**API updates for elm-indexeddb**

A few changes to close gaps in the API around `GeneratedKey` stores and key retrieval.

**`getAll` now returns keys alongside values**

```elm
-- before
getAll : Db -> Store k -> Decoder a -> ConcurrentTask Error (List a)

-- after
getAll : Db -> Store k -> Decoder a -> ConcurrentTask Error (List ( Key, a ))

```

Previously, retrieving both keys and values required two separate calls (`getAll` + `getAllKeys`), each running in its own transaction with no consistency guarantee. Now `getAll` fetches both in a single transaction. The overhead of also returning keys when you only need values is minimal. For `InlineKey` stores the key is already embedded in the value so this is mostly relevant for `ExplicitKey` and `GeneratedKey` stores.

**`insertMany` now returns the generated keys**

```elm
-- before
insertMany : Db -> Store GeneratedKey -> List Value -> ConcurrentTask Error ()

-- after
insertMany : Db -> Store GeneratedKey -> List Value -> ConcurrentTask Error (List Key)

```

The returned keys are in the same order as the input values, so you can associate each value with its generated key.

**New `replace` and `replaceMany` for `GeneratedKey` stores**

```elm
replace : Db -> Store GeneratedKey -> Key -> Value -> ConcurrentTask Error ()
replaceMany : Db -> Store GeneratedKey -> List ( Key, Value ) -> ConcurrentTask Error ()

```

These let you update existing records in a `GeneratedKey` store using a key previously obtained from `insert` or `insertMany`. `replaceMany` runs all updates in a single transaction.

---

<div class="post-metadata">

### Author: ![john\_s](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/john_s/32/4425_2.png) [@john\_s](https://discourse.elm-lang.org/u/john_s)
#### Post date: [February 20, 2026, 5:58pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/19 "2026-02-20T17:58:58Z")

</div>

That totally makes sense. It makes sense that this should be a general purpose low-level library designed to grant Elm developers access to IndexedDb so it should focus on abilities and flexibility over premature type safety. I have a tendency to want to lock things down with types perhaps a little too soon.

The Api that I suggested would be appropriate only for homogeneous stores, which is a subset of the total. IndexedDb supports heterogeneous data in a store so that engineers don’t need to do migrations - among other use cases.

Perhaps in a future version you could add support for

- Indexes
- Upgrade on open like [idb](https://www.npmjs.com/package/idb#opendb) does

---

<div class="post-metadata">

### Author: ![mattpiz](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/mattpiz/32/860_2.png) [@mattpiz](https://discourse.elm-lang.org/u/mattpiz)
#### Post date: [February 20, 2026, 6:40pm UTC](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559/20 "2026-02-20T18:40:25Z")

</div>

Indexes would be nice for a future version indeed.

For upgrade on open, I’m wondering if this would require a full featured DSL because we can’t just give it JS code from Elm. (Well I guess we could but I’d rather not do that ahah)

Currently, I feel like the only way to control it is to always keep old stores and just deprecate them in new versions. It would work this way:

- Always have a “version” store that just keep the last version that was used before the current open. Check the previous version used just after `open` call.
- If we are on a new schema version:
  - Apply some logic to manually process a migration
  - Upgrade the schema version in the version store
  - Clear the old stores of their data to free up space

Encode all that logic into a `migrateData` task and call it after open.

```elm
IndexedDb.open mySchema
    |> ConcurrentTask.andThen migrateData

```

[Next page](https://discourse.elm-lang.org/t/new-indexeddb-package-based-on-elm-concurrent-task/10559.md?page=2)
