# How to host an app your AI built with its own URL and saved data

> Source: <https://dev.to/usecharming/how-to-host-an-app-your-ai-built-with-its-own-url-and-saved-data-18ma>
> Published: 2026-07-24 20:17:03+00:00

Claude can write a working app right inside a chat. Close the chat and it's gone: no URL, no saved data, nothing to open tomorrow. This is how you turn that throwaway app into a hosted one with its own URL and a database behind it. The example is a reading list, live at [charm.ing/avi/reading-list](https://charm.ing/avi/reading-list).

The thing doing the hosting is [Charming](https://usecharming.com). Claude writes the code, Charming runs it. You keep using Claude, or whatever agent you already pay for. Charming is just where the app lives once the conversation ends.

The [canonical guide](https://usecharming.com/blog/how-to-host-an-app-built-with-claude) has the full walkthrough and screenshots. This version is for developers: how the connector hooks up, what the published module looks like, and how other agents reach it afterward.

Charming plugs into Claude as a custom MCP connector. One-time setup:

`https://charm.ing/mcp`

.Screenshots and troubleshooting: [usecharming.com/setup/claude](https://usecharming.com/setup/claude/).

In a normal Claude chat:

Build me a reading list app. I want to add books with a title and author, move each one between to-read, reading, and read, and give finished books a star rating.

Claude writes it and previews it inline. So far it's just like any other in-chat app. Then:

Create this on Charming so it has its own URL and saves my data.

Claude calls the connector and hands back a live URL. That's your hosted app, with storage behind it and an API other agents can call later.

Under the connector, Claude authors and ships one ES module with two named exports:

``` js
export const manifest = {
  id: 'reading-list',
  version: '0.0.1',
  displayName: 'Reading list',
  capabilities: {
    imports: ['buildy:storage/kv@1.0'], // gives the handlers env.storage
    exports: [],
  },
};

export const routes = [
  {
    op: 'list',
    method: 'GET',
    handler: async (_input, { env }) => (await env.storage.get('books')) ?? [],
  },
  {
    op: 'add',
    method: 'POST',
    handler: async (input, { env }) => {
      const books = (await env.storage.get('books')) ?? [];
      books.push({ ...input, status: 'to-read' });
      await env.storage.put('books', books);
      return books;
    },
  },
];
```

No server to stand up, no database to provision. `env.storage`

is per-app key/value storage that survives every later update, and each declared route becomes a discoverable operation at `/app/<id>/api/*`

.

Once it's published you're not locked into the chat that built it. Two ways in:

`https://charm.ing/mcp`

and it can read or write the same app's data.`POST https://charm.ing/api/pair/start`

pairs an agent to your account. From there `POST /app`

creates an app and `PUT /app/<id>`

updates one, and every declared route is callable at `/app/<id>/api/<op>`

.One data store, more than one agent. From a fresh chat I asked Claude to add the Harry Potter series to the same list. It read the existing app over the connector and queued all seven as to-read. I never touched the storage layer.

Close the Claude conversation. Open the app's URL again days later, in a new tab. Everything's still there: the books, the statuses, the ratings. They live in Charming's storage, not in the chat transcript.
