---
title: createRegistry - Item Registration and Indexing for Vue 3
meta:
- name: description
  content: A foundational composable for building registration-based systems, managing
    collections of registered items with automatic indexing, and lifecycle management.
- name: keywords
  content: createRegistry, registry, composable, Vue, state management
features:
  category: Composable
  label: 'E: createRegistry'
  github: /composables/createRegistry/
  level: 3
related:
  - /composables/reactivity/use-proxy-registry
  - /composables/selection/create-selection
  - /composables/registration/create-tokens
  - /composables/forms/create-form
---

# createRegistry

Foundation for ordered, keyed collections. Items are registered with IDs and indexes, and can be looked up by ID, index, or value.

<DocsPageFeatures :frontmatter />

## Usage

The `createRegistry` composable provides a powerful interface for managing collections of items in a registration-based system. It allows you to register, unregister, and look up items efficiently, while maintaining an index for quick access.

```ts collapse
import { createRegistry } from '@vuetify/v0'
import type { RegistryTicket } from '@vuetify/v0'

interface Item extends RegistryTicket<string> {
  label: string
}

const registry = createRegistry<Item>()

// Register individual items
const a = registry.register({ label: 'Alpha' })
const b = registry.register({ label: 'Beta' })
const c = registry.register({ label: 'Gamma' })

console.log(registry.size) // 3
console.log(a.index)       // 0

// Look up by id
const found = registry.get(b.id)
console.log(found?.label)  // 'Beta'

// Patch a field without replacing the ticket
registry.upsert(b.id, { label: 'Beta (updated)' })

// Move to a new position
registry.move(a.id, 2)
console.log(a.index)       // 2

// Remove one
registry.unregister(c.id)
console.log(registry.size) // 2

// Bulk load
registry.onboard([
  { id: 'x', label: 'X' },
  { id: 'y', label: 'Y' },
])

// Bulk remove
registry.offboard(['x', 'y'])
```

## Context / DI

Use `createRegistryContext` to share a registry across a component tree:

```ts
import { createRegistryContext } from '@vuetify/v0'

export const [useItems, provideItems, items] =
  createRegistryContext({ namespace: 'my:items' })

// In parent component
provideItems()

// In child component
const registry = useItems()
registry.register({ id: 'item-1', value: 'First' })
```

## Architecture

`createRegistry` is the foundation for specialized registration systems:

```mermaid "Registry Hierarchy"
flowchart TD
  createRegistry:::primary --> createModel
  createModel --> createSelection
  createModel --> createSlider
  createRegistry --> createTokens
  createRegistry --> createForm
  createRegistry --> createQueue
  createRegistry --> createTimeline
```

Each branch extends the base ticket pattern with domain-specific capabilities. See individual composable docs for their extension hierarchies.

## Reactivity

`createRegistry` uses **minimal reactivity by default** for performance. Collection methods are not reactive unless you opt in.

| Method | Notes |
| - | - |
| `register(ticket)` | Append a ticket to the registry; a supplied `index` is ignored — use `move()` to position |
| `unregister(id)` | Remove a ticket by ID |
| `upsert(id, partial)` | Register or update a ticket |
| `move(id, index)` | Move a ticket to a new index position; reindexes only the affected `[from..to]` span |
| `reorder(ids)` | Reorder the registry to match a canonical permutation in one O(n) pass |
| `onboard(tickets)` | Batch-register an array of tickets |
| `offboard(ids)` | Batch-unregister an array of IDs |
| `batch(fn)` | Run multiple mutations with deferred cache invalidation and events |
| `get(id)` | Retrieve a ticket by ID |
| `has(id)` | Check whether a ticket ID is registered |
| `browse(value)` | Reverse-lookup — find ticket ID(s) by value |
| `lookup(index)` | Find ticket ID by zero-based index |
| `seek(direction, from?, predicate?)` | Find `'first'` or `'last'` ticket, optionally starting from an index and/or filtered by predicate |
| `keys()` | All registered IDs as a readonly array |
| `values()` | All registered tickets as a readonly array |
| `entries()` | All `[id, ticket]` pairs as a readonly array |
| `clear()` | Remove all tickets |
| `dispose()` | Remove all tickets and clear event listeners |

