Quickstart
Get the reference app running, then add your own entity + view. About 10 minutes.
Prerequisites
Section titled “Prerequisites”- .NET SDK matching
global.json(net10.0, SDK 10.0.300+). - Node 22+ and npm (for the React UI).
- SQL Server reachable at
Server=.(the local default instance, integrated security).
No Redis required — the Inventory sample runs its cache in-memory. Adjust the connection string
in sample/Inventory/api/appsettings.json for your environment:
"ConnectionStrings": { "SqlServer": "Server=.;Database=InventorySample;Integrated Security=true;TrustServerCertificate=True;MultipleActiveResultSets=True"}1. Build
Section titled “1. Build”dotnet build src/Genie.slnx # .NET libraries + source generatorcd src/ui/genie-engine-ui && npm ci && npm run build # the React package2. Run the reference app (Inventory)
Section titled “2. Run the reference app (Inventory)”# backend → http://localhost:5184# (creates the InventorySample DB, applies the migration, and seeds sample data on first run)dotnet run --project sample/Inventory/api
# frontend → http://localhost:5183 (proxies /api, /files, /hubs to the backend)cd sample/Inventory/ui && npm install && npm run devOpen http://localhost:5183: you get the login page, then the permission-filtered navbar and the engine’s table/form views.
3. See the model files
Section titled “3. See the model files”The sample’s low-code models live in sample/Inventory/models/:
models/ entities/ *.entity.xml Category, Product, Supplier, Warehouse, StockMovement, PurchaseOrder, PurchaseOrderLine (→ EF tables) views/ *.view.xml grids + forms for each object sql/ *.sql functions, stored procedures, triggers, reporting views navbar/ *.navbar.xml the navigation tree wizards/ *.wizard.xml multi-step wizards workflows/ *.workflow.xml workflow definitionsThey are wired in sample/Inventory/api/Inventory.Sample.Api.csproj: *.entity.xml are fed to the
source generator as AdditionalFiles, and every model file is copied to the build output so the
runtime model migration can find it.
4. Add your own entity
Section titled “4. Add your own entity”Create sample/Inventory/models/entities/Brand.entity.xml:
<?xml version="1.0" encoding="utf-8"?><Schema> <Entity Name="Brand" PluralName="Brands" SchemaName="Inventory" Description="Manufacturer brand a product belongs to"> <Fields> <String Name="Name" Label="Brand" Required="true" Searchable="true" Limit="200" /> <String Name="Website" Label="Website" Required="false" Limit="256" /> <Boolean Name="IsActive" Label="Active" Required="true" DefaultValueIfNull="1" /> </Fields> </Entity></Schema>The generator picks it up on the next build (it matches the existing AdditionalFiles glob) and
emits a Brand entity, its EF configuration, and a DbSet<Brand> Brands on InventoryContext.
5. Materialise the table (EF migration)
Section titled “5. Materialise the table (EF migration)”The generated entity classes are part of InventoryContext, so a normal migration creates the
table. Stop the running host first (EF rebuilds the project, which can’t overwrite a locked,
running binary):
dotnet ef migrations add AddBrand \ --project sample/Inventory/api \ --startup-project sample/Inventory/api \ --context InventoryContext \ --output-dir Migrations/InventoryMigrationsRun the host again; the migration is applied at boot (Database.Migrate()).
6. Add a view
Section titled “6. Add a view”Create sample/Inventory/models/views/Brands.view.xml — the root element is <Table>, and whether
it behaves as a grid, a form, or a read-only view is inferred from which SQL blocks it declares:
<?xml version="1.0" encoding="utf-8"?><Table Name="Brands" Slug="brands" Label="Brands" Icon="fa fa-tag">
<Sql PrimaryKey="Id" SortBy="Name" SortDirection="ASC" PageSize="20"> <![CDATA[ SELECT b.Id, b.Name, b.Website, b.IsActive FROM [Inventory].[Brands] b WHERE b.IsDeleted = 0 AND b.CompanyId = @SessionCompanyId ]]> </Sql>
<Columns> <Column Name="Id" Hide="true" /> <Column Name="Name" Label="Brand" Search="true" StyleClasses="fw-bold" /> <Column Name="Website" /> <Column Name="IsActive" Label="Active" /> </Columns>
<EditorFields> <Text Name="Name" Label="Brand" Required="true" MaxLength="200" /> <Text Name="Website" Label="Website" MaxLength="256" Placeholder="https://…" /> <Boolean Name="IsActive" Label="Active" DisplayType="Switch" /> </EditorFields>
<Layout> <Section Label="Brand"> <Item Name="Name" Width="8" /> <Item Name="IsActive" Width="4" /> <Item Name="Website" Width="12" /> </Section> </Layout>
<InsertSql> <![CDATA[ INSERT INTO [Inventory].[Brands] (Name, Website, IsActive, IsDeleted, CompanyId, CreatedById, CreatedAt) VALUES (@Name, @Website, CASE WHEN UPPER(ISNULL(@IsActive,'')) IN ('TRUE','1','YES','Y','ON') THEN 1 ELSE 0 END, 0, @SessionCompanyId, @SessionUserId, GETDATE()); ]]> </InsertSql>
<EditSql> <![CDATA[ UPDATE [Inventory].[Brands] SET Name = @Name, Website = @Website, IsActive = CASE WHEN UPPER(ISNULL(@IsActive,'')) IN ('TRUE','1','YES','Y','ON') THEN 1 ELSE 0 END, UpdatedAt = GETDATE(), UpdatedById = @SessionUserId WHERE Id = @Id AND IsDeleted = 0 AND CompanyId = @SessionCompanyId; ]]> </EditSql>
<DeleteSql> <![CDATA[ UPDATE [Inventory].[Brands] SET IsDeleted = 1, DeletedAt = GETDATE(), DeletedById = @SessionUserId WHERE Id = @Id AND CompanyId = @SessionCompanyId; ]]> </DeleteSql>
</Table>Views are loaded into the engine’s ViewStore by the model migration at startup. The Inventory
sample already enables it in sample/Inventory/api/appsettings.json:
"Startup": { "ExecuteMigration": "Forced" }No skips migration, Yes applies only changed files (hash check), and Forced re-applies every
file regardless of hash. How the engine locates the models directory (and how the sample copies
models to the build output) is covered in Integration.
7. Use it
Section titled “7. Use it”Point the browser (or the navbar) at /table/brands — the UI route uses the view’s kebab-case
Slug. To add it to the navigation, drop an <Item> into
sample/Inventory/models/navbar/inventory.navbar.xml:
<Item Name="brands" Label="Brands" Icon="fa fa-tag" Route="/table/brands" />To make records findable from the global header search (enable it with
createGenieApp({ modules: { headerSearch: true } })), mark a field Searchable="true" and add a
<Search> block to the entity — see Model Authoring.
Recap of the loop
Section titled “Recap of the loop”| Want to… | Edit | Then |
|---|---|---|
| Add/lengthen a column | *.entity.xml |
rebuild → dotnet ef migrations add → run |
| Change a grid/form | *.view.xml |
run with ExecuteMigration = Yes (or Forced) |
| Add a stored proc / SQL view | *.sql |
run with ExecuteMigration = Yes |
| Change navigation | *.navbar.xml |
run with ExecuteMigration = Yes |
Next: the complete authoring reference → Model Authoring.