Dynamic content types for PhoenixKit. Define custom entities (like "Product", "Team Member", "FAQ") with flexible field schemas — no database migrations needed per entity.
- What this provides
- Quick start
- Dependency types
- Project structure
- Entity definitions
- Entity data records
- Field types
- Admin UI
- Multi-language support
- Public forms
- Filesystem mirroring
- Events & PubSub
- Available callbacks
- Mix tasks
- Testing
- Troubleshooting
- Dynamic entity definitions with JSONB field schemas (no migrations per entity)
- 12 field types: text, textarea, email, url, rich_text, number, boolean, date, select, radio, checkbox, file
- Complete admin UI (LiveView) for managing entity definitions and data records
- Multi-language support (auto-enabled when 2+ languages are active)
- Collaborative editing with FIFO locking and presence tracking
- Public form builder with honeypot, time-based validation, and rate limiting
- Filesystem mirroring for export/import of entity definitions and data
- PubSub events for real-time updates across admin sessions
- Sitemap integration for published entity data
- Zero-config auto-discovery — just add the dependency
Add to your parent app's mix.exs:
{:phoenix_kit_entities, path: "../phoenix_kit_entities"}Run mix deps.get and start the server. The module appears in:
- Admin sidebar (under Modules section) — browse entities and their data
- Admin > Modules — toggle it on/off
- Admin > Roles — grant/revoke access per role
- Admin > Settings > Entities — configure module settings
Enable the system:
PhoenixKitEntities.enable_system()Create your first entity:
{:ok, entity} = PhoenixKitEntities.create_entity(%{
name: "product",
display_name: "Product",
display_name_plural: "Products",
icon: "hero-cube",
created_by_uuid: admin_user.uuid,
fields_definition: [
%{"type" => "text", "key" => "name", "label" => "Name", "required" => true},
%{"type" => "number", "key" => "price", "label" => "Price"},
%{"type" => "textarea", "key" => "description", "label" => "Description"},
%{"type" => "select", "key" => "category", "label" => "Category",
"options" => ["Electronics", "Clothing", "Food"]}
]
})Create data records:
{:ok, record} = PhoenixKitEntities.EntityData.create(%{
entity_uuid: entity.uuid,
title: "iPhone 15",
status: "published",
created_by_uuid: admin_user.uuid,
data: %{
"name" => "iPhone 15",
"price" => 999,
"description" => "Latest iPhone model",
"category" => "Electronics"
}
}){:phoenix_kit_entities, path: "../phoenix_kit_entities"}Changes to the module's source are picked up automatically on recompile.
{:phoenix_kit_entities, git: "https://github.com/BeamLabEU/phoenix_kit_entities.git"}After updating the remote: mix deps.update phoenix_kit_entities, then mix deps.compile phoenix_kit_entities --force + restart the server.
{:phoenix_kit_entities, "~> 0.1.0"}lib/
phoenix_kit_entities.ex # Main module (schema + PhoenixKit.Module behaviour)
phoenix_kit_entities/
entity_data.ex # Data record schema and CRUD
field_type.ex # Field type struct
field_types.ex # Field type registry (12 types)
form_builder.ex # Dynamic form generation + validation
events.ex # PubSub broadcast/subscribe
presence.ex # Phoenix.Presence for editing
presence_helpers.ex # FIFO locking, session tracking
routes.ex # Admin + public route definitions
sitemap_source.ex # Sitemap integration
components/
entity_form.ex # Embeddable public form component
controllers/
entity_form_controller.ex # Public form submission handler
migrations/
v1.ex # Migration module (called by parent app)
mirror/
exporter.ex # Entity/data export to JSON
importer.ex # Entity/data import from JSON
storage.ex # File storage for mirror
mix_tasks/
export.ex # mix phoenix_kit_entities.export
import.ex # mix phoenix_kit_entities.import
web/
entities.ex # Entity list LiveView (inline template)
entity_form.ex # Entity definition builder LiveView
data_navigator.ex # Data record browser LiveView
data_form.ex # Data record form LiveView
data_view.ex # Data record read-only view
entities_settings.ex # Module settings LiveView
hooks.ex # Shared LiveView hooks
Entity definitions are blueprints for custom content types. Each entity has a name, display names, and a JSONB array of field definitions.
# List all entities
PhoenixKitEntities.list_entities()
# Get by name
PhoenixKitEntities.get_entity_by_name("product")
# Create
{:ok, entity} = PhoenixKitEntities.create_entity(%{...})
# Update
{:ok, entity} = PhoenixKitEntities.update_entity(entity, %{status: "published"})
# Delete (cascades to all data records)
{:ok, entity} = PhoenixKitEntities.delete_entity(entity)- Must be unique, snake_case, 2-50 characters
- Format:
^[a-z][a-z0-9_]*$ - Examples:
product,team_member,faq_item
Entities support three statuses: draft, published, archived.
Data records are instances of an entity definition. Field values are stored in a JSONB data column.
alias PhoenixKitEntities.EntityData
# List records for an entity
EntityData.list_by_entity(entity.uuid)
# Filter by status
EntityData.list_by_entity_and_status(entity.uuid, "published")
# Search by title
EntityData.search_by_title("iPhone", entity.uuid)
# Get by slug
EntityData.get_by_slug(entity.uuid, "iphone-15")
# CRUD
{:ok, record} = EntityData.create(%{...})
{:ok, record} = EntityData.update(record, %{...})
{:ok, record} = EntityData.delete(record)Entities can use auto sort (by creation date) or manual sort (by position). Configure via the entity's settings:
PhoenixKitEntities.update_sort_mode(entity, "manual")| Category | Types | Notes |
|---|---|---|
| Basic | text, textarea, email, url, rich_text |
Rich text is HTML-sanitized |
| Numeric | number |
Accepts integers and floats |
| Boolean | boolean |
Toggle/checkbox |
| Date | date |
Date picker |
| Choice | select, radio, checkbox |
Require options array |
| Media | file, image |
Coming soon |
| Relations | relation |
Coming soon |
Each field definition is a map with:
%{
"type" => "text", # Required
"key" => "title", # Required, unique per entity
"label" => "Title", # Required
"required" => true, # Optional, default false
"default" => "", # Optional
"options" => ["A", "B"], # Required for select/radio/checkbox
"validation" => %{...} # Optional validation rules
}Use the helper functions:
alias PhoenixKitEntities.FieldTypes
FieldTypes.text_field("name", "Full Name", required: true)
FieldTypes.select_field("category", "Category", ["Tech", "Business"])
FieldTypes.boolean_field("featured", "Featured", default: true)Admin routes are registered via PhoenixKitEntities.Routes (returned by route_module/0):
| Route | LiveView | Purpose |
|---|---|---|
/admin/entities |
Web.Entities |
List all entity definitions |
/admin/entities/new |
Web.EntityForm |
Create entity definition |
/admin/entities/:id/edit |
Web.EntityForm |
Edit entity definition |
/admin/entities/:name/data |
Web.DataNavigator |
Browse entity records |
/admin/entities/:name/data/new |
Web.DataForm |
Create record |
/admin/entities/:name/data/:uuid |
Web.DataForm |
Edit record |
/admin/settings/entities |
Web.EntitiesSettings |
Module settings |
Multilang is auto-enabled when PhoenixKit has 2+ languages configured. Data is stored in a nested JSONB structure:
# Multilang data format
%{
"en" => %{"title" => "Hello", "description" => "..."},
"es" => %{"title" => "Hola", "description" => "..."}
}See PhoenixKit.Utils.Multilang for helper functions.
Entities can expose public submission forms. Enable in entity settings, then embed:
<EntityForm entity_slug="contact" />Or use the controller endpoint. Public forms include:
- Honeypot field for bot detection
- Time-based validation (minimum 3 seconds)
- Rate limiting (5 submissions per 60 seconds)
- Browser/OS/device metadata capture
Export and import entity definitions and data as JSON files:
mix phoenix_kit_entities.export
mix phoenix_kit_entities.importOr programmatically:
PhoenixKitEntities.Mirror.Exporter.export_all(path)
PhoenixKitEntities.Mirror.Importer.import_all(path)Subscribe to real-time events:
alias PhoenixKitEntities.Events
# Entity lifecycle
Events.subscribe_to_entities()
# Receives: {:entity_created, uuid}, {:entity_updated, uuid}, {:entity_deleted, uuid}
# Data lifecycle (all entities)
Events.subscribe_to_all_data()
# Receives: {:data_created, entity_uuid, data_uuid}, etc.
# Data lifecycle (specific entity)
Events.subscribe_to_entity_data(entity_uuid)This module implements PhoenixKit.Module with these callbacks:
| Callback | Value |
|---|---|
module_key/0 |
"entities" |
module_name/0 |
"Entities" |
enabled?/0 |
Reads entities_enabled setting |
enable_system/0 |
Sets entities_enabled to true |
disable_system/0 |
Sets entities_enabled to false |
permission_metadata/0 |
Icon: hero-cube-transparent |
admin_tabs/0 |
Entities tab with dynamic entity children |
settings_tabs/0 |
Settings tab under admin settings |
children/0 |
[PhoenixKitEntities.Presence] |
css_sources/0 |
[:phoenix_kit_entities] |
route_module/0 |
PhoenixKitEntities.Routes |
get_config/0 |
Returns enabled status, limits, stats |
# Export all entities and data to JSON
mix phoenix_kit_entities.export
# Import entities and data from JSON
mix phoenix_kit_entities.importDatabase tables and migrations are managed by the parent PhoenixKit project. This repo provides PhoenixKitEntities.Migrations.V1 as a library module that the parent app's migrations call — there are no migrations to run in this repo directly.
# Two tables:
# phoenix_kit_entities — entity definitions (blueprints)
# phoenix_kit_entity_data — data records (instances)
# Both use UUIDv7 primary keys# Create test database
createdb phoenix_kit_entities_test
# Run all tests
mix test
# Run only unit tests (no DB needed)
mix test --exclude integration- Verify the dependency is in
mix.exsandmix deps.getwas run - Check
PhoenixKitEntities.enabled?()returnstrue - Run
PhoenixKitEntities.enable_system()if needed
The settings are seeded by the migration. If using PhoenixKit core migrations, they're created by V17. If standalone, run the PhoenixKitEntities.Migrations.V1 migration.
Names must be snake_case, start with a letter, 2-50 characters. Examples: product, team_member, faq_item. Invalid: Product, 123abc, a.
Force a clean rebuild: mix deps.clean phoenix_kit_entities && mix deps.get && mix deps.compile phoenix_kit_entities --force && mix compile --force
Note: This repo has no database migrations. All tables and migrations are managed by the parent PhoenixKit project. The test helper creates necessary DB functions directly when a test database is available.