> [!TIP] Need reactive collections?
> Pass `{ reactive: true }` to make `keys()`, `values()`, `entries()`, `size`, and per-ticket field reads reactive in templates and computeds. Upserts on existing tickets propagate through the `shallowReactive` wrapping. For event-driven snapshots — or when you want `deep: true` tracking or need reactivity without wrapping the tickets themselves — use [useProxyRegistry](/composables/reactivity/use-proxy-registry) with `{ events: true }`.

## Examples

::: gn-example
/composables/create-registry/context.ts
/composables/create-registry/TaskProvider.vue
/composables/create-registry/TaskConsumer.vue
/composables/create-registry/task-manager.vue

### Task Manager

This example demonstrates the full `createRegistry` lifecycle paired with `createContext` so registry mutations stay encapsulated in the provider and the consumer only sees clean, typed methods.

```mermaid "Event Flow"
sequenceDiagram
  participant C as TaskConsumer
  participant P as TaskProvider
  participant R as registry
  participant L as listener

  C->>P: addTask(text, priority)
  P->>R: register(ticket)
  R->>L: emit "register:ticket"
  L->>P: eventLog.push(...)
  P->>C: tasks computed invalidated
```

**File breakdown:**

| File | Role |
|------|------|
| `context.ts` | Defines `TaskTicketInput` (extending `RegistryTicketInput`) and `TaskContext`, then creates the `[useTaskRegistry, provideTaskRegistry]` tuple |
| `TaskProvider.vue` | Creates the registry with `events: true`, wires lifecycle listeners, seeds initial data via `onboard`, and exposes mutation methods through context |
| `TaskConsumer.vue` | Calls `useTaskRegistry()` to access tasks and methods; owns local UI state (filter, new-task input) and derives `filteredTasks` and `stats` as computed |
| `task-manager.vue` | Entry point—composes `TaskProvider` around `TaskConsumer` |

**Key patterns:**

- onboard — bulk-loads the initial task list in a single batch
- `registry.register()` — adds a ticket with custom fields (`value`, `priority`, `done`)
- `registry.upsert()` — patches a single field without touching the rest of the ticket
- `registry.move()` — moves a ticket to a new index position, triggers reindex
- `registry.offboard()` — batch-removes all completed tasks in one call
- `registry.on('register:ticket')` / `on('unregister:ticket')` — reacts to lifecycle events for the audit log
- `void version.value` inside a computed — the standard pattern for making a non-reactive `registry.values()` snapshot reactive

Add tasks, toggle completion, and filter by priority. Watch the event log at the bottom track every registration change in real time.

:::

## FAQ

::: faq

??? Why is the `index` I pass to `register` ignored?

`register` always appends to the end. A supplied `index` is intentionally ignored — call `move(id, index)` after registering to reposition a ticket, which reindexes only the affected span.

??? Why don't registry reads update reactively in my template?

createRegistry uses minimal reactivity by default for performance. Pass `{ reactive: true }` to make `keys()`, `values()`, `entries()`, `size`, and per-ticket field reads reactive in templates and computeds.

??? What's the difference between createRegistry and createSelection?

createRegistry is the base ordered, keyed collection — registration, indexing, and lookup. [createSelection](/composables/selection/create-selection) extends it (through createModel) with selection state. Use createRegistry when you need to track items but not which are selected.

??? When should I use `{ reactive: true }` vs `useProxyRegistry`?

Pass `{ reactive: true }` to make `keys()`, `values()`, `entries()`, `size`, and per-ticket field reads reactive directly on the registry. Reach for [useProxyRegistry](/composables/reactivity/use-proxy-registry) when you want event-driven snapshots, `deep: true` tracking, or reactivity without wrapping the tickets themselves.

??? How do I run several mutations without firing events and reindexing on each one?

Wrap them in `batch(fn)`. It defers cache invalidation and event emission until the callback finishes, so a sequence of `register` / `move` / `unregister` calls settles once instead of per-mutation.

??? Does unregistering many tickets one at a time scale?

Each `unregister` does an O(n) scan to locate and splice the ticket, so tearing down N tickets individually — a parent unmounting hundreds of registered children, for example — is O(n²). Batch removals through `offboard(ids)` or `clear()`, which compact in a single pass, and keep the mounted (and therefore registered) count bounded with [createVirtual](/composables/data/create-virtual) for large lists.

:::

<DocsApi />
