Start Building
This guide walks through the supported, opinionated starting point for a VUEDA-based project, focusing on the minimum steps needed to get a VUEDA API server and a VUEDA client application talking to each other.
If you already have an existing Django or Vue project, this guide is still useful as a reference for how VUEDA expects projects to be structured, but it does not attempt to provide conversion steps.
By the end of this guide, you will have a running Django API and Vue client connected via VUEDA, exposing a simple inventory model end-to-end.
Prerequisites
- Python 3.11+: for running the VUEDA Server
- Node.js 22+: for running the VUEDA Client
- A PostgreSQL database: for hosting your application data
- Git: for version control
- Copier: for scaffolding from the project templates
- uv: for Python dependency management
- pnpm: for Node.js dependency management
Optional
- just: for common developer CLI tooling (included in the DX template)
WARNING
VUEDA assumes an ASGI runtime. Django's built-in runserver command uses WSGI and will not exercise VUEDA's ASGI middleware stack (CORS, sessions, CSRF token handling). The DX template includes gunicorn and uvicorn and its just serve command uses them automatically. If you are using the minimal template, install an ASGI server (e.g. gunicorn + uvicorn) and use it instead of runserver.
Environment Setup
This guide assumes access to a bash-like shell (Linux, macOS, WSL2, etc.) for running commands. Adjust accordingly for other environments (PowerShell, cmd.exe, etc.).
Package Registry Access
VUEDA is available from public PyPI and public npm. No registry credentials are needed.
The v3 series is currently a prerelease. The templates select the v3 packages. When adding VUEDA to an existing project, use uv add --prerelease allow vueda for the server and pnpm add @arrai-innovations/vueda@alpha for the client. npm's latest tag still points to v1, so select alpha explicitly for v3.
Scaffold a New Project
VUEDA provides two Copier templates for scaffolding a new Integrator project:
integrator-monorepo: minimal setup with directuv/pnpmworkflows.integrator-monorepo-dx: DX-focused setup with repository automation viajust(includes linting, formatting, git hooks, andjust servefor running both servers concurrently).
Pick one and run:
# DX template (recommended)
uvx copier copy --vcs-ref=HEAD gh:arrai-innovations/vueda/templates/integrator-monorepo-dx ./your-project
# or: minimal template
uvx copier copy --vcs-ref=HEAD gh:arrai-innovations/vueda/templates/integrator-monorepo ./your-projectCopier will prompt you for a project name, slug, ports, and other options. The defaults are sensible for most setups.
The template generates:
- A Django server under
server/with VUEDA wired into settings, URLs, and config - A custom
usersapp underserver/<your_package>/users/with a project-specificUsermodel extending VUEDA's base user (required byAUTH_USER_MODEL) - Two TOML config files:
server/config.toml(shared, safe to commit) andserver/config.local.toml(secrets, do not commit) - A Vue client under
client/with VUEDA's router and action system bootstrapped - A
uvworkspace root andpnpmworkspace definition - (DX template only) A
Justfile,lefthookconfig,ruff,eslint, andprettiersetup
Initialize the Repository
The DX template includes lefthook for git hooks, which runs automatically during pnpm install. Initialize a git repository before installing dependencies:
cd your-project
git initInstall Dependencies
DX template
just bootstrapMinimal template
uv sync --all-packages
pnpm installConfigure Local Settings
VUEDA projects use a two-file TOML configuration system, both under server/:
config.toml: shared settings safe to commit (allowed hosts, frontend URL, app registry, CORS origins, etc.). The template ships placeholder public-deployment defaults; replace them with your real deployment domain before production use.config.local.toml: local-only overrides and secrets (do not commit). This is where machine-specific values like database credentials, local frontend origins, andDEBUGbelong.
Settings in config.local.toml override those in config.toml. Both files are loaded by TomlEnv in server/config/settings/base.py and consumed by VUEDA's core.default_settings.get_defaults, which sets up Django settings (INSTALLED_APPS, DATABASES, CACHES, middleware, auth, etc.) from these keys.
Before starting the server, open server/config.local.toml and set real values:
SECRET_KEY = "a-real-secret-key"
DEBUG = true
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
FRONTEND_DOMAIN = "http://localhost:5173"
CSRF_TRUSTED_ORIGINS = ["http://localhost:5173"]
CORS_ALLOWED_ORIGINS = ["http://localhost:5173"]
DATABASE_URL = "postgres://postgres:postgres@localhost:5432/your-project"The template pre-populates the local host/origin values from the bind IP and client port you chose during scaffolding, and pre-populates DATABASE_URL with a reasonable guess based on your project slug. Update those values if your local network or Postgres connection details differ. SECRET_KEY should be changed from the placeholder for any non-trivial use.
TIP
The template's config.toml also registers the scaffolded users app via LOCAL_APPS and sets AUTH_USER_MODEL = "users.User". These are required for VUEDA's user system to work. You can add your own apps to LOCAL_APPS or append to INSTALLED_APPS directly in base.py (the guide uses the latter approach below).
First Contact
At this point we have a minimal VUEDA server and client setup. Let's verify that everything is wired up correctly.
TIP
The remainder of this guide uses localhost for simplicity. If you are working in WSL2, Docker containers, or other networked environments, you may need to adjust hostnames or use 0.0.0.0 for binding.
First, apply database migrations:
# DX template
just manage migrate
# Minimal template
cd server
uv run python manage.py migrateNow start the server and client. With the DX template, run both concurrently:
just serveWith the minimal template, start each in its own terminal:
# Terminal 1: server
cd server
uv run gunicorn config.asgi -k uvicorn.workers.UvicornWorker --reload --bind localhost:8000# Terminal 2: client
cd client
pnpm devThe client dev server defaults to port 5173 (configurable via the copier options).
Now verify the server is responding:
curl -i http://localhost:8000/routes/vueda.user/who-is/You should get a 200 response with an empty JSON object, indicating that the server is up and running but you are not authenticated.
For the client, open your browser and navigate to http://localhost:5173. You should see a page load without console errors. There is nothing to display yet since we have not added any routes or components.
TIP
If you want your local environment to match production security settings (secure session and CSRF cookies, HTTPS-only), see Local HTTPS Development.
WARNING
If you are having issues from here, consult Django or Vite documentation for troubleshooting tips, or a system administrator for networking problems, as the issues are likely outside the scope of this guide.
VUEDA Server
With the boilerplate in place, we can now start building on the VUEDA Server. For this guide, we will add several simple models in the same Django app to demonstrate VUEDA's capabilities.
Create a Django App
Add a new Django app under the project namespace. In server/your_project/, create a new folder called inventory with the following files:
__init__.pyapps.pymodels.pyserializers.pyviewsets.pyfiltersets.pyrouters.pyurls.py
Models
VUEDA provides its own extensions of Django's Model class:
- VuedaModel: adds an expected VuedaModel.formatted_name
GeneratedField(by default based on a model'snamefield) and a custom BaseModelMeta class that sets up default permissions in VUEDA's expected way. - Lookup: extends VuedaModel with a unique
codefield, intended for lightweight, potentially user-defined, reference data tables.
IMPORTANT
VUEDA uses create, read, update, delete, and list permissions, which aligns better with djangorestframework's viewset actions than Django's default add, change, delete, and view permissions. All VUEDA models must therefore inherit from VuedaModel to ensure proper permission handling, and must have a class Meta(VuedaModel.Meta) (or equivalently, class Meta({@api py:class:vueda.core.models.BaseModelMeta})) by default.
server/your_project/inventory/models.py:
from django.db import models
from vueda.core.models import BaseModelMeta, Lookup, VuedaModel
class Product(VuedaModel):
name = models.CharField(max_length=255)
sku = models.CharField(max_length=64, unique=True)
description = models.TextField(blank=True)
class Meta(BaseModelMeta):
ordering = ["name", "id"]
class OptionType(Lookup):
class Meta(BaseModelMeta):
ordering = ["name", "id"]
class ProductOption(VuedaModel):
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name="options"
)
option_type = models.ForeignKey(
OptionType, on_delete=models.PROTECT, related_name="product_options"
)
name = models.CharField(max_length=255)
value = models.CharField(max_length=255)
sort_order = models.PositiveIntegerField(default=0)
class Meta(BaseModelMeta):
ordering = ["sort_order", "id"]Serializers
VUEDA provides VuedaSerializer and VuedaLookupSerializer base classes for DRF serializers. VuedaLookupSerializer handles the boilerplate around the code field for Lookup models.
IMPORTANT
As with models, all VUEDA serializers should have a class Meta({@api py:class:vueda.core.serializers.VuedaSerializer}.Meta) or class Meta({@api py:class:vueda.core.serializers.VuedaLookupSerializer}.Meta) to ensure proper default behavior.
server/your_project/inventory/serializers.py:
from vueda.core.serializers import VuedaLookupSerializer, VuedaSerializer
from your_project.inventory.models import OptionType, Product, ProductOption
class ProductSerializer(VuedaSerializer):
class Meta(VuedaSerializer.Meta):
model = Product
fields = [
"id",
"name",
"sku",
"description",
"formatted_name",
"available_actions",
]
class OptionTypeSerializer(VuedaLookupSerializer):
class Meta(VuedaLookupSerializer.Meta):
model = OptionType
fields = VuedaLookupSerializer.Meta.fields
class ProductOptionSerializer(VuedaSerializer):
class Meta(VuedaSerializer.Meta):
model = ProductOption
fields = [
"id",
"product",
"option_type",
"name",
"value",
"sort_order",
"formatted_name",
"available_actions",
]Viewsets
VUEDA provides a VuedaViewSet base class which:
- sets up default behavior for CRUDL actions
- integrates with VUEDA's permission system
- extends DRF's
ModelViewSetto cause more intentional errors when passing extra query parameters or fields (rather than silently ignoring them) - provides row-level filtering hooks
- integrates and extends drf-flex-fields, adding
permit_{action}_expandsbeyond the default which only supportspermit_list_expands
server/your_project/inventory/viewsets.py:
from vueda.core.viewsets import VuedaViewSet
from your_project.inventory.filtersets import (
OptionTypeFilterSet,
ProductFilterSet,
ProductOptionFilterSet,
)
from your_project.inventory.models import OptionType, Product, ProductOption
from your_project.inventory.serializers import (
OptionTypeSerializer,
ProductOptionSerializer,
ProductSerializer,
)
class ProductViewSet(VuedaViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filterset_class = ProductFilterSet
class OptionTypeViewSet(VuedaViewSet):
queryset = OptionType.objects.all()
serializer_class = OptionTypeSerializer
filterset_class = OptionTypeFilterSet
class ProductOptionViewSet(VuedaViewSet):
queryset = ProductOption.objects.all()
serializer_class = ProductOptionSerializer
filterset_class = ProductOptionFilterSetFiltersets
VUEDA provides VuedaFilterSet as a base for DRF filtersets.
server/your_project/inventory/filtersets.py:
from vueda.core.filters import VuedaFilterSet
from your_project.inventory.models import OptionType, Product, ProductOption
class ProductFilterSet(VuedaFilterSet):
class Meta:
model = Product
fields = ["id", "name", "sku"]
class OptionTypeFilterSet(VuedaFilterSet):
class Meta:
model = OptionType
fields = ["id", "code", "name"]
class ProductOptionFilterSet(VuedaFilterSet):
class Meta:
model = ProductOption
fields = ["id", "product", "option_type", "name"]Router and URLs
VUEDA provides VuedaRouter, which builds on DRF's SimpleRouter to generate standard CRUDL routes, namespaces route names with the app label, and supports Bulk Actions via @action(bulk=True).
server/your_project/inventory/routers.py:
from vueda.core.routers import VuedaRouter
from your_project.inventory.viewsets import (
OptionTypeViewSet,
ProductOptionViewSet,
ProductViewSet,
)
router = VuedaRouter()
router.register(r"products", ProductViewSet)
router.register(r"option-types", OptionTypeViewSet)
router.register(r"product-options", ProductOptionViewSet)
urlpatterns = router.urlsserver/your_project/inventory/urls.py:
from django.urls import include, path
from your_project.inventory.routers import urlpatterns
urlpatterns = [
path("", include(urlpatterns)),
]Finally, wire the inventory URLs into your project's namespace URL file. The copier template generates server/your_project/urls.py with an empty urlpatterns. Add the inventory app:
from django.urls import include, path
urlpatterns = [
path("inventory/", include("your_project.inventory.urls")),
]The template's server/config/urls.py already includes your project namespace under the routes/ prefix, so the inventory endpoints will be available at /routes/inventory/.
App Configuration and Model-Info Registration
VUEDA's client discovers models through a metadata API. For your models to appear in this API (and therefore be usable by the client), you need to register them with VUEDA's info.registration.register function in the app's AppConfig.ready() method.
server/your_project/inventory/apps.py:
from django.apps import AppConfig
class InventoryConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "your_project.inventory"
def ready(self):
from vueda.info import register
from .serializers import (
OptionTypeSerializer,
ProductOptionSerializer,
ProductSerializer,
)
from .viewsets import (
OptionTypeViewSet,
ProductOptionViewSet,
ProductViewSet,
)
register(ProductSerializer, ProductViewSet)
register(OptionTypeSerializer, OptionTypeViewSet)
register(ProductOptionSerializer, ProductOptionViewSet)The imports are inside ready() deliberately. Registration resolves Content Types internally, which requires the Django app registry to be fully initialized first.
IMPORTANT
Without info.registration.register, the model's API endpoints will work (you can still curl them), but the client will not be able to discover the model's fields, actions, or permissions. This is the most common cause of "model doesn't show up in the client."
Register the App
Add the new app to INSTALLED_APPS. The copier template's settings use core.default_settings.get_defaults from VUEDA, which assembles INSTALLED_APPS. That list holds Django's own apps, the default VUEDA_APPS list, third-party apps, and any apps named in LOCAL_APPS in config.toml. The scaffolded users app is already there. You need to add your new app as well.
The default VUEDA_APPS list installs every VUEDA app, and two of them are optional. Django App Boundaries separates required infrastructure from optional feature apps and gives the combinations VUEDA tests.
In server/config/settings/base.py, after the locals().update(get_defaults(env)) line, add:
INSTALLED_APPS += ["your_project.inventory"]Alternatively, you could add the app to LOCAL_APPS in config.toml. Either approach works; INSTALLED_APPS += in base.py keeps the registration close to the code, while LOCAL_APPS keeps it in config.
Then run migrations:
cd server
uv run python manage.py makemigrations inventory
uv run python manage.py migrateVerify the New API Endpoints
Since VUEDA enforces CRUDL permissions by default, the quickest path is to log in as a superuser.
Create one if you haven't already:
uv run python manage.py createsuperuserThen, in a new terminal, log in via curl and store the session cookie. The login endpoint sets a CSRF cookie in its response, which you will need for subsequent mutating requests.
TIP
The CSRF cookie name is project-specific (<project-slug>-csrf-token by default, configured via CSRF_COOKIE_NAME in local.py). The examples below use your-project-csrf-token as a placeholder; substitute your actual project slug.
COOKIE_JAR=/tmp/vueda-cookies.txt
CSRF_COOKIE=your-project-csrf-token
# Log in (uses email + password; sets CSRF cookie in the response)
curl -c $COOKIE_JAR \
-H "Content-Type: application/json" \
-X POST http://localhost:8000/routes/vueda.user/login/ \
-d '{"email":"you@example.com","password":"your-password"}'
# Extract the CSRF token for subsequent requests
CSRF_TOKEN=$(awk -v name="$CSRF_COOKIE" '$6 == name {print $7}' $COOKIE_JAR)If the login succeeded, who-is should now return your user info:
curl -b $COOKIE_JAR http://localhost:8000/routes/vueda.user/who-is/Now test CRUDL on the inventory endpoints:
# Create
curl -b $COOKIE_JAR -c $COOKIE_JAR \
-H "Content-Type: application/json" \
-H "X-CSRFToken: $CSRF_TOKEN" \
-X POST http://localhost:8000/routes/inventory/products/ \
-d '{"name":"Starter Kit","sku":"STARTER-001","description":"Demo product"}'
# Expect: 201 with the created object
# List
curl -b $COOKIE_JAR http://localhost:8000/routes/inventory/products/
# Expect: 200 with a list including the created object
# Retrieve
curl -b $COOKIE_JAR http://localhost:8000/routes/inventory/products/1/
# Expect: 200 with the created object
# Partial update
curl -b $COOKIE_JAR -c $COOKIE_JAR \
-H "Content-Type: application/json" \
-H "X-CSRFToken: $CSRF_TOKEN" \
-X PATCH http://localhost:8000/routes/inventory/products/1/ \
-d '{"description":"Updated description"}'
# Expect: 200 with the updated object
# Delete
curl -b $COOKIE_JAR -c $COOKIE_JAR \
-H "X-CSRFToken: $CSRF_TOKEN" \
-X DELETE http://localhost:8000/routes/inventory/products/1/
# Expect: 204 with no contentVUEDA Client
The scaffolded client has Vue, Pinia, vue-router, and VUEDA's action router wired up. Next, add the server connection, CRUDL data adapters, the theme, a sign-in view, and CRUDL View Resolution.
Connect to the Server
During local development the client dev server and Django run on different ports. The scaffolded client/.env.development already contains VITE_DJANGO_CONNECTION_PORT set to the port you chose during scaffolding, so VUEDA knows where to reach the Django server. No Vite proxy is needed; the template's config.local.toml already includes the local client origin in CORS_ALLOWED_ORIGINS.
Set Up Tailwind CSS
The scaffolded client/src/index.css is empty. The vueda-tailwind theme maps component slots to Tailwind utility classes, so Tailwind must be configured to generate CSS for those classes.
Replace client/src/index.css with:
@import "tailwindcss";
@import "@vueda/theme/vueda-tailwind/base.css";The @vueda/theme/vueda-tailwind/base.css import defines the semantic color tokens (foreground, background, primary, muted, sidebar, and related variants) that the theme relies on. If your project already provides these tokens (for example, from a custom design system), you can omit that import.
Register Plugins
Replace client/src/main.js with:
import TheApp from "./TheApp.vue";
import { getRouter } from "./router/index.js";
import vuedaTailwind from "@vueda/theme/vueda-tailwind/index.js";
import { setTheme } from "@vueda/use/useTheme.js";
import { setupDefaultListCrud } from "@vueda/utils/listCrud.js";
import { setupDefaultObjectCrud } from "@vueda/utils/objectCrud.js";
import { createPinia } from "pinia";
import { createApp } from "vue";
setTheme(vuedaTailwind);
setupDefaultListCrud();
setupDefaultObjectCrud();
const app = createApp(TheApp);
const pinia = createPinia();
const router = getRouter(app, pinia);
app.use(pinia);
app.use(router);
app.mount("#the-app");
export default app;setTheme(vuedaTailwind) registers the built-in Tailwind CSS theme so that all VUEDA components receive their default styling classes. The theme system is CSS-framework-agnostic; vuedaTailwind is a first-party preset that maps component slots to Tailwind utility classes. setupDefaultListCrud and setupDefaultObjectCrud register the HTTP adapters that VUEDA's composables use for every CRUDL operation. VUEDA's controls and widgets are first-party components (built on Reka UI) and need no third-party UI plugin registration. See Client Plugin Prerequisites for details on each step.
TIP
setTheme(vuedaTailwind) registers every component's default theme up front. It is the simplest path and the one this tutorial uses. If you later want to trim the bundle to just the components your app renders, VUEDA also supports per-family and fully-lazy registration; see How the theme is registered.
Add a Sign-In View
The scaffolded router's authRedirect points to a sign-in route that does not exist yet. Create client/src/views/ViewSignIn.vue:
<script setup>
import { ControlButton } from "@vueda/controls/button";
import FormField from "@vueda/form/form-model/FormField.vue";
import { storeUser } from "@vueda/stores/storeUser.js";
import AuthorizingForm from "@vueda/views/AuthorizingForm.vue";
import WidgetTextInput from "@vueda/widgets/WidgetTextInput.vue";
const userStore = storeUser();
function login({ formValues }) {
return userStore.login(formValues);
}
</script>
<template>
<AuthorizingForm header="Sign In" :run-action="login">
<template #action-form-inner>
<FormField name="email" label="Email" required>
<WidgetTextInput />
</FormField>
<FormField name="password" label="Password" required>
<WidgetTextInput type="password" />
</FormField>
</template>
<template #action-bar>
<ControlButton type="submit">Sign In</ControlButton>
</template>
</AuthorizingForm>
</template>AuthorizingForm handles form state, watches storeUser for login, and redirects to the welcome route on success. FormField and WidgetTextInput register fields in the form context so their values are collected into formValues on submit. See Build Auth Views for more on auth view patterns.
Add a Welcome View
After sign-in, AuthorizingForm redirects to the route named welcome. Create client/src/views/ViewWelcome.vue:
<script setup>
import { storeUser } from "@vueda/stores/storeUser.js";
import { computed } from "vue";
const userStore = storeUser();
const displayName = computed(() => userStore.user?.first_name || userStore.user?.email || "there");
</script>
<template>
<div style="padding: 2rem">
<h1>Welcome, {{ displayName }}</h1>
<p>
You are signed in. Try navigating to
<RouterLink to="/inventory/product/list/">Products</RouterLink>
to see the inventory list.
</p>
</div>
</template>Configure CRUDL View Resolution and Routes
The scaffolded router calls setCrudComponents with an empty object and has no routes for sign-in or welcome. Replace client/src/router/index.js with:
import { requireInitialized } from "@vueda/router/guards.js";
import { makeCRUDRoutes } from "@vueda/router/makeCrud.js";
import { setCrudComponents } from "@vueda/router/routerComponent.js";
import { createRouter, createWebHistory } from "vue-router";
export function getRouter(app, pinia) {
const crudComponents = {
list: async () => (await import("@vueda/views/ViewList.vue")).default,
create: async () => (await import("@vueda/views/ViewCreate.vue")).default,
read: async () => (await import("@vueda/views/ViewRead.vue")).default,
update: async () => (await import("@vueda/views/ViewUpdate.vue")).default,
destroy: async () => (await import("@vueda/views/ViewDestroy.vue")).default,
};
setCrudComponents(crudComponents);
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [],
});
const routes = [
{
path: "/sign-in/",
name: "sign-in",
component: () => import("@/views/ViewSignIn.vue"),
meta: { title: "Sign In" },
},
{
path: "/welcome/",
name: "welcome",
component: () => import("@/views/ViewWelcome.vue"),
meta: { title: "Welcome" },
beforeEnter: () => requireInitialized(router, pinia),
},
...makeCRUDRoutes({
component: async () => (await import("@vueda/views/ViewActionRouter.vue")).default,
authRedirect: { name: "sign-in" },
groupsRedirect: { name: "welcome" },
actionRedirect: { name: "not-found" },
groups: [],
vueApp: app,
router,
pinia,
}),
{
path: "/:pathMatch(.*)*",
name: "not-found",
component: async () => (await import("@vueda/views/ViewNotFound.vue")).default,
meta: {
title: "Not Found",
titles: {
view: "Not Found",
},
},
beforeEnter: () => requireInitialized(router, pinia),
props: (route) => {
return {
...(route.params || {}),
...(route.query || {}),
title: route.meta.title,
};
},
},
];
for (const route of routes) {
router.addRoute(route);
}
return router;
}crudComponents maps each CRUDL action to a built-in view (ViewList, ViewCreate, ViewRead, ViewUpdate, ViewDestroy). ViewActionRouter uses this map to resolve which component to render. These views auto-discover fields, filters, and permissions from Model Info, so no per-model client code is needed for a working baseline. See Routing and View Resolution for the full resolution chain.
Per-model view overrides
In a real project you may want a custom view for a specific model. The common pattern is a dynamic import with a fallback:
list: async ({ app, model }) => {
try {
return (await import(`@/views/ViewList${pascal(app)}${pascal(model)}.vue`)).default;
} catch {
return (await import("@vueda/views/ViewList.vue")).default;
}
},This lets you drop in a ViewListInventoryProduct.vue for one model while every other model keeps the default. See Creating a CRUDL Surface for details.
Verify in the Browser
Start both servers if they are not already running:
# DX template
just serve
# Minimal template (two terminals)
cd server && uv run gunicorn config.asgi -k uvicorn.workers.UvicornWorker --reload --bind localhost:8000
cd client && pnpm devOpen http://localhost:5173 in your browser.
- You should be redirected to
/sign-in/(not authenticated yet). - Sign in with the superuser credentials you created earlier.
- After login you should land on
/welcome/. - Click the "Products" link (or navigate to
http://localhost:5173/inventory/product/list/). If you created products via curl earlier, they appear here. To reach other models, the client URL pattern is/{app_label}/{model}/list/, where{model}comes from model-info and follows Django'smodel_nameconvention: the class name lowercased with no separators. For example,ProductOptionbecomesproductoption, so its list URL is/inventory/productoption/list/. This is separate from the server-side DRF router prefix (e.g.product-options), which controls the REST API path. - Use the "Create" action to add a product and verify it appears in the list.
- Click a product row to open the read view, then try update and destroy.
Customize with Model Config
The built-in views render every field the serializer exposes. To adjust which fields appear, set sort defaults, or reorder columns without building custom views, use storeModelConfig.
Create client/src/setupModelConfig.js:
import { storeModelConfig } from "@vueda/stores/storeModelConfig.js";
export function setupModelConfig() {
const modelConfig = storeModelConfig();
modelConfig.setConfig(
{ app: "inventory", model: "product" },
{
displayFields: ["name", "sku", "description"],
sorted: ["name"],
},
{
create: {
fields: ["name", "sku", "description"],
},
},
);
}Then call it from main.js after app.use(pinia):
import { setupModelConfig } from "./setupModelConfig.js";
// ... after app.use(pinia)
setupModelConfig();The fields shorthand sets displayFields, fetchFields, and submitFields together. Per-view configs (keyed by action name) merge on top of the generic config. See Configure CRUDL Views for all available options.