Skip to main content

Working with Server

By default the Kanban widget works entirely in the browser - you pass a cards array and every change stays in memory. When you need persistence, wire the board to a backend through the built-in RestDataProvider or handle actions manually. For large datasets, dynamic loading lets columns fetch their cards only when they become visible.

RestDataProvider

RestDataProvider maps the five card-level store actions to conventional REST endpoints under a single URL root:

ActionHTTP call
add-cardPOST /cards
update-cardPUT /cards/:id (debounced 500 ms)
move-cardPUT /cards/:id/move
delete-cardDELETE /cards/:id
duplicate-cardPOST /cards/:id/duplicate

It also handles date conversion: deadline strings from the server are parsed into Date objects on load, and Date values are serialized as ISO strings on the way out.

Two steps connect the provider to the board: call getData() for the initial fetch, then attach the provider inside init so subsequent user actions flow to the server.

Loading initial cards

Call getData() to fetch cards from the server and assign them to the cards prop. It issues GET /cards and parses dates before returning the result.

<script setup>
import { Kanban, RestDataProvider } from "@svar-ui/vue-kanban";
import { ref } from "vue";

const url = "https://api.example.com";
const provider = new RestDataProvider(url);

const cards = ref([]);

provider.getData().then(data => {
cards.value = data;
});
</script>

<template>
<Kanban :cards="cards" :columns="columns" />
</template>

cards is reactive, so assigning the fetched array triggers a render once data arrives. Until then the board shows empty columns.

Saving to backend

Wire the provider into the action pipeline with api.setNext(provider) inside the init callback. After that, every add-card, update-card, move-card, delete-card, and duplicate-card action from the UI forwards to its REST endpoint automatically.

<script setup>
import { Kanban, Editor, RestDataProvider } from "@svar-ui/vue-kanban";
import { ref } from "vue";

const url = "https://api.example.com";
const provider = new RestDataProvider(url);

const api = ref(null);
const cards = ref([]);

provider.getData().then(data => {
cards.value = data;
});

function init(kanbanApi) {
kanbanApi.setNext(provider);
}
</script>

<template>
<Kanban ref="api" :init="init" :cards="cards" :columns="columns" />
<Editor v-if="api" :api="api" />
</template>

The provider runs after the store's default handler and any on listeners. You can combine it with intercept to validate or enrich payloads before they hit the network:

function init(api) {
api.setNext(provider);
api.intercept("add-card", ev => {
ev.card.priority = 1;
});
}

Customizing date serialization

If your backend expects dates in a format other than ISO 8601, subclass RestDataProvider and override formatDate:

class CustomProvider extends RestDataProvider {
formatDate(date) {
return date.toISOString().split("T")[0]; // "2025-06-15"
}
}

Dynamic loading

PRO

The functionality is available in PRO Edition only

Loading every card up front isn't always practical. Dynamic loading defers this: the board starts with an empty cards array and requests data for each column only when it becomes visible and expanded.

Set dynamicData to enable the mode. When a column first appears on screen, the store emits request-data with the column id. Your code listens for that action, fetches the cards, and responds with provide-data.

<script setup>
import { Kanban } from "@svar-ui/vue-kanban";
import { ref } from "vue";

const api = ref(null);

function onrequestdata(event) {
fetch(`/api/cards?column=${event.id}`)
.then(r => r.json())
.then(cards => {
api.value.exec("provide-data", {
id: event.id,
cards,
});
});
}

function init(kanbanApi) {
api.value = kanbanApi;
kanbanApi.on("request-data", onrequestdata);
}
</script>

<template>
<Kanban
:init="init"
:cards="[]"
:columns="columns"
dynamicData
/>
</template>

Each column transitions through three internal states: unknown (no data requested yet), loading (request in flight), and loaded (cards received). Once a column reaches loaded it stays there - the store doesn't re-fetch on subsequent scrolls. Filters and sorting apply to dynamically loaded cards the same way they apply to static ones.

Pair dynamic loading with column virtualization when the board has many columns, so only the visible subset triggers data requests:

<template>
<Kanban
:init="init"
:cards="[]"
:columns="columns"
dynamicData
:render="{
columnScroll: false,
virtualizeColumns: true,
columnOverscan: 0,
estimatedCardHeight: 96,
}"
/>
</template>