-- Your agent wrote this:
posts <- sqlQueryTyped [typedSql|
SELECT id, title, publish_date FROM posts
|]
error: [GHC-39584]
• typedSql: prepare failed:
ERROR: column "publish_date" does not exist
LINE 1: SELECT id, title, publish_date FROM posts
^
This never reaches production. typedSql asks your Postgres to
describe the query while your code is still compiling.
An agent produces more code in an afternoon than a team can review in a week. The bottleneck was never typing speed — it's knowing the code is correct. In most stacks the answer is "run it and find out." In IHP the answer is: it doesn't compile.
Every query is checked against your real Postgres schema at compile time. A hallucinated column is a build error, not a 3am page.
learn moreYour schema file is the single source of truth. Records, ids and enums are generated from it, so your agent's code can't drift from your database.
learn moreHSX parses your HTML at compile time. The routes DSL validates every path capture and query parameter against your action types.
learn moreA Hoogle server runs on every project by default, indexing every package in your flake. Your agent looks up real type signatures instead of inventing them.
learn more$ ihp-new blog
blog/
├── AGENTS.md # house rules your agent reads first
├── CLAUDE.md # → AGENTS.md
├── .claude/
│ └── launch.json # Claude Code preview, auto-port
├── Application/Schema.sql
└── flake.nix
Conventions, committed. An AGENTS.md ships with the project: use typed SQL over raw SQL, stamp migrations with a real timestamp, and verify before handing off.
Claude Code preview, already wired. The dev server honors the PORT environment variable, so the preview picks a free port and IHP binds exactly that one instead of silently drifting to another.
Typechecks headless. Compile-time SQL checking starts a temporary Postgres on demand, so it still works in agent workspaces where the dev server isn't running.
One command to verify. Run nix flake check --impure — it builds, typechecks and runs the suite, the handoff gate before you even look at a diff.
A walkthrough of building an IHP app end to end — clicking together the schema, generating the controller and views, and watching the dev server reload as the code changes.
Everything here is the same loop your agent works in.
Recognized by G2 reviewers








While haskell is a compiled language, the built-in dev server automatically reloads your code changes using the fastest way possible.
Changes are reflected instantly. Just like good old PHP.
IHP is written in Haskell — a language with a type system strong enough that Mercury and Standard Chartered run banking infrastructure on it. That used to be a trade-off: more guarantees, less familiarity.
With an agent at the keyboard it stops being a trade-off. Haskell tells your agent it's wrong immediately and precisely, in a way Python and TypeScript can't. The stricter the language, the tighter the loop.
You review behaviour. The compiler reviews the code.
-- Make a password hash
hash <- hashPassword "hunter2"
-- Set values
let user = newRecord @User
|> set #email "someone@example.com"
|> set #passwordHash hash
-- Insert it into the DB
createRecord user
action UsersAction = do
-- Fetch 10 users ordered by firstname
users <- query @User
|> orderBy #firstname
|> limit 10
|> fetch
render IndexView { .. }
-- The autoRefresh keyword makes the action realtime
-- No app-specific JS needed
-- ↘↘↘
action MessagesAction = autoRefresh do
messages <- query @Message
|> orderBy #createdAt
|> fetch
render IndexView { .. }
ihp-openai gives you streaming completions, typed function calling and structured output. When a stream drops it resumes from the partial response, so your user never sees the retry.
Write each token into a Postgres column and Auto Refresh does the rest — it diffs the page server-side and pushes only what changed over a WebSocket. No client-side state, no streaming endpoint, no JavaScript.
Long-running work goes to background jobs backed by Postgres LISTEN/NOTIFY, with retries and scheduling built in.
-- Each token lands in Postgres as it arrives
streamCompletion config request (pure ()) \chunk ->
forEach chunk.choices \choice ->
forEach choice.delta.content \token ->
sqlExecTyped [typedSql|
UPDATE answers SET body = body || ${token}
WHERE id = ${answerId}
|]
-- The view is already live. No JavaScript needed.
action ShowAnswerAction { answerId } = autoRefresh do
answer <- fetch answerId
render ShowView { .. }

IHP is a database centric web framework. We introspect your database schema to provide type-safe APIs and query builders.
You don't have to be a database expert to use IHP, with the Schema Designer you quickly click together your data structures.
If you like your code editor more, you can always manually edit the SQL files.
Stop dealing with repetitive form HTML code. IHP brings you a sophisticated but simple form engine that takes care of the HTML and validation logic.
instance View NewView where
html NewView { .. } = [hsx|
<h1>New Comment</h1>
{renderForm comment}
|]
renderForm comment = formFor comment [hsx|
{hiddenField #threadId}
<!-- Labels + Validation Results are taken care of -->
{textareaField #body}
{submitButton}
|]
instance View NewView where
html NewView { .. } = [hsx|
<h1>New Comment</h1>
{renderForm comment}
|]
renderForm comment = formFor comment [hsx|
{hiddenField #threadId}
{(textareaField #body) {
fieldLabel = "Your Comment:",
helpText = "You can use markdown here."
}}
<p>
<!-- Use any HTML inside your forms -->
Please double check your comment
for spelling mistakes.
</p>
{submitButton}
|]
action CreateCommentAction = do
let comment = newRecord @Comment
comment
|> fill @["body", "threadId"]
|> set #userId currentUserId
-- Actual validation here
|> validateField #body nonEmpty
|> validateField #threadId nonEmpty
|> ifValid \case
-- Validation Failed -> Render form + errors
Left comment -> render NewView { .. }
-- Validation Good
Right comment -> do
-- Insert comment to DB
comment <- comment |> createRecord
let commentId = get #id comment
-- Redirect to comment
redirectTo ShowCommentAction { commentId }
Try out what the future of software engineering feels like
