{
  "version": "v1",
  "generated_at": "2022-01-01T05:00:00.000Z",
  "self": "https://aatmanjain.com/data/v1/glossary.json",
  "html_url": "https://aatmanjain.com/glossary",
  "markdown_url": "https://aatmanjain.com/glossary.md",
  "intro": "Most of these I've explained more than once, usually in the Base44 Discord. So here they are, written down properly. One plain sentence first, then what I'd actually tell you if you asked.",
  "last_checked": "2026-09-26",
  "categories": [
    {
      "id": "base44",
      "title": "Base44",
      "intro": "The platform, and the bits people ask about first."
    },
    {
      "id": "security",
      "title": "Security",
      "intro": "Who can see what, and where keys belong."
    },
    {
      "id": "backend",
      "title": "Backend",
      "intro": "Server code, email, payments and jobs that run on their own."
    },
    {
      "id": "ai",
      "title": "AI and agents",
      "intro": "Agents, and the tools I build them with."
    },
    {
      "id": "seo",
      "title": "Search",
      "intro": "Getting found by search engines and AI assistants."
    },
    {
      "id": "code",
      "title": "Code",
      "intro": "Bits of computer science from my own projects."
    }
  ],
  "count": 27,
  "items": [
    {
      "slug": "agent-skill",
      "term": "Agent skill",
      "aka": [
        "SKILL.md"
      ],
      "category": "ai",
      "short": "A folder of instructions, built around a SKILL.md file, that an AI agent reads only when a task calls for it.",
      "body_markdown": "A skill is a folder with a `SKILL.md` file inside. The top of the file names the skill and says when to use it. Everything below that is the instructions. Here's the top of one of mine:\n\n```md\n---\nname: base44-prompt-engineer\ndescription: Craft high-quality, production-grade mega-prompts for the Base44 AI chat...\n---\n```\n\nThe clever bit is that the agent only reads those two lines up front. It opens the rest when your request matches the description. So you can install loads of skills without stuffing every conversation full of them.\n\nSkills follow an open spec, so the same folder works in different agents. Base44 publishes its own set (`npx skills add base44/skills`) that teaches coding agents its CLI and SDK. Confusingly, Base44 also uses \"agent skills\" for the instructions inside your app's own agents. Different thing, same name.\n\nMy open-source skills on GitHub include the prompt engineer above and one for Modelsheet.",
      "body_text": "A skill is a folder with a SKILL.md file inside. The top of the file names the skill and says when to use it. Everything below that is the instructions. Here's the top of one of mine:\n\nmd --- name: base44-prompt-engineer description: Craft high-quality, production-grade mega-prompts for the Base44 AI chat... ---\n\nThe clever bit is that the agent only reads those two lines up front. It opens the rest when your request matches the description. So you can install loads of skills without stuffing every conversation full of them.\n\nSkills follow an open spec, so the same folder works in different agents. Base44 publishes its own set (npx skills add base44/skills) that teaches coding agents its CLI and SDK. Confusingly, Base44 also uses \"agent skills\" for the instructions inside your app's own agents. Different thing, same name.\n\nMy open-source skills on GitHub include the prompt engineer above and one for Modelsheet.",
      "related": [
        "claude-code",
        "prompt-engineering"
      ],
      "see_also": [
        {
          "label": "Open-source skills",
          "url": "https://aatmanjain.com/resources/skills"
        },
        {
          "label": "Open source",
          "url": "https://aatmanjain.com/open-source"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: skills",
          "url": "https://docs.base44.com/developers/backend/overview/skills"
        },
        {
          "label": "Agent Skills specification",
          "url": "https://agentskills.io/specification"
        }
      ],
      "url": "https://aatmanjain.com/glossary/agent-skill",
      "markdown_url": "https://aatmanjain.com/glossary/agent-skill.md"
    },
    {
      "slug": "baas",
      "term": "BaaS",
      "aka": [
        "backend as a service"
      ],
      "category": "base44",
      "short": "Backend as a service: a database, logins, file storage and server code that are already running, so you only build the app on top.",
      "body_markdown": "Every app has a front and a back. The front is what people see and tap. The back saves the data and decides who's allowed to see it. Building that yourself means servers, a database, password resets and a lot of lost weekends.\n\nA BaaS hands you the back already running. Base44 is one. Every app comes with [entities](https://aatmanjain.com/glossary/entity) for data, logins, [row-level security](https://aatmanjain.com/glossary/row-level-security), backend functions and integrations from day one.\n\nThe picture I use is renting a furnished flat. You still choose where the sofa goes. You just don't fix the boiler.\n\nI made a free course about it, [What's BaaS?](https://whatsbaas.base44.app/): twelve five-minute lessons and a playground for trying SDK calls without breaking anything. No code needed.",
      "body_text": "Every app has a front and a back. The front is what people see and tap. The back saves the data and decides who's allowed to see it. Building that yourself means servers, a database, password resets and a lot of lost weekends.\n\nA BaaS hands you the back already running. Base44 is one. Every app comes with entities for data, logins, row-level security, backend functions and integrations from day one.\n\nThe picture I use is renting a furnished flat. You still choose where the sofa goes. You just don't fix the boiler.\n\nI made a free course about it, What's BaaS?: twelve five-minute lessons and a playground for trying SDK calls without breaking anything. No code needed.",
      "related": [
        "entity",
        "backend-function",
        "row-level-security"
      ],
      "see_also": [
        {
          "label": "What's BaaS? case study",
          "url": "https://aatmanjain.com/work/whats-baas"
        },
        {
          "label": "Base44 app development",
          "url": "https://aatmanjain.com/services/base44-app-development"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: developer platform",
          "url": "https://docs.base44.com/developers/home"
        }
      ],
      "url": "https://aatmanjain.com/glossary/baas",
      "markdown_url": "https://aatmanjain.com/glossary/baas.md"
    },
    {
      "slug": "backend-function",
      "term": "Backend function",
      "aka": [],
      "category": "backend",
      "short": "Your own code running on Base44's servers instead of in the browser, for anything that needs a secret key or shouldn't be visible to users.",
      "body_markdown": "Anything in your frontend can be read by whoever opens dev tools. So code that uses a secret key, or decides who gets what, belongs in a backend function.\n\nOn Base44 they run on Deno, and the folder name becomes the function name. A small one:\n\n```ts\nimport { createClientFromRequest } from 'npm:@base44/sdk';\n\nDeno.serve(async (req) => {\n  const base44 = createClientFromRequest(req);\n  const user = await base44.auth.me().catch(() => null);\n  if (!user) return Response.json({ error: 'Sign in first' }, { status: 401 });\n  return Response.json({ hello: user.email });\n});\n```\n\nFrom the page you call it with `base44.functions.invoke('name', data)`. Each function also gets its own URL, which is how webhooks reach it.\n\nThe gotcha: `invoke` gives you the whole HTTP response, so your data is on `res.data`. If a function seems to return nothing, check that before anything else.",
      "body_text": "Anything in your frontend can be read by whoever opens dev tools. So code that uses a secret key, or decides who gets what, belongs in a backend function.\n\nOn Base44 they run on Deno, and the folder name becomes the function name. A small one:\n\n`ts import { createClientFromRequest } from 'npm:@base44/sdk';\n\nDeno.serve(async (req) => { const base44 = createClientFromRequest(req); const user = await base44.auth.me().catch(() => null); if (!user) return Response.json({ error: 'Sign in first' }, { status: 401 }); return Response.json({ hello: user.email }); }); `\n\nFrom the page you call it with base44.functions.invoke('name', data). Each function also gets its own URL, which is how webhooks reach it.\n\nThe gotcha: invoke gives you the whole HTTP response, so your data is on res.data. If a function seems to return nothing, check that before anything else.",
      "related": [
        "secrets",
        "service-role",
        "integration"
      ],
      "see_also": [
        {
          "label": "Backend and integrations",
          "url": "https://aatmanjain.com/services/backend-and-integrations"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: backend functions",
          "url": "https://docs.base44.com/developers/backend/resources/backend-functions/overview"
        }
      ],
      "url": "https://aatmanjain.com/glossary/backend-function",
      "markdown_url": "https://aatmanjain.com/glossary/backend-function.md"
    },
    {
      "slug": "base44-cli",
      "term": "Base44 CLI",
      "aka": [],
      "category": "base44",
      "short": "Base44's command-line tool for building and deploying Base44 projects from your own terminal or AI coding agent.",
      "body_markdown": "The CLI turns a Base44 backend into files in a folder. Entities, functions, agents and connectors all become things you can edit and push like normal code.\n\n```bash\nbase44 login\nbase44 link\nbase44 dev\nbase44 deploy\n```\n\nTwo warnings, both in the docs, both easy to miss.\n\nFirst, `base44 deploy` fully syncs entities, connectors, agents and agent skills. If something exists on Base44 but not in your local folder, deploy removes it. The data survives, but the SDK can't reach that entity any more. Deploying from an old folder is how that happens.\n\nSecond, if your app syncs with GitHub (this site does), a CLI deploy skips the sync and the live app quietly drifts away from the repo. Push to GitHub and publish from the dashboard instead.\n\nI built Whetstone backend-first through the CLI, entity schemas and all. It's still in development.",
      "body_text": "The CLI turns a Base44 backend into files in a folder. Entities, functions, agents and connectors all become things you can edit and push like normal code.\n\nbash base44 login base44 link base44 dev base44 deploy\n\nTwo warnings, both in the docs, both easy to miss.\n\nFirst, base44 deploy fully syncs entities, connectors, agents and agent skills. If something exists on Base44 but not in your local folder, deploy removes it. The data survives, but the SDK can't reach that entity any more. Deploying from an old folder is how that happens.\n\nSecond, if your app syncs with GitHub (this site does), a CLI deploy skips the sync and the live app quietly drifts away from the repo. Push to GitHub and publish from the dashboard instead.\n\nI built Whetstone backend-first through the CLI, entity schemas and all. It's still in development.",
      "related": [
        "entity",
        "agent-skill"
      ],
      "see_also": [
        {
          "label": "Open source",
          "url": "https://aatmanjain.com/open-source"
        },
        {
          "label": "Base44 app development",
          "url": "https://aatmanjain.com/services/base44-app-development"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: CLI overview",
          "url": "https://docs.base44.com/developers/references/cli/get-started/overview"
        },
        {
          "label": "Base44 docs: deploy command",
          "url": "https://docs.base44.com/developers/references/cli/commands/deploy"
        }
      ],
      "url": "https://aatmanjain.com/glossary/base44-cli",
      "markdown_url": "https://aatmanjain.com/glossary/base44-cli.md"
    },
    {
      "slug": "claude-code",
      "term": "Claude Code",
      "aka": [],
      "category": "ai",
      "short": "Anthropic's AI coding agent: it works through a whole task in your codebase, running commands and editing files as it goes.",
      "body_markdown": "Autocomplete finishes your line. Claude Code takes the whole task. It reads the repo, plans the change, edits the files and runs the tests, and it asks before doing anything risky.\n\nMy opinion changelog here is slightly embarrassing. I thought GPT was mostly hype. Then I used only Claude Code. Then I decided Codex was better. Now I pay for both. Make of that what you will.\n\nWhat matters more than which agent you pick is the context file. A `CLAUDE.md` (or `AGENTS.md`, which Codex reads) that says how to run and test the project makes any agent far less confused.\n\nFor Base44 work, install Base44's official skills first. Then it writes entities and access rules the way the platform expects, instead of the way some random tutorial did.",
      "body_text": "Autocomplete finishes your line. Claude Code takes the whole task. It reads the repo, plans the change, edits the files and runs the tests, and it asks before doing anything risky.\n\nMy opinion changelog here is slightly embarrassing. I thought GPT was mostly hype. Then I used only Claude Code. Then I decided Codex was better. Now I pay for both. Make of that what you will.\n\nWhat matters more than which agent you pick is the context file. A CLAUDE.md (or AGENTS.md, which Codex reads) that says how to run and test the project makes any agent far less confused.\n\nFor Base44 work, install Base44's official skills first. Then it writes entities and access rules the way the platform expects, instead of the way some random tutorial did.",
      "related": [
        "agent-skill",
        "mcp",
        "prompt-engineering"
      ],
      "see_also": [
        {
          "label": "What I use",
          "url": "https://aatmanjain.com/uses"
        }
      ],
      "sources": [
        {
          "label": "Claude Code docs",
          "url": "https://code.claude.com/docs/en/overview"
        }
      ],
      "url": "https://aatmanjain.com/glossary/claude-code",
      "markdown_url": "https://aatmanjain.com/glossary/claude-code.md"
    },
    {
      "slug": "connector",
      "term": "Connector",
      "aka": [],
      "category": "backend",
      "short": "A sign-in link between a Base44 app and an account on another service, like Gmail or Slack, so the app can act there.",
      "body_markdown": "A connector gets your app into another service without API keys pasted everywhere. You click connect, sign in, approve what it's allowed to do, and Base44 keeps the token.\n\nThe real decision is whose account it is. A shared connector is one account for the whole app, like the team Slack. An app user connector lets each person connect their own, which is what you want the moment it's somebody's inbox or calendar. App user connectors also need your own OAuth app details, so decide early.\n\nSome connected tools can start a [workflow](https://aatmanjain.com/glossary/workflow) as well: a new email landing in Gmail, say, or an event changing in Google Calendar.\n\nAsk for the smallest set of permissions the job needs. People do read that consent screen, and a scary one gets closed.",
      "body_text": "A connector gets your app into another service without API keys pasted everywhere. You click connect, sign in, approve what it's allowed to do, and Base44 keeps the token.\n\nThe real decision is whose account it is. A shared connector is one account for the whole app, like the team Slack. An app user connector lets each person connect their own, which is what you want the moment it's somebody's inbox or calendar. App user connectors also need your own OAuth app details, so decide early.\n\nSome connected tools can start a workflow as well: a new email landing in Gmail, say, or an event changing in Google Calendar.\n\nAsk for the smallest set of permissions the job needs. People do read that consent screen, and a scary one gets closed.",
      "related": [
        "workflow",
        "integration",
        "superagent"
      ],
      "see_also": [
        {
          "label": "AI agents and automation",
          "url": "https://aatmanjain.com/services/ai-agents-and-automation"
        },
        {
          "label": "Backend and integrations",
          "url": "https://aatmanjain.com/services/backend-and-integrations"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: connectors",
          "url": "https://docs.base44.com/Integrations/Connectors"
        },
        {
          "label": "Base44 docs: app user connectors",
          "url": "https://docs.base44.com/developers/backend/resources/connectors/app-user-connectors"
        }
      ],
      "url": "https://aatmanjain.com/glossary/connector",
      "markdown_url": "https://aatmanjain.com/glossary/connector.md"
    },
    {
      "slug": "entity",
      "term": "Entity",
      "aka": [
        "data table"
      ],
      "category": "base44",
      "short": "A table of data in a Base44 app that holds one kind of thing, like tasks or bookings.",
      "body_markdown": "Every app is secretly a pile of lists. A gym app has a list of members and a list of bookings. In Base44 each list is an entity, and each booking is one record in it.\n\nYou describe the fields once, in a schema. Usually you say it in plain English and the builder writes the JSON. Every record also gets `id`, `created_date`, `updated_date` and `created_by` for free, so don't add your own copies.\n\nTwo mistakes I see a lot:\n\n- One giant entity called Data with a field for everything. Split it, one entity per kind of thing.\n- Trusting `unique`. Base44 stores that setting and ignores it. If duplicates matter, check for them yourself.\n\nAnd the one that hurts: a new entity with no access rules is open to everyone. That's what [row-level security](https://aatmanjain.com/glossary/row-level-security) is for.",
      "body_text": "Every app is secretly a pile of lists. A gym app has a list of members and a list of bookings. In Base44 each list is an entity, and each booking is one record in it.\n\nYou describe the fields once, in a schema. Usually you say it in plain English and the builder writes the JSON. Every record also gets id, created_date, updated_date and created_by for free, so don't add your own copies.\n\nTwo mistakes I see a lot:\n\nOne giant entity called Data with a field for everything. Split it, one entity per kind of thing. Trusting unique. Base44 stores that setting and ignores it. If duplicates matter, check for them yourself.\n\nAnd the one that hurts: a new entity with no access rules is open to everyone. That's what row-level security is for.",
      "related": [
        "row-level-security",
        "baas"
      ],
      "see_also": [
        {
          "label": "What's BaaS? case study",
          "url": "https://aatmanjain.com/work/whats-baas"
        },
        {
          "label": "Base44 app development",
          "url": "https://aatmanjain.com/services/base44-app-development"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: entity schemas",
          "url": "https://docs.base44.com/developers/backend/resources/entities/entity-schemas"
        }
      ],
      "url": "https://aatmanjain.com/glossary/entity",
      "markdown_url": "https://aatmanjain.com/glossary/entity.md"
    },
    {
      "slug": "fisher-yates-shuffle",
      "term": "Fisher-Yates shuffle",
      "aka": [
        "Knuth shuffle"
      ],
      "category": "code",
      "short": "A way to shuffle a list in one pass so that every possible order is equally likely.",
      "body_markdown": "Walk backwards through the list. At each position, pick a random spot from the start up to and including where you are, then swap the two.\n\n```js\nfor (let i = a.length - 1; i > 0; i--) {\n  const j = Math.floor(rand() * (i + 1));\n  [a[i], a[j]] = [a[j], a[i]];\n}\n```\n\nThe classic bug is picking from the whole list every time, or sorting with `() => Math.random() - 0.5`. Both look random. Both quietly make some orders more likely than others. In a sweepstake, that's a rigged draw nobody meant to rig.\n\n[Hattle](https://aatmanjain.com/work/hattle), my World Cup sweepstake app, used it to deal all 48 teams out to a group. The `rand` there wasn't `Math.random()`, though. It was [seeded](https://aatmanjain.com/glossary/seeded-randomness), so any draw can be re-run and checked.",
      "body_text": "Walk backwards through the list. At each position, pick a random spot from the start up to and including where you are, then swap the two.\n\njs for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(rand() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; }\n\nThe classic bug is picking from the whole list every time, or sorting with () => Math.random() - 0.5. Both look random. Both quietly make some orders more likely than others. In a sweepstake, that's a rigged draw nobody meant to rig.\n\nHattle, my World Cup sweepstake app, used it to deal all 48 teams out to a group. The rand there wasn't Math.random(), though. It was seeded, so any draw can be re-run and checked.",
      "related": [
        "seeded-randomness"
      ],
      "see_also": [
        {
          "label": "Hattle case study",
          "url": "https://aatmanjain.com/work/hattle"
        }
      ],
      "sources": [
        {
          "label": "Wikipedia: Fisher-Yates shuffle",
          "url": "https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle"
        }
      ],
      "url": "https://aatmanjain.com/glossary/fisher-yates-shuffle",
      "markdown_url": "https://aatmanjain.com/glossary/fisher-yates-shuffle.md"
    },
    {
      "slug": "geo",
      "term": "GEO",
      "aka": [
        "generative engine optimisation",
        "AI SEO"
      ],
      "category": "seo",
      "short": "Generative engine optimisation: making your site easy for AI assistants like ChatGPT and Perplexity to understand and cite correctly.",
      "body_markdown": "Base44's docs make a split I like. AI crawlers, like GPTBot and ClaudeBot, fetch your pages. AI assistants, the things people actually talk to, answer from whatever those crawlers and search engines already collected. Nobody asks a crawler anything.\n\nSo GEO is mostly SEO done properly, plus writing that's easy to quote. Real content in the HTML. One name for yourself, used the same way everywhere. Headings that match real questions, with the answer in the first sentence underneath. [Structured data](https://aatmanjain.com/glossary/json-ld) that matches the page.\n\nThe question I care about is whether an assistant describes you correctly. Being mentioned and being wrong is worse than silence.\n\nThis glossary is partly a GEO exercise, if I'm honest. Every entry opens with a one-sentence answer for exactly that reason.",
      "body_text": "Base44's docs make a split I like. AI crawlers, like GPTBot and ClaudeBot, fetch your pages. AI assistants, the things people actually talk to, answer from whatever those crawlers and search engines already collected. Nobody asks a crawler anything.\n\nSo GEO is mostly SEO done properly, plus writing that's easy to quote. Real content in the HTML. One name for yourself, used the same way everywhere. Headings that match real questions, with the answer in the first sentence underneath. Structured data that matches the page.\n\nThe question I care about is whether an assistant describes you correctly. Being mentioned and being wrong is worse than silence.\n\nThis glossary is partly a GEO exercise, if I'm honest. Every entry opens with a one-sentence answer for exactly that reason.",
      "related": [
        "json-ld",
        "llms-txt",
        "spa-seo"
      ],
      "see_also": [
        {
          "label": "SEO and performance",
          "url": "https://aatmanjain.com/services/seo-and-performance"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: SEO and search visibility",
          "url": "https://docs.base44.com/Performance-and-SEO/SEO-and-search-visibility"
        }
      ],
      "url": "https://aatmanjain.com/glossary/geo",
      "markdown_url": "https://aatmanjain.com/glossary/geo.md"
    },
    {
      "slug": "ai-agent",
      "term": "In-app agent",
      "aka": [
        "AI agent"
      ],
      "category": "ai",
      "short": "An AI assistant built into a Base44 app that chats with your users and can use the data and functions you give it access to.",
      "body_markdown": "An in-app agent lives inside your app and talks to your users. The difference from a plain chatbot is tools. You can let it work with certain entities and call your backend functions, so it checks an order's actual status instead of guessing.\n\nPeople mix it up with a [Superagent](https://aatmanjain.com/glossary/superagent) all the time. The quick test: an in-app agent works for your app's users. A Superagent works for you.\n\nEach message costs integration credits, around 3 on the default model according to Base44's docs. For a one-off job, like tagging a message or summarising a field, a single `InvokeLLM` call is all you need.\n\nWhatever you give it, scope it tight. An agent with write access to every entity is a security review waiting to happen.",
      "body_text": "An in-app agent lives inside your app and talks to your users. The difference from a plain chatbot is tools. You can let it work with certain entities and call your backend functions, so it checks an order's actual status instead of guessing.\n\nPeople mix it up with a Superagent all the time. The quick test: an in-app agent works for your app's users. A Superagent works for you.\n\nEach message costs integration credits, around 3 on the default model according to Base44's docs. For a one-off job, like tagging a message or summarising a field, a single InvokeLLM call is all you need.\n\nWhatever you give it, scope it tight. An agent with write access to every entity is a security review waiting to happen.",
      "related": [
        "superagent",
        "integration",
        "workflow"
      ],
      "see_also": [
        {
          "label": "AI agents and automation",
          "url": "https://aatmanjain.com/services/ai-agents-and-automation"
        },
        {
          "label": "What's BaaS? case study",
          "url": "https://aatmanjain.com/work/whats-baas"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: AI agents for apps",
          "url": "https://docs.base44.com/Building-your-app/AI-agents-for-apps"
        }
      ],
      "url": "https://aatmanjain.com/glossary/ai-agent",
      "markdown_url": "https://aatmanjain.com/glossary/ai-agent.md"
    },
    {
      "slug": "integration",
      "term": "Integration",
      "aka": [],
      "category": "backend",
      "short": "A ready-made Base44 service, like sending an email or calling an AI model, that your app can use in one line of code.",
      "body_markdown": "Integrations are the jobs nearly every app needs, already built. `InvokeLLM` calls an AI model. `UploadFile` stores a file and hands you back a URL:\n\n```js\nconst { file_url } = await base44.integrations.Core.UploadFile({ file });\n```\n\nEach call spends integration credits. Fine on a button someone presses once. Less fine on a page that fires an AI call every time it loads. Base44's security scan even checks whether people outside your app can reach your credit-using features and spend the credits for you.\n\nThe `SendEmail` rule catches people out: it can always email users registered in your app, but outside addresses need a paid plan and a verified custom domain.\n\nIntegration or [backend function](https://aatmanjain.com/glossary/backend-function)? Try the integration first. Write a function when you need your own logic wrapped around it.",
      "body_text": "Integrations are the jobs nearly every app needs, already built. InvokeLLM calls an AI model. UploadFile stores a file and hands you back a URL:\n\njs const { file_url } = await base44.integrations.Core.UploadFile({ file });\n\nEach call spends integration credits. Fine on a button someone presses once. Less fine on a page that fires an AI call every time it loads. Base44's security scan even checks whether people outside your app can reach your credit-using features and spend the credits for you.\n\nThe SendEmail rule catches people out: it can always email users registered in your app, but outside addresses need a paid plan and a verified custom domain.\n\nIntegration or backend function? Try the integration first. Write a function when you need your own logic wrapped around it.",
      "related": [
        "backend-function",
        "connector",
        "resend"
      ],
      "see_also": [
        {
          "label": "Backend and integrations",
          "url": "https://aatmanjain.com/services/backend-and-integrations"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: built-in integrations",
          "url": "https://docs.base44.com/Integrations/built-in-integrations"
        },
        {
          "label": "Base44 docs: sending emails",
          "url": "https://docs.base44.com/documentation/building-your-app/sending-emails"
        }
      ],
      "url": "https://aatmanjain.com/glossary/integration",
      "markdown_url": "https://aatmanjain.com/glossary/integration.md"
    },
    {
      "slug": "json-ld",
      "term": "JSON-LD",
      "aka": [
        "structured data"
      ],
      "category": "seo",
      "short": "Structured data in a script tag that tells search engines and AI tools, in schema.org's vocabulary, exactly what a page is about.",
      "body_markdown": "Search engines are decent at guessing what a page is about. JSON-LD means they don't have to guess. You add a script tag with plain facts in schema.org's vocabulary:\n\n```json\n{\n  \"@context\": \"https://schema.org\",\n  \"@type\": \"DefinedTerm\",\n  \"name\": \"JSON-LD\",\n  \"url\": \"https://aatmanjain.com/glossary/json-ld\"\n}\n```\n\nThat's a trimmed-down copy of what's on this page. Every entry in this glossary is a DefinedTerm inside one DefinedTermSet, which is a fancy way of saying \"these are definitions, and they belong together\".\n\nBase44 adds breadcrumb data to every page for you, and its SEO & GEO scan can generate a few common types. Anything else, you add yourself.\n\nThe rule: only describe what's visible on the page. Then check it with Google's Rich Results Test before you trust it.",
      "body_text": "Search engines are decent at guessing what a page is about. JSON-LD means they don't have to guess. You add a script tag with plain facts in schema.org's vocabulary:\n\njson { \"@context\": \"https://schema.org\", \"@type\": \"DefinedTerm\", \"name\": \"JSON-LD\", \"url\": \"https://aatmanjain.com/glossary/json-ld\" }\n\nThat's a trimmed-down copy of what's on this page. Every entry in this glossary is a DefinedTerm inside one DefinedTermSet, which is a fancy way of saying \"these are definitions, and they belong together\".\n\nBase44 adds breadcrumb data to every page for you, and its SEO & GEO scan can generate a few common types. Anything else, you add yourself.\n\nThe rule: only describe what's visible on the page. Then check it with Google's Rich Results Test before you trust it.",
      "related": [
        "geo",
        "spa-seo"
      ],
      "see_also": [
        {
          "label": "SEO and performance",
          "url": "https://aatmanjain.com/services/seo-and-performance"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: SEO and search visibility",
          "url": "https://docs.base44.com/Performance-and-SEO/SEO-and-search-visibility"
        },
        {
          "label": "schema.org: DefinedTerm",
          "url": "https://schema.org/DefinedTerm"
        },
        {
          "label": "Google's Rich Results Test",
          "url": "https://search.google.com/test/rich-results"
        }
      ],
      "url": "https://aatmanjain.com/glossary/json-ld",
      "markdown_url": "https://aatmanjain.com/glossary/json-ld.md"
    },
    {
      "slug": "llms-txt",
      "term": "llms.txt",
      "aka": [],
      "category": "seo",
      "short": "A plain Markdown file at the root of a website that tells AI tools what the site is and which pages matter.",
      "body_markdown": "It lives at `yoursite.com/llms.txt`. A title and a short summary, then lists of links with a line about each. Think robots.txt, except it's written for a model to read rather than a rule for a bot to obey.\n\nIs it an official standard? No. It was proposed in 2024, and plenty of AI tools never look for it. I ship one anyway. It's cheap, and it's the one place I get to say exactly how a site should be summarised.\n\nThe Base44 bit: the SEO & GEO page can generate one for you, and it's off by default. If you write your own in `public/`, leave the generator off, because when it's on, the platform's version wins.\n\n[What's BaaS?](https://whatsbaas.base44.app/) has an llms.txt and an llms-full.txt, plus Markdown packs of the whole course, so you can hand the lot to your AI with one link.",
      "body_text": "It lives at yoursite.com/llms.txt. A title and a short summary, then lists of links with a line about each. Think robots.txt, except it's written for a model to read rather than a rule for a bot to obey.\n\nIs it an official standard? No. It was proposed in 2024, and plenty of AI tools never look for it. I ship one anyway. It's cheap, and it's the one place I get to say exactly how a site should be summarised.\n\nThe Base44 bit: the SEO & GEO page can generate one for you, and it's off by default. If you write your own in public/, leave the generator off, because when it's on, the platform's version wins.\n\nWhat's BaaS? has an llms.txt and an llms-full.txt, plus Markdown packs of the whole course, so you can hand the lot to your AI with one link.",
      "related": [
        "geo",
        "spa-seo"
      ],
      "see_also": [
        {
          "label": "SEO and performance",
          "url": "https://aatmanjain.com/services/seo-and-performance"
        },
        {
          "label": "What's BaaS? case study",
          "url": "https://aatmanjain.com/work/whats-baas"
        }
      ],
      "sources": [
        {
          "label": "llmstxt.org",
          "url": "https://llmstxt.org/"
        },
        {
          "label": "Base44 docs: SEO and search visibility",
          "url": "https://docs.base44.com/Performance-and-SEO/SEO-and-search-visibility"
        }
      ],
      "url": "https://aatmanjain.com/glossary/llms-txt",
      "markdown_url": "https://aatmanjain.com/glossary/llms-txt.md"
    },
    {
      "slug": "mcp",
      "term": "MCP",
      "aka": [
        "Model Context Protocol"
      ],
      "category": "ai",
      "short": "Model Context Protocol: an open standard that lets AI assistants plug into outside tools and data through one shared kind of connection.",
      "body_markdown": "Before MCP, every AI tool needed its own glue for every service. Now a service builds one MCP server, and any assistant that speaks MCP can plug into it. Anthropic introduced it in late 2024, and it has spread well beyond Claude since.\n\nA server offers tools (things the AI can do) and resources (things it can read). The assistant decides when to use them.\n\nBase44 runs its own MCP server for creating and managing backend projects, and App MCP lets assistants work with a published app's data.\n\nI use Higgsfield's MCP every day for the images and video in my content pipeline. Once it's connected, I just ask.\n\nOne rule, though. An MCP server acts on your behalf, so only connect ones you trust, and read the permissions before you click allow.",
      "body_text": "Before MCP, every AI tool needed its own glue for every service. Now a service builds one MCP server, and any assistant that speaks MCP can plug into it. Anthropic introduced it in late 2024, and it has spread well beyond Claude since.\n\nA server offers tools (things the AI can do) and resources (things it can read). The assistant decides when to use them.\n\nBase44 runs its own MCP server for creating and managing backend projects, and App MCP lets assistants work with a published app's data.\n\nI use Higgsfield's MCP every day for the images and video in my content pipeline. Once it's connected, I just ask.\n\nOne rule, though. An MCP server acts on your behalf, so only connect ones you trust, and read the permissions before you click allow.",
      "related": [
        "claude-code",
        "agent-skill"
      ],
      "see_also": [
        {
          "label": "What I use",
          "url": "https://aatmanjain.com/uses"
        }
      ],
      "sources": [
        {
          "label": "Model Context Protocol",
          "url": "https://modelcontextprotocol.io/"
        },
        {
          "label": "Base44 docs: MCP server",
          "url": "https://docs.base44.com/developers/backend/overview/mcp-server"
        },
        {
          "label": "Base44 docs: App MCP",
          "url": "https://docs.base44.com/Integrations/app-mcp"
        }
      ],
      "url": "https://aatmanjain.com/glossary/mcp",
      "markdown_url": "https://aatmanjain.com/glossary/mcp.md"
    },
    {
      "slug": "office-hours",
      "term": "Office Hours",
      "aka": [],
      "category": "base44",
      "short": "Base44's live weekly sessions where the Base44 team demos features and answers builders' questions.",
      "body_markdown": "Three sessions a week, all at 5pm UTC: beginners on Tuesday, an open mic on Wednesday and an advanced deep dive on Thursday. Schedules move, so check Base44's Discord before you set an alarm.\n\nThe trouble with live sessions is that the answer you need is always 38 minutes into a recording you'll never find again.\n\nSo I built the [unofficial Office Hours archive](https://unofficial-events.base44.app). Every session up to #70, each with a recap and a searchable bank of the questions people asked. There's also Mini Gingy, a little assistant that knows the archive by heart and will even tell you which sessions never happened.\n\nIt's community-made and unofficial, not a Base44 site, even though it borrows their look.\n\nNew to Base44? Watch a couple of the beginner sessions and every security one. It'll save you a lot of trial and error.",
      "body_text": "Three sessions a week, all at 5pm UTC: beginners on Tuesday, an open mic on Wednesday and an advanced deep dive on Thursday. Schedules move, so check Base44's Discord before you set an alarm.\n\nThe trouble with live sessions is that the answer you need is always 38 minutes into a recording you'll never find again.\n\nSo I built the unofficial Office Hours archive. Every session up to #70, each with a recap and a searchable bank of the questions people asked. There's also Mini Gingy, a little assistant that knows the archive by heart and will even tell you which sessions never happened.\n\nIt's community-made and unofficial, not a Base44 site, even though it borrows their look.\n\nNew to Base44? Watch a couple of the beginner sessions and every security one. It'll save you a lot of trial and error.",
      "related": [
        "row-level-security",
        "security-scan"
      ],
      "see_also": [
        {
          "label": "Office Hours archive case study",
          "url": "https://aatmanjain.com/work/office-hours-archive"
        }
      ],
      "sources": [],
      "url": "https://aatmanjain.com/glossary/office-hours",
      "markdown_url": "https://aatmanjain.com/glossary/office-hours.md"
    },
    {
      "slug": "prompt-engineering",
      "term": "Prompt engineering",
      "aka": [],
      "category": "ai",
      "short": "Writing instructions for an AI model so that it does what you actually meant, reliably.",
      "body_markdown": "It sounds grander than it is. Mostly it's being specific.\n\nThe habit that helped me most with the Base44 builder is using its words. It knows exactly what an \"entity\" and an \"access rule\" are. Say \"database table\" or \"RLS policy\" and you're making it translate, and things get lost in translation.\n\nA few more that earn their place. Say what done looks like. Show it one good example. Say what not to touch, because \"don't change the layout\" saves a lot of undoing. And split big jobs into steps, so it checks before it changes anything.\n\nThat first habit is basically why my open-source [Base44 prompt engineer](https://aatmanjain.com/resources/skills) exists. It's a Claude skill that writes prompts for the Base44 builder in the platform's own terms, so they land first time more often.",
      "body_text": "It sounds grander than it is. Mostly it's being specific.\n\nThe habit that helped me most with the Base44 builder is using its words. It knows exactly what an \"entity\" and an \"access rule\" are. Say \"database table\" or \"RLS policy\" and you're making it translate, and things get lost in translation.\n\nA few more that earn their place. Say what done looks like. Show it one good example. Say what not to touch, because \"don't change the layout\" saves a lot of undoing. And split big jobs into steps, so it checks before it changes anything.\n\nThat first habit is basically why my open-source Base44 prompt engineer exists. It's a Claude skill that writes prompts for the Base44 builder in the platform's own terms, so they land first time more often.",
      "related": [
        "agent-skill",
        "claude-code"
      ],
      "see_also": [
        {
          "label": "Open-source skills",
          "url": "https://aatmanjain.com/resources/skills"
        }
      ],
      "sources": [],
      "url": "https://aatmanjain.com/glossary/prompt-engineering",
      "markdown_url": "https://aatmanjain.com/glossary/prompt-engineering.md"
    },
    {
      "slug": "resend",
      "term": "Resend",
      "aka": [],
      "category": "backend",
      "short": "An email API for developers that sends transactional email, like receipts and password resets, from your own domain.",
      "body_markdown": "Your code calls Resend's API, and the email goes out from an address on your own domain. Receipts, password resets, the boring emails that really do have to arrive.\n\nThe step people skip is DNS. You add a few records that prove you own the domain. Miss one, or typo it, and your mail lands in spam or nowhere. That's the answer to most \"my emails aren't sending\" questions.\n\nOn Base44 you get a choice. Built-in `SendEmail` is fine for low-volume mail to your own users. Reach for Resend when you want richer templates or more volume from your own domain. You can connect it through Base44's Resend integration (Builder plan and up) or call it from a [backend function](https://aatmanjain.com/glossary/backend-function) with the key in [secrets](https://aatmanjain.com/glossary/secrets).\n\nI've got Resend running in production on client apps, next to Stripe.",
      "body_text": "Your code calls Resend's API, and the email goes out from an address on your own domain. Receipts, password resets, the boring emails that really do have to arrive.\n\nThe step people skip is DNS. You add a few records that prove you own the domain. Miss one, or typo it, and your mail lands in spam or nowhere. That's the answer to most \"my emails aren't sending\" questions.\n\nOn Base44 you get a choice. Built-in SendEmail is fine for low-volume mail to your own users. Reach for Resend when you want richer templates or more volume from your own domain. You can connect it through Base44's Resend integration (Builder plan and up) or call it from a backend function with the key in secrets.\n\nI've got Resend running in production on client apps, next to Stripe.",
      "related": [
        "integration",
        "backend-function",
        "secrets"
      ],
      "see_also": [
        {
          "label": "Backend and integrations",
          "url": "https://aatmanjain.com/services/backend-and-integrations"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: Resend integration",
          "url": "https://docs.base44.com/Integrations/Resend-integration"
        },
        {
          "label": "Base44 docs: sending emails",
          "url": "https://docs.base44.com/documentation/building-your-app/sending-emails"
        },
        {
          "label": "Resend docs",
          "url": "https://resend.com/docs"
        }
      ],
      "url": "https://aatmanjain.com/glossary/resend",
      "markdown_url": "https://aatmanjain.com/glossary/resend.md"
    },
    {
      "slug": "row-level-security",
      "term": "Row-level security",
      "aka": [
        "RLS",
        "access rules"
      ],
      "category": "security",
      "short": "Rules on each entity that decide who can create, read, update and delete which rows, checked on the server before any data is sent.",
      "body_markdown": "If a rule says you can only read your own tasks, other people's tasks never reach your browser. They're never sent at all, so there's nothing to dig out of dev tools.\n\nEach Base44 entity gets four rules, one per action:\n\n```json\n\"rls\": {\n  \"create\": true,\n  \"read\": { \"created_by\": \"{{user.email}}\" },\n  \"update\": { \"created_by\": \"{{user.email}}\" },\n  \"delete\": { \"user_condition\": { \"role\": \"admin\" } }\n}\n```\n\nAnyone can add a task. You can read and edit only your own. Only admins can delete.\n\nThe mistake I see most is an entity with no rules at all, which is open to everyone, including visitors who never signed in. Close second: filtering rows on the page and calling it security. By then the data has already arrived.\n\nLesson 7 of [What's BaaS?](https://whatsbaas.base44.app/) is all about this, with a wall of parcel lockers as the picture. It's also where I start every security review.",
      "body_text": "If a rule says you can only read your own tasks, other people's tasks never reach your browser. They're never sent at all, so there's nothing to dig out of dev tools.\n\nEach Base44 entity gets four rules, one per action:\n\njson \"rls\": { \"create\": true, \"read\": { \"created_by\": \"{{user.email}}\" }, \"update\": { \"created_by\": \"{{user.email}}\" }, \"delete\": { \"user_condition\": { \"role\": \"admin\" } } }\n\nAnyone can add a task. You can read and edit only your own. Only admins can delete.\n\nThe mistake I see most is an entity with no rules at all, which is open to everyone, including visitors who never signed in. Close second: filtering rows on the page and calling it security. By then the data has already arrived.\n\nLesson 7 of What's BaaS? is all about this, with a wall of parcel lockers as the picture. It's also where I start every security review.",
      "related": [
        "entity",
        "security-scan",
        "service-role"
      ],
      "see_also": [
        {
          "label": "Security reviews",
          "url": "https://aatmanjain.com/services/security-reviews"
        },
        {
          "label": "What's BaaS? case study",
          "url": "https://aatmanjain.com/work/whats-baas"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: entity security",
          "url": "https://docs.base44.com/developers/backend/resources/entities/security"
        }
      ],
      "url": "https://aatmanjain.com/glossary/row-level-security",
      "markdown_url": "https://aatmanjain.com/glossary/row-level-security.md"
    },
    {
      "slug": "secrets",
      "term": "Secrets",
      "aka": [
        "environment variables"
      ],
      "category": "security",
      "short": "Encrypted settings, like API keys, that only your Base44 backend can read, so they never end up in the browser.",
      "body_markdown": "Anything in your frontend code is public. Open dev tools and it's right there. So an API key sitting in a page component belongs to whoever finds it first.\n\nSecrets live in the dashboard, or `base44 secrets set` if you use the CLI. A backend function reads one like this:\n\n```ts\nconst key = Deno.env.get('STRIPE_SECRET_KEY');\n```\n\nThe pattern never changes. The page calls your function. The function reads the secret, calls the other service and sends back only the result. The key never leaves the server.\n\nIf Base44's security scan flags an exposed secret, moving it isn't enough. Assume it leaked, rotate it with the provider, then store the new one properly.",
      "body_text": "Anything in your frontend code is public. Open dev tools and it's right there. So an API key sitting in a page component belongs to whoever finds it first.\n\nSecrets live in the dashboard, or base44 secrets set if you use the CLI. A backend function reads one like this:\n\nts const key = Deno.env.get('STRIPE_SECRET_KEY');\n\nThe pattern never changes. The page calls your function. The function reads the secret, calls the other service and sends back only the result. The key never leaves the server.\n\nIf Base44's security scan flags an exposed secret, moving it isn't enough. Assume it leaked, rotate it with the provider, then store the new one properly.",
      "related": [
        "backend-function",
        "security-scan",
        "connector"
      ],
      "see_also": [
        {
          "label": "Security reviews",
          "url": "https://aatmanjain.com/services/security-reviews"
        },
        {
          "label": "Backend and integrations",
          "url": "https://aatmanjain.com/services/backend-and-integrations"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: backend functions",
          "url": "https://docs.base44.com/developers/backend/resources/backend-functions/overview"
        },
        {
          "label": "Base44 docs: secrets set",
          "url": "https://docs.base44.com/developers/references/cli/commands/secrets-set"
        }
      ],
      "url": "https://aatmanjain.com/glossary/secrets",
      "markdown_url": "https://aatmanjain.com/glossary/secrets.md"
    },
    {
      "slug": "security-scan",
      "term": "Security scan",
      "aka": [],
      "category": "security",
      "short": "Base44's built-in check for the usual ways apps leak, like open data and exposed API keys, with a suggested fix for each problem it finds.",
      "body_markdown": "Dashboard, then Security, then Run Security Scan. It's free on every plan, so there's no excuse.\n\nIt checks for seven kinds of problem. The big ones for most apps are entities with missing or loose rules, API keys sitting where visitors can find them, backend functions anyone can run, and credit-using features that strangers could call to spend your credits.\n\nFixes are one click, and Base44 makes a checkpoint first so you can roll back. Read before you click, though. The \"anyone can run this function\" fix makes the function require a signed-in user, which will break a webhook or a page for signed-out visitors.\n\nRun it before your first publish, and again whenever you add entities or change rules.\n\nThink of it as the minimum. The scan can't know that your Invoice entity should only be visible to the customer it belongs to. That takes someone who understands the business.",
      "body_text": "Dashboard, then Security, then Run Security Scan. It's free on every plan, so there's no excuse.\n\nIt checks for seven kinds of problem. The big ones for most apps are entities with missing or loose rules, API keys sitting where visitors can find them, backend functions anyone can run, and credit-using features that strangers could call to spend your credits.\n\nFixes are one click, and Base44 makes a checkpoint first so you can roll back. Read before you click, though. The \"anyone can run this function\" fix makes the function require a signed-in user, which will break a webhook or a page for signed-out visitors.\n\nRun it before your first publish, and again whenever you add entities or change rules.\n\nThink of it as the minimum. The scan can't know that your Invoice entity should only be visible to the customer it belongs to. That takes someone who understands the business.",
      "related": [
        "row-level-security",
        "secrets",
        "service-role"
      ],
      "see_also": [
        {
          "label": "Security reviews",
          "url": "https://aatmanjain.com/services/security-reviews"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: running a security scan",
          "url": "https://docs.base44.com/Setting-up-your-app/running-a-security-scan"
        }
      ],
      "url": "https://aatmanjain.com/glossary/security-scan",
      "markdown_url": "https://aatmanjain.com/glossary/security-scan.md"
    },
    {
      "slug": "seeded-randomness",
      "term": "Seeded randomness",
      "aka": [
        "seeded PRNG"
      ],
      "category": "code",
      "short": "Random-looking numbers generated from a starting value, the seed, so the same seed always produces exactly the same sequence.",
      "body_markdown": "`Math.random()` can't be replayed. Whatever it did, it did. A seeded generator (a PRNG, or pseudo-random number generator) takes a seed and produces the same sequence every single time you give it that seed.\n\nThat replay is the feature. In [Hattle](https://aatmanjain.com/work/hattle), each draw got a seed made of football words and four digits, like `frenzy-rocket-4821`. The seed was hashed into a number, a small mulberry32-style generator turned that into a sequence, and the sequence drove the [Fisher-Yates shuffle](https://aatmanjain.com/glossary/fisher-yates-shuffle) that dealt the teams. Paste the seed into the verify page and you get the identical draw. Proof instead of trust.\n\nThe flip side: never use it for anything secret. Tokens and passwords need `crypto.getRandomValues()`, because anyone who knows or guesses the seed can rebuild the whole sequence. Publishing the seed is great for a sweepstake. For a password, it's a disaster.",
      "body_text": "Math.random() can't be replayed. Whatever it did, it did. A seeded generator (a PRNG, or pseudo-random number generator) takes a seed and produces the same sequence every single time you give it that seed.\n\nThat replay is the feature. In Hattle, each draw got a seed made of football words and four digits, like frenzy-rocket-4821. The seed was hashed into a number, a small mulberry32-style generator turned that into a sequence, and the sequence drove the Fisher-Yates shuffle that dealt the teams. Paste the seed into the verify page and you get the identical draw. Proof instead of trust.\n\nThe flip side: never use it for anything secret. Tokens and passwords need crypto.getRandomValues(), because anyone who knows or guesses the seed can rebuild the whole sequence. Publishing the seed is great for a sweepstake. For a password, it's a disaster.",
      "related": [
        "fisher-yates-shuffle"
      ],
      "see_also": [
        {
          "label": "Hattle case study",
          "url": "https://aatmanjain.com/work/hattle"
        }
      ],
      "sources": [
        {
          "label": "MDN: Crypto.getRandomValues()",
          "url": "https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues"
        }
      ],
      "url": "https://aatmanjain.com/glossary/seeded-randomness",
      "markdown_url": "https://aatmanjain.com/glossary/seeded-randomness.md"
    },
    {
      "slug": "service-role",
      "term": "Service role",
      "aka": [
        "asServiceRole"
      ],
      "category": "security",
      "short": "Full data access for trusted Base44 backend code that skips row-level security entirely, so it has to be handled carefully.",
      "body_markdown": "Inside a backend function, `base44.asServiceRole.entities` ignores every access rule you wrote. Scary, and useful.\n\nA real one from my old site: anyone could sign up to the newsletter, but nobody outside could read the subscriber list. Good. Except then how do you stop duplicate sign-ups? The sign-up function used the service role to check the list on the server, without ever opening it to the public.\n\nThe rules I stick to:\n\n- Only in backend functions. It isn't available in the browser, and that's on purpose.\n- Check who's calling before touching anything. That means a signed-in user with the right role, or a verified webhook signature.\n- Validate input yourself, because the access rules aren't there to catch bad data any more.\n- Send back only what the caller needs.\n\nThe security scan flags functions anyone can run. On a function that uses the service role, take that warning seriously.",
      "body_text": "Inside a backend function, base44.asServiceRole.entities ignores every access rule you wrote. Scary, and useful.\n\nA real one from my old site: anyone could sign up to the newsletter, but nobody outside could read the subscriber list. Good. Except then how do you stop duplicate sign-ups? The sign-up function used the service role to check the list on the server, without ever opening it to the public.\n\nThe rules I stick to:\n\nOnly in backend functions. It isn't available in the browser, and that's on purpose. Check who's calling before touching anything. That means a signed-in user with the right role, or a verified webhook signature. Validate input yourself, because the access rules aren't there to catch bad data any more. Send back only what the caller needs.\n\nThe security scan flags functions anyone can run. On a function that uses the service role, take that warning seriously.",
      "related": [
        "backend-function",
        "row-level-security",
        "security-scan"
      ],
      "see_also": [
        {
          "label": "Security reviews",
          "url": "https://aatmanjain.com/services/security-reviews"
        },
        {
          "label": "Backend and integrations",
          "url": "https://aatmanjain.com/services/backend-and-integrations"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: entity security",
          "url": "https://docs.base44.com/developers/backend/resources/entities/security"
        },
        {
          "label": "Base44 docs: backend functions",
          "url": "https://docs.base44.com/developers/backend/resources/backend-functions/overview"
        }
      ],
      "url": "https://aatmanjain.com/glossary/service-role",
      "markdown_url": "https://aatmanjain.com/glossary/service-role.md"
    },
    {
      "slug": "spa-seo",
      "term": "SPA SEO",
      "aka": [
        "single-page app SEO"
      ],
      "category": "seo",
      "short": "The problem of getting search engines and AI crawlers to read a single-page app, which builds its pages in the browser with JavaScript.",
      "body_markdown": "Most Base44 apps are single-page apps, and so is this site. The server sends an almost empty HTML file, then JavaScript builds the page. A crawler that doesn't run JavaScript sees a blank shell.\n\nGoogle runs JavaScript, eventually. Plenty of AI crawlers and link-preview bots don't bother.\n\nThe fix is prerendering: handing crawlers a rendered copy of each page. Base44 says it does exactly this, refreshed when you publish, and falls back to a short summary for pages it hasn't rendered yet.\n\nWhen I checked a few of my own Base44 apps with bot user agents, one got the full page and the others got the summary. So I don't assume any more. I fetch pages as Googlebot and compare.\n\nThe rest is the usual list: a real title, description and canonical on every page, [JSON-LD](https://aatmanjain.com/glossary/json-ld), a sitemap, and a custom domain, because Base44's SEO features are designed around one.",
      "body_text": "Most Base44 apps are single-page apps, and so is this site. The server sends an almost empty HTML file, then JavaScript builds the page. A crawler that doesn't run JavaScript sees a blank shell.\n\nGoogle runs JavaScript, eventually. Plenty of AI crawlers and link-preview bots don't bother.\n\nThe fix is prerendering: handing crawlers a rendered copy of each page. Base44 says it does exactly this, refreshed when you publish, and falls back to a short summary for pages it hasn't rendered yet.\n\nWhen I checked a few of my own Base44 apps with bot user agents, one got the full page and the others got the summary. So I don't assume any more. I fetch pages as Googlebot and compare.\n\nThe rest is the usual list: a real title, description and canonical on every page, JSON-LD, a sitemap, and a custom domain, because Base44's SEO features are designed around one.",
      "related": [
        "json-ld",
        "geo",
        "llms-txt"
      ],
      "see_also": [
        {
          "label": "SEO and performance",
          "url": "https://aatmanjain.com/services/seo-and-performance"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: SEO and search visibility",
          "url": "https://docs.base44.com/Performance-and-SEO/SEO-and-search-visibility"
        }
      ],
      "url": "https://aatmanjain.com/glossary/spa-seo",
      "markdown_url": "https://aatmanjain.com/glossary/spa-seo.md"
    },
    {
      "slug": "stripe-webhook",
      "term": "Stripe webhook",
      "aka": [],
      "category": "backend",
      "short": "A message Stripe sends to your server when something happens in your account, like a payment succeeding or a subscription being cancelled.",
      "body_markdown": "The page a customer lands on after checkout is not proof they paid. They can close the tab early or type that URL in themselves. Subscriptions also change with nobody on your site at all, like a renewal at 3am or a card that expires.\n\nWebhooks are how your app finds out. Stripe posts the event to an endpoint, and your code checks it's genuine before updating anything.\n\nOn Base44 that endpoint is a [backend function](https://aatmanjain.com/glossary/backend-function), since each one gets its own URL. Keep the signing secret in [secrets](https://aatmanjain.com/glossary/secrets) and verify the signature on every request. Otherwise anyone can post a fake \"payment succeeded\".\n\nBase44 workflows can react to app payments now, but the docs say subscription renewals may not trigger one yet. So for subscriptions, the webhook is still your source of truth.\n\nExpect the same event to arrive twice, and test the sad paths, like a failed card.\n\nI've got Stripe payments and subscriptions in production on client apps.",
      "body_text": "The page a customer lands on after checkout is not proof they paid. They can close the tab early or type that URL in themselves. Subscriptions also change with nobody on your site at all, like a renewal at 3am or a card that expires.\n\nWebhooks are how your app finds out. Stripe posts the event to an endpoint, and your code checks it's genuine before updating anything.\n\nOn Base44 that endpoint is a backend function, since each one gets its own URL. Keep the signing secret in secrets and verify the signature on every request. Otherwise anyone can post a fake \"payment succeeded\".\n\nBase44 workflows can react to app payments now, but the docs say subscription renewals may not trigger one yet. So for subscriptions, the webhook is still your source of truth.\n\nExpect the same event to arrive twice, and test the sad paths, like a failed card.\n\nI've got Stripe payments and subscriptions in production on client apps.",
      "related": [
        "backend-function",
        "secrets",
        "workflow"
      ],
      "see_also": [
        {
          "label": "Backend and integrations",
          "url": "https://aatmanjain.com/services/backend-and-integrations"
        }
      ],
      "sources": [
        {
          "label": "Stripe docs: webhooks",
          "url": "https://docs.stripe.com/webhooks"
        },
        {
          "label": "Base44 docs: setting up Stripe",
          "url": "https://docs.base44.com/documentation/setting-up-your-app/setting-up-payments"
        },
        {
          "label": "Base44 docs: creating workflows",
          "url": "https://docs.base44.com/Building-your-app/Creating-workflows"
        }
      ],
      "url": "https://aatmanjain.com/glossary/stripe-webhook",
      "markdown_url": "https://aatmanjain.com/glossary/stripe-webhook.md"
    },
    {
      "slug": "superagent",
      "term": "Superagent",
      "aka": [],
      "category": "ai",
      "short": "A personal AI agent from Base44 that takes real actions for you in the background, across the tools you connect it to.",
      "body_markdown": "The line I use in my guide: a chatbot waits, a Superagent works. A chatbot answers when you ask, then stops. A Superagent runs on a schedule, reacts when something happens in your connected tools, remembers things between conversations and messages you when it's done.\n\nA typical job: every weekday at 8am, scan my inbox and send me a summary of anything urgent on WhatsApp.\n\nI break it into six parts: the Brain, Tasks, Artifacts, Files, Channels, and Settings & Permissions. You don't wire most of that up yourself. Knowing it's there just makes it easier to ask for the right thing. (Base44 keeps adding bits. Superagents can join meetings and browse the web now.)\n\nI wrote the free [Superagents Guide](https://learnsuperagents.base44.app/), with a mascot called Nova, because the same questions kept coming up in the Discord. Every claim in it was checked against Base44's docs.",
      "body_text": "The line I use in my guide: a chatbot waits, a Superagent works. A chatbot answers when you ask, then stops. A Superagent runs on a schedule, reacts when something happens in your connected tools, remembers things between conversations and messages you when it's done.\n\nA typical job: every weekday at 8am, scan my inbox and send me a summary of anything urgent on WhatsApp.\n\nI break it into six parts: the Brain, Tasks, Artifacts, Files, Channels, and Settings & Permissions. You don't wire most of that up yourself. Knowing it's there just makes it easier to ask for the right thing. (Base44 keeps adding bits. Superagents can join meetings and browse the web now.)\n\nI wrote the free Superagents Guide, with a mascot called Nova, because the same questions kept coming up in the Discord. Every claim in it was checked against Base44's docs.",
      "related": [
        "ai-agent",
        "connector",
        "workflow"
      ],
      "see_also": [
        {
          "label": "Superagents Guide case study",
          "url": "https://aatmanjain.com/work/superagents-guide"
        },
        {
          "label": "AI agents and automation",
          "url": "https://aatmanjain.com/services/ai-agents-and-automation"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: building a Superagent",
          "url": "https://docs.base44.com/Getting-Started/superagent"
        }
      ],
      "url": "https://aatmanjain.com/glossary/superagent",
      "markdown_url": "https://aatmanjain.com/glossary/superagent.md"
    },
    {
      "slug": "tauri",
      "term": "Tauri",
      "aka": [],
      "category": "code",
      "short": "A framework for building small desktop apps (and, since Tauri 2, mobile apps) with a web frontend and a Rust backend.",
      "body_markdown": "Electron ships a whole copy of Chromium inside every app. Tauri doesn't. It uses the webview the operating system already has, so apps come out much smaller. You build the interface with whatever web framework you like, and Rust handles the native side, like the system tray and notifications. A lot of that native side comes as plugins.\n\nI'm learning Rust and Windows app development at the moment, and Tauri is a big part of that.\n\nI've opened [PR #3540](https://github.com/tauri-apps/plugins-workspace/pull/3540) on Tauri's plugins-workspace, with a proposed fix for tauri-plugin-notification on Windows. It's still open, so it's a proposal, not a merged fix.\n\nIf you already build for the web, Tauri is a friendly way into desktop apps. And the Rust half is a good excuse to finally learn Rust.",
      "body_text": "Electron ships a whole copy of Chromium inside every app. Tauri doesn't. It uses the webview the operating system already has, so apps come out much smaller. You build the interface with whatever web framework you like, and Rust handles the native side, like the system tray and notifications. A lot of that native side comes as plugins.\n\nI'm learning Rust and Windows app development at the moment, and Tauri is a big part of that.\n\nI've opened PR #3540 on Tauri's plugins-workspace, with a proposed fix for tauri-plugin-notification on Windows. It's still open, so it's a proposal, not a merged fix.\n\nIf you already build for the web, Tauri is a friendly way into desktop apps. And the Rust half is a good excuse to finally learn Rust.",
      "related": [],
      "see_also": [
        {
          "label": "Open source",
          "url": "https://aatmanjain.com/open-source"
        }
      ],
      "sources": [
        {
          "label": "Tauri docs",
          "url": "https://tauri.app/"
        }
      ],
      "url": "https://aatmanjain.com/glossary/tauri",
      "markdown_url": "https://aatmanjain.com/glossary/tauri.md"
    },
    {
      "slug": "workflow",
      "term": "Workflow",
      "aka": [],
      "category": "backend",
      "short": "Multi-step work a Base44 app runs on its own when a trigger fires, like a schedule or a change to your data.",
      "body_markdown": "A workflow is a trigger plus some steps. Something happens, then your app runs a function, waits, checks a condition and goes one way or the other.\n\nMy old site's weekly newsletter ran from a scheduled workflow, every Monday at 8am UTC.\n\nIf you've been around a while you'll know automations. Workflows replace them. Apps created from 6 July 2026 use workflows, older apps may still have automations, and an app has one or the other, never both. Copy an old example with `automations` in a function's config into a workflows app and the deploy fails.\n\nThings worth knowing before they bite:\n\n- Scheduled workflows run at most once every five minutes.\n- Bulk operations like `bulkCreate` don't fire entity triggers.\n- A workflow that updates the record that triggered it can end up in a loop.\n- If one keeps failing, Base44 switches it off and emails you.\n\nYou need the Builder plan or above.",
      "body_text": "A workflow is a trigger plus some steps. Something happens, then your app runs a function, waits, checks a condition and goes one way or the other.\n\nMy old site's weekly newsletter ran from a scheduled workflow, every Monday at 8am UTC.\n\nIf you've been around a while you'll know automations. Workflows replace them. Apps created from 6 July 2026 use workflows, older apps may still have automations, and an app has one or the other, never both. Copy an old example with automations in a function's config into a workflows app and the deploy fails.\n\nThings worth knowing before they bite:\n\nScheduled workflows run at most once every five minutes. Bulk operations like bulkCreate don't fire entity triggers. A workflow that updates the record that triggered it can end up in a loop. If one keeps failing, Base44 switches it off and emails you.\n\nYou need the Builder plan or above.",
      "related": [
        "backend-function",
        "connector",
        "superagent"
      ],
      "see_also": [
        {
          "label": "AI agents and automation",
          "url": "https://aatmanjain.com/services/ai-agents-and-automation"
        },
        {
          "label": "Backend and integrations",
          "url": "https://aatmanjain.com/services/backend-and-integrations"
        }
      ],
      "sources": [
        {
          "label": "Base44 docs: creating workflows",
          "url": "https://docs.base44.com/Building-your-app/Creating-workflows"
        },
        {
          "label": "Base44 docs: automations (older apps)",
          "url": "https://docs.base44.com/Building-your-app/Creating-automations"
        }
      ],
      "url": "https://aatmanjain.com/glossary/workflow",
      "markdown_url": "https://aatmanjain.com/glossary/workflow.md"
    }
  ]
}
