Features

Saved Views

Vextro provides two layers of view persistence for collection list pages: cookie-based views stored per-browser, and database-backed views that are permanent and optionally shareable across all admin users.

How persistence layers work

LayerScopeSurvives browser closeShareable
Cookie (vextro-views)Per-browserYes (1 year)No
Database (SavedView)Per-user or team-wideYes (permanent)Yes (isShared: true)

Both layers store the same logical state: visible columns, active filters, sort order, search query, and an optional layout preference (list or grid).

When a user visits a collection list page, Vextro resolves the active view by checking the cookie first, then falling back to the database default for that collection.


The vextro-views cookie holds all temporary view configurations for every collection in a single JSON blob, keyed by collection slug. It is set with a 1-year max-age and SameSite=Lax, and is capped at approximately 3,800 bytes. When the cookie would overflow, Vextro automatically prunes non-active views from collections other than the currently-visited one.

type ViewsCookie = Record<string, CollectionViewState>;

type CollectionViewState = {
  a: string | null;      // active view name
  v: Record<string, SavedViewDef>;  // named views
};

type SavedViewDef = {
  c: string[];           // visible column field names
  s?: string[];          // searchable fields (defaults to c)
  q?: string;            // serialized URL query string (filters/sort/search)
  d?: boolean;           // is default view
  l?: "list" | "grid";  // layout preference
};

Client-side helper functions

These functions are exported from vextro/lib/viewsCookie and run only in the browser:

FunctionDescription
saveView(slug, name, def)Create or update a named view for a collection
setActiveView(slug, name | null)Set the currently-active view
deleteView(slug, name)Remove a named view
renameView(slug, oldName, newName)Rename a view
toggleDefault(slug, name)Toggle the default flag (clears other defaults first)
setCollectionLayout(slug, layout)Persist "list" or "grid" layout preference

Parse-only functions work on both server and client:

FunctionDescription
parseViewsCookie(raw)Parse the raw cookie string into a ViewsCookie object
getActiveView(data, slug)Return the active SavedViewDef for a collection
getActiveViewName(data, slug)Return the active view name string
getDefaultView(data, slug)Return the default { name, view } for a collection
getCollectionViews(data, slug)Return all named views for a collection
getCollectionLayout(data, slug)Return the persisted layout preference

Database-backed saved views

Database views are stored in the Vextro component and surfaced through the admin module API. They are permanent, survive cookie clearing, and can be shared with all admin users.

SavedView type

type SavedView = {
  _id: string;
  _creationTime: number;
  name: string;
  collectionSlug: string;
  query?: {
    filters?: Array<{ field: string; operator: string; value?: unknown }>;
    sort?: Array<{ field: string; direction: "asc" | "desc" }>;
    search?: string;
    pageSize?: number;
  };
  columns?: Array<string>;
  isDefault?: boolean;
  isShared?: boolean;
  createdBy: string;   // user ID string
  updatedAt: number;   // Unix timestamp
};

Sharing views

Setting isShared: true makes a view visible to all admin users who have read access to the collection — not just the user who created it.

// Create a shared "Active items" view for all admins
await api.admin.createSavedView({
  collectionSlug: "products",
  name: "Active items",
  query: {
    filters: [{ field: "status", operator: "eq", value: "active" }],
    sort: [{ field: "createdAt", direction: "desc" }],
  },
  columns: ["title", "status", "price", "createdAt"],
  isShared: true,
});

Default views

Setting isDefault: true makes a view the landing configuration when a user first navigates to the collection list. Only one view should be default at a time — updating a view with isDefault: true is the caller's responsibility to manage.

await api.admin.createSavedView({
  collectionSlug: "posts",
  name: "Recent drafts",
  query: {
    filters: [{ field: "status", operator: "eq", value: "draft" }],
    sort: [{ field: "updatedAt", direction: "desc" }],
  },
  isDefault: true,
  isShared: true,
});

Admin module API

The following functions are available on every generated admin module. They require the caller to be authenticated as an admin user with read access (requireAdminRead).

listSavedViews

Returns all saved views for a collection visible to the current user — both the user's own views and any shared views.

const views = await api.admin.listSavedViews({
  collectionSlug: "posts",
});
// Returns: SavedView[]

createSavedView

Creates a new saved view and returns the new view's ID.

const viewId = await api.admin.createSavedView({
  collectionSlug: "posts",
  name: "Published this week",
  query: {
    filters: [{ field: "status", operator: "eq", value: "published" }],
    sort: [{ field: "publishedAt", direction: "desc" }],
    pageSize: 25,
  },
  columns: ["title", "status", "publishedAt"],
  isShared: false,
  isDefault: false,
});
// Returns: string (view ID)
ArgumentTypeDescription
collectionSlugstring* Collection the view belongs to
namestring* Display name for the view
queryobjectFilters, sort, search, and pageSize
columnsstring[]Ordered list of visible column field names
isSharedbooleanMake visible to all admin users (default false)
isDefaultbooleanUse as the landing view for the collection (default false)

updateSavedView

Partially updates an existing saved view by ID.

await api.admin.updateSavedView({
  id: viewId,
  patch: {
    name: "Published this month",
    isShared: true,
  },
});
Patch fieldTypeDescription
namestringNew display name
queryobjectReplacement query config
columnsstring[]Replacement column list
isSharedbooleanShare or unshare the view
isDefaultbooleanSet or unset as default

deleteSavedView

Permanently deletes a saved view by ID.

await api.admin.deleteSavedView({ id: viewId });

createSavedView triggers a cache invalidation for the collection so list pages reflect the new view immediately. updateSavedView and deleteSavedView do not invalidate the cache — the Convex query subscription will update reactively.


Use cookie views when:

  • Users want quick personal presets that don't need to persist after clearing cookies
  • Layout preference (list vs grid) only needs to be per-device

Use database views when:

  • A team needs a standardized "starting point" view for a collection
  • An admin wants to share a complex filter/sort configuration with colleagues
  • Views should survive across devices or browsers for the same user

Both layers can coexist. The admin UI can allow users to "promote" a cookie view to a database view by reading the cookie state and calling createSavedView with the same configuration.

Previous
List Views