Docs
Recipes

Bulk administration

Keep selection across pages and submit only chosen fields.

What you will build

Combine cross-page selection, validated bulk patches, row permissions and an application-owned Archive action.

Start with the React setup or Vue setup. Use the complete files below with the shared recipe modules. The browser-memory store resets on reload; an API integration must preserve the same contracts.

Required files: shared/products.ts, shared/store.ts, shared/admin-mutations.ts, product-config.ts, list-records.ts, view-actions.ts, editable-actions.ts, admin-actions.ts, product-form.ts, get-form-config.ts, admin-config.ts, recipe-admin.

Copy the shared mutation store and adapter and the product form catalog. The recipe deliberately uses two rows per page to make cross-page selection visible.

admin-config.ts

admin-config.ts
import { defineTableConfig } from "@/components/ui/yayaw-table/config/helpers";
import { productConfig } from "./product-config";

export const adminConfig = defineTableConfig({
  ...productConfig,
  table: {
    ...productConfig.table,
    layoutPreset: "admin",
    defaultPageSize: 2,
    pageSizeOptions: [2, 10, 20],
    enableRowSelection: true,
    enableMultiRowSelection: true,
    preserveSelectionOnQuery: true,
    allowEdit: true,
    allowBulkEdit: true,
    canEditRow: (row) => row.status !== "archived",
    canSelectRow: (row) => row.status !== "archived",
    bulkExport: true,
  },
  form: { editFormType: "products" },
});
admin-config.ts
import { defineTableConfig } from "@/components/ui/yayaw-table-vue/config";
import { productConfig } from "./product-config";

export const adminConfig = defineTableConfig({
  ...productConfig,
  table: {
    ...productConfig.table,
    layoutPreset: "admin",
    defaultPageSize: 2,
    pageSizeOptions: [2, 10, 20],
    enableRowSelection: true,
    enableMultiRowSelection: true,
    preserveSelectionOnQuery: true,
    allowEdit: true,
    allowBulkEdit: true,
    canEditRow: (row) => row.status !== "archived",
    canSelectRow: (row) => row.status !== "archived",
    bulkExport: true,
  },
  form: { editFormType: "products" },
});

Entry component

Expand ↗

Sample data. Changes stay in this preview.

Expand ↗

Sample data. Changes stay in this preview.

Verify the result

  1. Select one record on page one.

  2. Go to page two and select another: the selection contains two IDs.

  3. Open bulk edit and choose Status only.

  4. Set Archived and submit: both selected records change; stock and price stay unchanged.

  5. Exercise bulkUpdate with one missing ID: failedIds reports that ID, while successful changes remain saved.

  6. Verify Name is excluded from the bulk editor by bulkEdit: false.

Use the existing playground to explore the library’s controls.

Enforce the record policy in mutations

The same rule is checked again at write time. This demo is not authentication: replace it with your server’s actor and tenant checks. Unknown or archived targets are reported as failed IDs, so successful writes are not retried by the built-in editor.

admin-mutations.ts
import { type createProductStore, productStore } from "./store";

/** Demo record policy. A real server must also authenticate and scope every request. */
export function createAdminMutations(
  store: ReturnType<typeof createProductStore>
) {
  const update = (id: string, patch: Record<string, unknown>) => {
    if (store.rows.find((row) => row.id === id)?.status === "archived") {
      return { success: false, error: "Archived products are read-only." };
    }
    return store.update(id, patch);
  };
  const bulkUpdate = (ids: string[], patch: Record<string, unknown>) => {
    const failedIds: string[] = [];
    for (const id of new Set(ids)) {
      if (!update(id, patch).success) {
        failedIds.push(id);
      }
    }
    return {
      success: failedIds.length === 0,
      failedIds,
      ...(failedIds.length
        ? {
            error:
              "Some products cannot be edited. Retry the remaining records.",
          }
        : {}),
    };
  };
  return { update, bulkUpdate };
}

export const adminMutations = createAdminMutations(productStore);
admin-actions.ts
import type { TableActions } from "@/components/ui/yayaw-table/providers/table-provider";
import { adminMutations } from "../shared/admin-mutations";
import { editableActions } from "./editable-actions";

const adminActions: TableActions = {
  ...editableActions,
  update: (id, patch) => Promise.resolve(adminMutations.update(id, patch)),
  bulkUpdate: (ids, patch) =>
    Promise.resolve(adminMutations.bulkUpdate(ids, patch)),
};
export const getAdminActions = (tableType: string) =>
  tableType === "products" ? adminActions : undefined;
admin-actions.ts
import type { TableActions } from "@/components/ui/yayaw-table-vue/types";
import { adminMutations } from "../shared/admin-mutations";
import { editableActions } from "./editable-actions";

const adminActions: TableActions = {
  ...editableActions,
  update: (id, patch) => Promise.resolve(adminMutations.update(id, patch)),
  bulkUpdate: (ids, patch) =>
    Promise.resolve(adminMutations.bulkUpdate(ids, patch)),
};
export const getAdminActions = (tableType: string) =>
  tableType === "products" ? adminActions : undefined;

Walk through the result

ActionExpected result
Select two products and open Edit.The property list starts empty and Apply is disabled.
Add Stock, type 24, then apply.Only Stock changes on those two targets; names, prices and the third product remain unchanged.
Add and remove Price before applying another property.Price is absent from the submitted patch.
Select a product, choose Archive and confirm.Its status becomes Archived; the row can no longer be selected or edited.
Export the selected rows before archiving.The CSV contains the selected records, rather than only the current page.

On mobile, use View settings for presentation, the Data menu for export, and the search button in the toolbar. Resetting a view does not undo writes. Try the keyboard as well as pointer input.