{"id":50901377,"url":"https://github.com/go9/lantern","last_synced_at":"2026-06-16T03:04:03.722Z","repository":{"id":360988296,"uuid":"1250858065","full_name":"go9/lantern","owner":"go9","description":"Embeddable Postgres table viewer \u0026 editor for Phoenix LiveView","archived":false,"fork":false,"pushed_at":"2026-06-11T19:47:40.000Z","size":178,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T21:08:24.785Z","etag":null,"topics":["admin","database","elixir","hex","phoenix","phoenix-liveview","postgres","postgresql"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/go9.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-27T03:07:06.000Z","updated_at":"2026-06-11T19:47:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/go9/lantern","commit_stats":null,"previous_names":["go9/lantern"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/go9/lantern","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go9%2Flantern","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go9%2Flantern/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go9%2Flantern/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go9%2Flantern/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/go9","download_url":"https://codeload.github.com/go9/lantern/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go9%2Flantern/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34388670,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["admin","database","elixir","hex","phoenix","phoenix-liveview","postgres","postgresql"],"created_at":"2026-06-16T03:03:59.902Z","updated_at":"2026-06-16T03:04:03.716Z","avatar_url":"https://github.com/go9.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lantern\n\nAn embeddable Postgres **table viewer and editor** for Phoenix LiveView. Hand\nit a database connection and drop the component into any LiveView — you get a\nschema selector, sidebar of tables, a sortable/filterable grid, inline editing,\nrow insertion, bulk delete, type-aware inputs, foreign-key lookups, and fullscreen mode.\n\n**[Try the live demo →](https://lantern-demo.flickercloud.com)**\n\n- **Drop-in** — one `live_component`, a stylesheet, and a JS hook. No Fluxon, no\n  icon library, no design-system assumptions.\n- **Connection-agnostic** — point it at any Postgres via a `postgres://` URL, a\n  keyword list, a map, or a struct. It opens short-lived connections; there's\n  no pool for you to supervise.\n- **Safe writes** — every value is sent as a cast text parameter\n  (`$1::text::int4`), never interpolated into SQL. Edits and deletes are scoped\n  to the row's primary key; a table without one is insert-only (you can add\n  rows, but not edit or delete existing ones).\n- **Schema editing** — create and drop tables, add/rename/drop columns, and\n  rename tables from the UI or the data API. Identifiers are always quoted and\n  column types pass an allowlist, so DDL can't be used for injection.\n- **Themeable** — all styling is semantic `lt-*` classes in a low-priority\n  cascade layer, driven by `--lt-*` CSS variables. Override a handful of\n  variables, or any class, with zero specificity fights.\n\n\u003e ⚠️ **Lantern exposes whatever database you connect it to, including every\n\u003e column.** It is meant for operators/admins. Always put it behind your own\n\u003e authentication and authorization — see [Security](#security). The raw SQL\n\u003e filter input is **disabled by default**; pass `allow_raw_filter: true` to\n\u003e enable it only in trusted operator contexts.\n\n## Installation\n\nAdd Lantern to your deps. From Hex:\n\n```elixir\ndef deps do\n  [{:lantern, \"~\u003e 0.3.0\"}]\nend\n```\n\nOr straight from GitHub:\n\n```elixir\ndef deps do\n  [{:lantern, github: \"go9/lantern\"}]\nend\n```\n\nLantern needs `phoenix_live_view ~\u003e 1.1`, `postgrex`, and `jason` (all pulled in\ntransitively). LiveView 1.1 is required because dialogs render through\n`Phoenix.Component.portal/1`.\n\n## Quick start\n\nRender the component in any LiveView, passing a `:source`:\n\n```elixir\ndef render(assigns) do\n  ~H\"\"\"\n  \u003c.live_component\n    module={Lantern.Explorer}\n    id=\"db\"\n    source={\"postgres://user:pass@localhost:5432/my_db\"}\n    title=\"My database\"\n  /\u003e\n  \"\"\"\nend\n```\n\nThen wire up the [CSS](#styling) and the [JS hook](#javascript-hook). That's it.\n\n## Connection sources\n\n`:source` is anything `Lantern.Source.from/1` accepts:\n\n```elixir\n# URL string\n\"postgres://user:pass@host:5432/my_db?sslmode=require\"\n\n# keyword list / map\n[hostname: \"localhost\", port: 5432, username: \"postgres\",\n password: \"postgres\", database: \"my_db\"]\n\n# a struct/record exposing host(name)/port/username/password/database,\n# e.g. one you already use to describe a tenant or branch database\n%MyApp.Database{host: \"...\", port: 5432, username: \"...\", password: \"...\", database: \"...\"}\n```\n\n## Styling\n\nImport the bundled stylesheet so the explorer looks good out of the box. With\nesbuild/Tailwind, import it from the dep in your `app.css`:\n\n```css\n@import \"../../deps/lantern/priv/static/lantern/lantern.css\";\n```\n\nIt's self-contained and lives in a low-priority `@layer lantern`, so any rule\nyou write outside that layer overrides it. Re-theme by setting `--lt-*`\nvariables on `.lantern` (or a parent):\n\n```css\n.lantern {\n  --lt-accent: #e0552d;\n  --lt-radius: 0.75rem;\n  --lt-height: 720px;     /* fixed shell height */\n  --lt-font: \"Inter\", sans-serif;\n  --lt-mono: \"JetBrains Mono\", monospace;\n}\n```\n\nYou can also set styling hooks directly on the component:\n\n```elixir\n\u003c.live_component\n  module={Lantern.Explorer}\n  id=\"db\"\n  source={...}\n  class=\"my-admin-db\"\n  theme={:dark}\n  style=\"--lt-accent: #e0552d; --lt-height: 720px;\"\n/\u003e\n```\n\nStyling assigns:\n\n| Assign | Values | Purpose |\n| --- | --- | --- |\n| `:class` | string/list | Extra classes appended to the root `.lantern` element. |\n| `:theme` | `:system`, `:light`, `:dark` | Sets `data-theme`; `:system` follows `prefers-color-scheme`. |\n| `:style` | string | Inline style for quick CSS-variable overrides. |\n\nTheme variables:\n\n| Variable | Purpose |\n| --- | --- |\n| `--lt-bg`, `--lt-bg-subtle`, `--lt-bg-hover` | Surfaces and hover states. |\n| `--lt-fg`, `--lt-fg-muted` | Text colors. |\n| `--lt-border`, `--lt-ring` | Borders and focus rings. |\n| `--lt-accent`, `--lt-danger`, `--lt-success` | Action/status colors. |\n| `--lt-font`, `--lt-mono` | UI and monospace fonts. |\n| `--lt-text`, `--lt-text-sm` | Base and small text sizes. |\n| `--lt-radius`, `--lt-radius-sm` | Corner radii. |\n| `--lt-height` | Fixed explorer shell height. |\n\nForce dark mode with `theme={:dark}` or `data-theme=\"dark\"`; force light mode\nwith `theme={:light}` or `data-theme=\"light\"`. Omit it to follow the OS setting.\n\n**Using Tailwind?** Optionally import the preset to map Lantern's variables onto\nyour Tailwind color scale:\n\n```css\n@import \"../../deps/lantern/priv/static/lantern/lantern.css\";\n@import \"../../deps/lantern/priv/static/lantern/lantern.tailwind.css\";\n```\n\n## JavaScript hook\n\nColumn resizing, the \"set NULL\" buttons, and live JSON validation use a\nLiveView hook. Register it on your `LiveSocket`. With esbuild, point at the dep:\n\n```js\nimport { LanternGrid } from \"../deps/lantern/priv/static/lantern/hooks\"\n\nconst liveSocket = new LiveSocket(\"/live\", Socket, {\n  params: { _csrf_token: csrfToken },\n  hooks: { LanternGrid },\n})\n```\n\n(Browsing and editing still work without the hook — you just lose resizing,\none-click NULL, and the JSON syntax highlight.)\n\n## Headless data API\n\nThe data layer is usable on its own, without the component:\n\n```elixir\n{:ok, schemas} = Lantern.list_schemas(source)\n{:ok, tables}  = Lantern.list_tables(source)                # defaults to schema: \"public\"\n{:ok, tables}  = Lantern.list_tables(source, schema: \"ops\")\n{:ok, stats}   = Lantern.table_stats(source, schema: \"ops\") # total/table/index sizes\n{:ok, columns} = Lantern.columns(source, \"users\")          # name, type, nullable, enum, fk\n{:ok, page}    = Lantern.query(source, \"users\",\n                   schema: \"public\",\n                   filters: [%{column: \"email\", op: \"contains\", value: \"@example.com\"}],\n                   sort_by: \"inserted_at\", sort_dir: :desc,\n                   count: false,\n                   limit: 50, offset: 0)\n\n{:ok, row}     = Lantern.insert(source, \"users\", %{\"email\" =\u003e \"a@b.co\"})\n{:ok, updated} = Lantern.update(source, \"users\", %{\"name\" =\u003e \"Ada\"}, %{\"id\" =\u003e \"1\"})\n{:ok, count}   = Lantern.delete(source, \"users\", [%{\"id\" =\u003e \"1\"}, %{\"id\" =\u003e \"2\"}])\n```\n\nWrite values are passed as strings (or `nil` for SQL `NULL`) and cast to each\ncolumn's type by Postgres.\n\nPass `schema: \"...\"` to reads, writes, and DDL calls to target non-`public`\nPostgres schemas. Identifiers are schema-qualified and quoted safely.\n\nSchema changes (DDL) are available too. Identifiers are quoted and types are\nchecked against an allowlist before anything touches the database:\n\n```elixir\n:ok = Lantern.create_table(source, \"widgets\", [\n        %{name: \"id\", type: \"bigserial\", nullable: false, primary_key: true},\n        %{name: \"label\", type: \"text\"}\n      ])\n:ok = Lantern.add_column(source, \"widgets\", %{name: \"qty\", type: \"integer\"})\n:ok = Lantern.rename_column(source, \"widgets\", \"label\", \"name\")\n:ok = Lantern.drop_column(source, \"widgets\", \"qty\")\n:ok = Lantern.rename_table(source, \"widgets\", \"gadgets\")\n:ok = Lantern.drop_table(source, \"gadgets\")\n```\n\n## Security\n\nLantern runs arbitrary reads and writes against the database you give it. By\ndefault, **the raw SQL filter input is hidden** (`allow_raw_filter: false`),\nbecause a literal SQL fragment appended after `WHERE` can execute data-\nmodifying CTEs, sub-selects, and other arbitrary SQL under the connection\nrole's privileges — that's an open SQL proxy in disguise.\n\nFor operator-facing pages where you trust the user, opt in:\n\n```elixir\n\u003c.live_component module={Lantern.Explorer} id=\"db\" source={...} allow_raw_filter={true} /\u003e\n```\n\nYou are responsible for:\n\n- Gating the page behind authentication/authorization (e.g. an admin pipeline).\n- Only handing Lantern a `:source` you control. Never build a `:source` from\n  untrusted user input — that turns your server into an open SQL proxy.\n- Only enabling `allow_raw_filter: true` in trusted operator contexts.\n\nBecause the connection is operator-supplied, Lantern itself adds no extra\nsandboxing; the trust boundary is your auth layer.\n\n### Read-only deployments\n\nFor viewers you don't fully trust (a public dashboard, a support read-out),\npass `read_only: true`:\n\n```elixir\n\u003c.live_component module={Lantern.Explorer} id=\"db\" source={...} read_only={true} /\u003e\n```\n\nIt hides every write affordance (inline edit, row insert, bulk delete, and all\nDDL) **and** refuses the matching events server-side, and restricts the SQL\nworkspace to `SELECT`/`EXPLAIN`. Browsing, filtering, sorting, foreign-key\nnavigation, charts, and exports stay available.\n\n## Demo\n\nA live demo is running at **[lantern-demo.flickercloud.com](https://lantern-demo.flickercloud.com)** — the shared database is read-only; request a sandbox to get a full read/write Postgres branch of your own.\n\nTo run the demo locally:\n\n```bash\ncd examples/demo\ndocker compose up -d\nmix setup\nmix phx.server\n```\n\nOpen \u003chttp://localhost:4001\u003e. See `examples/demo/README.md` for details.\n\n## Development\n\n```bash\nmix deps.get\nmix test                      # unit tests (no database needed)\n```\n\nIntegration tests run real Postgres round-trips and are excluded by default.\nPoint them at a database you can create/drop tables in:\n\n```bash\nLANTERN_TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/lantern_test \\\n  mix test --include integration\n```\n\n## License\n\nMIT © Giovanni Orlando. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo9%2Flantern","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgo9%2Flantern","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo9%2Flantern/lists"}