Skip to content

BeamLabEU/phoenix_kit_entities

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PhoenixKitEntities

Dynamic content types for PhoenixKit. Define custom entities (like "Product", "Team Member", "FAQ") with flexible field schemas — no database migrations needed per entity.

Table of Contents

What this provides

  • 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

Quick start

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"
  }
})

Dependency types

Local development (path:)

{:phoenix_kit_entities, path: "../phoenix_kit_entities"}

Changes to the module's source are picked up automatically on recompile.

Git dependency (git:)

{: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.

Hex package

{:phoenix_kit_entities, "~> 0.1.0"}

Project structure

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

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)

Name constraints

  • Must be unique, snake_case, 2-50 characters
  • Format: ^[a-z][a-z0-9_]*$
  • Examples: product, team_member, faq_item

Status workflow

Entities support three statuses: draft, published, archived.

Entity data records

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)

Manual ordering

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")

Field types

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 UI

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

Multi-language support

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.

Public forms

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

Filesystem mirroring

Export and import entity definitions and data as JSON files:

mix phoenix_kit_entities.export
mix phoenix_kit_entities.import

Or programmatically:

PhoenixKitEntities.Mirror.Exporter.export_all(path)
PhoenixKitEntities.Mirror.Importer.import_all(path)

Events & PubSub

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)

Available callbacks

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

Mix tasks

# Export all entities and data to JSON
mix phoenix_kit_entities.export

# Import entities and data from JSON
mix phoenix_kit_entities.import

Database

Database 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

Testing

# Create test database
createdb phoenix_kit_entities_test

# Run all tests
mix test

# Run only unit tests (no DB needed)
mix test --exclude integration

Troubleshooting

Module not appearing in admin

  1. Verify the dependency is in mix.exs and mix deps.get was run
  2. Check PhoenixKitEntities.enabled?() returns true
  3. Run PhoenixKitEntities.enable_system() if needed

"entities_enabled" setting not found

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.

Entity name validation fails

Names must be snake_case, start with a letter, 2-50 characters. Examples: product, team_member, faq_item. Invalid: Product, 123abc, a.

Changes not taking effect after editing

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.

About

PhoenixKit Entities Module

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages