---
url: "https://vueda.dev/v3/guides/link-list-rows-to-detail-views.md"
title: "Link List Rows to Read and Update Views"
---

# Link List Rows to Read and Update Views

Turn one list column into a [Row Link](/v3/reference/glossary.md#row-link), so each row's value opens that row's `update` or `read` view. Pick a column that identifies the record, such as a purchase order's `reference`.

You need a working CRUD surface ([Create a CRUD Surface](/v3/guides/create-crud-surface.md)). [Configure `list`/`read`/`create`/`update` Views](/v3/guides/configure-crud-views.md) describes how model config overrides combine.

## Set `detailLinkField`

1. Choose a column that the list already displays. The link wraps that column's value and adds no column of its own.

2. Name it in [`detailLinkField`](/v3/reference/api/js/stores/storeModelConfig/types/ModelConfig.md#detailLinkField) in the model's `list` override. Register the override when the application starts:

    ```js
    import { storeModelConfig } from "@vueda/stores/storeModelConfig.js";

    export function setupModelConfig(pinia) {
        storeModelConfig(pinia).setConfig({ app: "catalog", model: "purchaseorder" }, null, {
            list: {
                displayFields: ["reference", "supplier", "total_value"],
                detailLinkField: "reference",
            },
        });
    }
    ```

    Each [`setConfig`](/v3/reference/api/js/stores/storeModelConfig/methods/setConfig.md) call replaces the levels that it names, so pass `null` for a level that you leave alone. [Register Overrides with `setConfig`](/v3/guides/configure-crud-views.md#register-overrides-with-setconfig) describes when to call it.

    `detailLinkField` defaults to `null`, which turns row links off. Set it to `null` in a `list` override to turn off a model-wide value.

3. Keep the linked field in the list request. The list fetches the columns that it displays unless you set [`fetchFields`](/v3/reference/api/js/stores/storeModelConfig/types/ModelConfig.md#fetchFields). If you set `fetchFields`, include the linked field.

4. Check that the model's serializer includes [Available Actions](/v3/reference/glossary.md#available-actions). [VuedaSerializer](/v3/reference/api/py/vueda.core.serializers/VuedaSerializer.md) declares the `available_actions` field. When `detailLinkField` is set, the list adds `available_actions` and the primary key to its requested fields. It does this even when the [`listFields`](/v3/reference/api/vue/ViewList.md#prop-listFields) prop replaces the fetch list. Neither field becomes a visible column.

[ViewList](/v3/reference/api/vue/ViewList.md) then reads each row's `available_actions` to pick the link's destination:

| Row's `available_actions`                     | Link destination                   |
| --------------------------------------------- | ---------------------------------- |
| Includes `update`                             | The row's `update` view            |
| Includes `retrieve`, without `update`         | The row's `read` view              |
| Includes neither, or the field is not present | Displayed value without a row link |

Each row picks its own destination. A user who can retrieve a purchase order but cannot update it gets a read link on its reference. An editable row in the same list gets an update link.

The link controls navigation in the UI only. Route guards and server permissions still decide access when the user follows it.

## Check Which Columns Get the Link

`ViewList` wraps the configured column only when its [Column Adapter](/v3/reference/glossary.md#column-adapter) is one of these built-in adapters. The adapter keeps its display props.

- [ColumnText](/v3/reference/api/vue/ColumnText.md)
- [ColumnBoolean](/v3/reference/api/vue/ColumnBoolean.md)
- [ColumnDateTime](/v3/reference/api/vue/ColumnDateTime.md)
- [ColumnDuration](/v3/reference/api/vue/ColumnDuration.md)
- [ColumnJson](/v3/reference/api/vue/ColumnJson.md)

The check uses the adapter that the column resolves to, so a `columnComponents` override that names one of these adapters keeps the link. Other adapters render without a row link:

- [ColumnModelLink](/v3/reference/api/vue/ColumnModelLink.md) already links to the related record and keeps that target.
- [ColumnFile](/v3/reference/api/vue/ColumnFile.md) already links to the stored file and keeps that target.
- A custom adapter may contain links, buttons, or other controls, so VUEDA leaves its navigation alone. Use a field slot (next section) to link custom content.
- A column whose override does not name a component renders no cells, and the list shows an error for that column. [Customize List Column Rendering](/v3/guides/customize-list-column-rendering.md) describes the override chain.

A `field(<name>)` slot on the configured column replaces the automatic link. Remove the slot to use the row link.

The link is an anchor with the row's URL in both table and card layouts. The cell's content is the link text. A plain click navigates inside the application. The browser handles a modified click, such as Ctrl+click, so opening in a new tab works.

## Link a Custom Cell With `LinkModelView`

Use [LinkModelView](/v3/reference/api/vue/LinkModelView.md) in a `field(<name>)` slot when the cell needs custom content or its own destination rule. `LinkModelView` does not check permissions, so the slot reads the row's `available_actions` itself.

This list view applies the same update-then-read rule to custom content in the `reference` column:

```vue
<script setup>
import LinkModelView from "@vueda/navigation/link-model-view/LinkModelView.vue";
import ViewList from "@vueda/views/ViewList.vue";

defineProps({
    app: { type: String, required: true },
    model: { type: String, required: true },
});
defineOptions({ inheritAttrs: false });

const detailView = (obj) => {
    const actions = obj.available_actions || [];
    return actions.includes("update") ? "update" : actions.includes("retrieve") ? "read" : null;
};
</script>

<template>
    <view-list v-bind="{ ...$props, ...$attrs }">
        <template #[`field(reference)`]="{ pk, obj, formatted }">
            <link-model-view v-if="detailView(obj)" :app="app" :model="model" :pk="pk" :view="detailView(obj)">
                {{ formatted }}
            </link-model-view>
            <span v-else>{{ formatted }}</span>
        </template>
    </view-list>
</template>
```

To render this component as the model's list view, return it from a `list` loader passed to [setCrudComponents](/v3/reference/api/js/router/routerComponent/functions/setCrudComponents.md). [View Component Resolution Order](/v3/core-concepts/routing-and-view-resolution-model.md#view-component-resolution-order) describes the loader.

Without `detailLinkField`, the list does not request `available_actions`. Add it to `fetchFields`, or to the `listFields` prop when you pass one, and leave it out of `displayFields`. Do not put another link or control inside the `LinkModelView`.

## Verify the Result

- Open the list as a user with a mix of editable, read-only, and unavailable rows. Confirm update links, read links, and plain values on the matching rows.
- In the browser's network panel, confirm that the list request's fields include the primary key, the displayed columns, and `available_actions`. Confirm that no extra column appears.
- Check both table and card layouts, keyboard activation, and a modified click on the linked value.
