External Data and Environment Variables
By Syncfusion®, Inc. · Last updated
Fetching data in the component script with top-level await, handling fetch errors, headless CMS, custom content loaders, databases overview, .env files, import.meta.env, PUBLIC_ variables, built-in variables and type-safe astro:env with envField.
External Data
- So far, all of Asha's data has lived in her project: Markdown files and JSON files.
- Real sites often get data from somewhere else: an API, a CMS, or a database.
- API = a web address that returns data (usually JSON) instead of a page.
- In Astro, the component script can
fetch()that data while the site is built. - In this lesson, you will load data from an API and keep secret keys safe.
Simple lesson: the component script can fetch data at build time, and the page ships the result as HTML.
A Real-World Example
- Asha keeps her reading list in an online app. It has an API.
- She wants her site's "Now reading" box to show the list.
- She does not want readers' browsers to call the API on every visit.
- So her page fetches the list once, at build time. Readers get plain HTML.
Press ▶ Run. The Terminal shows the API call made during the build.
What happened:
await fetch(...)called the API while the page was built.response.json()turned the reply into a JavaScript list.- The HTML sent to browser has the finished list. It has no
fetchand no script.
In this lab, https://api.devjournal.dev is a pretend API that runs inside the preview. It has /posts, /posts/:id, /books and /weather. On your computer, fetch() calls the real internet.
What You Will Learn
In this lesson, you will learn:
- How to fetch data in the component script with
await. - How to handle a failed fetch.
- What a headless CMS is.
- How to load API data into a collection with a custom loader.
- Where databases and backend services fit in.
- How
.envfiles andimport.meta.envwork. - Why
PUBLIC_variables are different. - How
astro:envchecks your variables for you.
Fetching Data
fetch() in the Component Script
fetch()is the standard way to call a web address from JavaScript.- In the component script, it runs on your computer during the build, not in the reader's browser.
- So it can call APIs that need a secret key. The key never reaches the browser.
Top-Level await
- await = "wait for this result before going on".
- In the component script, you can use
awaitdirectly, at the top level. Noasyncfunction is needed. - Astro waits for the data before it builds the HTML.
Fetching from a Remote API
A typical pattern:
const res = await fetch(url);const data = await res.json();- Use
datain the template.
- In a static site, this happens once per build. To show new data, you build again.
Animation: during the build, the page asks the API for data once. The finished HTML then goes to many readers, who never call the API.
Build time (once) Every visit
API
index.astro await fetch()
HTML
👩 Reader👨 Reader🧑 Reader
One API call per build. Zero API calls per reader.
The data is fetched once. Readers only download HTML.
Handling Fetch Errors
- An API can be down, slow, or return "404 Not Found".
fetch()does not throw for a 404 or 500 status. Checkres.okyourself.res.okistruefor status 200 to 299.- Wrap the call in
try/catchfor network errors, and show a friendly fallback. - Decide on purpose: should a broken API stop the build, or should the page show a fallback?
Press ▶ Run. The post with ID 99 does not exist. The page shows a fallback instead of breaking.
Simple lesson: check res.ok, catch errors, and choose a fallback on purpose.
Headless CMS and Custom Loaders
Headless CMS Overview
- CMS (content management system) = an app where people write and edit content in a web form.
- Headless CMS = a CMS with no website of its own. It only offers the content through an API.
- Astro builds the website. The CMS holds the content. Examples: Contentful, Sanity, Storyblok, Strapi, WordPress (as an API).
- Many CMS tools have an official Astro guide or loader.
The Content Loader API
- A collection's
loadercan be your own object, not justglob()orfile(). - Loader object =
{ name, load }.load()gets astoreand adds entries withstore.set({ id, data }). - Your entries then get the same schema check,
getCollection()and types as file entries.
Loading Remote Data into a Collection
Press ▶ Run. The posts collection is loaded from the API by a custom loader, then checked by a schema.
- On your computer, Astro also caches loader results between builds. A real loader can save time by skipping unchanged items.
- This is an advanced topic. For most sites,
glob(),file()or a ready-made CMS loader is enough.
Simple lesson: a custom loader puts API data into a collection, so it gets the same checks and tools as files.
Databases and Backend Services
Querying a Database in Server Code
- A database stores data in tables you can search and update, such as users or orders.
- In Astro, you query a database from server code: the component script, endpoints or actions.
- For a static site, a query runs once, at build time. Pages that need fresh data on every visit are built on request. You will learn that in On-Demand and Mixed Rendering.
Using node:sqlite or Drizzle ORM
node:sqlite= a small database built into Node.js. Good for learning and small sites.- ORM (object-relational mapper) = a library that lets you query a database with JavaScript instead of SQL text.
- Drizzle ORM = a popular, typed ORM. It works with SQLite, PostgreSQL and MySQL.
Why Astro DB Is No Longer Available
- Older guides mention Astro DB, an Astro database add-on.
- It was removed in Astro 7. Use Drizzle,
node:sqliteor a database service instead.
Backend Services Overview
- Backend service = a hosted product that gives you a database, logins and file storage through an API.
- Examples: Supabase (PostgreSQL), Firebase (Google), Turso (SQLite in the cloud).
- You call them from server code, with a secret key kept in an environment variable.
Simple lesson: query databases from server code. Astro DB is gone, so use Drizzle, SQLite or a backend service.
Environment Variables
.env Files
- Environment variable = a named setting that lives outside your code, such as an API address or a secret key.
- Put them in a file called
.envat the top of the project:API_KEY=abc123. - Add
.envto.gitignore, so secrets never go into Git. A new Astro project already does this.
import.meta.env
- Read a variable in server code with
import.meta.env.API_KEY. - In the component script, every variable from
.envis available.
Public Variables (PUBLIC_ Prefix)
- Browser scripts can only read variables whose names start with
PUBLIC_. - Astro writes their values into the browser JavaScript at build time.
- Every other variable becomes
undefinedin the browser. This keeps secrets safe.
Built-In Variables
Astro always sets these:
import.meta.env.MODE="development"or"production".import.meta.env.DEVandimport.meta.env.PROD=trueorfalse.import.meta.env.SITE= thesitefrom your config.import.meta.env.BASE_URL= thebasefrom your config, or/.
Press ▶ Run. The component script can read both variables. The browser script only gets the PUBLIC_ one.
- In the browser,
API_KEYisundefined. Open HTML sent to browser: the secret is not in it.
[TS] Typing Environment Variables in env.d.ts
- Describe your variables in
src/env.d.ts, so the editor knows their names:
interface ImportMetaEnv {
readonly PUBLIC_SITE_NAME: string;
readonly API_KEY: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
- A better way, with checks too, is
astro:envbelow.
Never write a secret into a PUBLIC_ variable, a <script> or a template. Anything that reaches the browser can be read by anyone.
Simple lesson: secrets in .env without PUBLIC_. Only PUBLIC_ variables reach the browser.
Type-Safe Environment Variables
astro:env and the env.schema Option
import.meta.envdoes not check anything. A missing key is justundefined.astro:envlets you describe every variable in the config. Astro checks them before it builds.- Describe them in
env.schema, usingenvFieldfromastro/config.
envField
envField.string({...}),envField.number({...}),envField.boolean({...})andenvField.enum({...})describe one variable.- Options:
context,access,optionalanddefault.
Client and Server Variables
context: "client"= may be used in browser code. Must beaccess: "public".context: "server"= only in server code.
Public and Secret Variables
access: "public"= the value may be written into the built files.access: "secret"= the value is never written into the built files.
Using getSecret()
- Import server variables from
astro:env/server, and client variables fromastro:env/client. getSecret("NAME")reads a secret by name, even one not in the schema.
Press ▶ Run. Then delete the API_URL line from .env and run again. The build stops with a clear message.
Use astro:env in real projects. A missing or wrong variable stops the build on your computer, instead of breaking the live site.
Simple lesson: describe variables with envField. Astro checks them and keeps secrets out of the browser.
TL;DR
- fetch() in the script = runs at build time. Use top-level
await. - Errors = check
res.ok, usetry/catch, show a fallback on purpose. - Headless CMS = content through an API. Astro builds the site.
- Custom loader =
{ name, load({ store }) }withstore.set({ id, data }). - Databases = from server code. Drizzle,
node:sqliteor a backend service. Astro DB was removed. - .env = variables outside the code. Keep it out of Git.
- import.meta.env.NAME = read a variable. Only
PUBLIC_names reach the browser. - Built-ins =
MODE,DEV,PROD,SITE,BASE_URL. - astro:env =
env.schemawithenvField. Import fromastro:env/serverorastro:env/client.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
Not checking res.ok | fetch() throws on a 404 | Check res.ok | The page shows an error object as data |
| Expecting fresh data on a static page | Each visit fetches again | Static pages fetch once per build | Readers see old data until the next build |
A secret in a PUBLIC_ variable | The prefix is just a name | PUBLIC_ values go into browser files | Anyone can read the secret |
Reading API_KEY in a <script> | All variables reach the browser | Only PUBLIC_ ones do | The value is undefined |
Committing .env to Git | Git is private | Keep .env in .gitignore | Secrets leak with the code |
| Following an Astro DB guide | Astro DB still exists | Use Drizzle, SQLite or a service | Install and build errors |
Check Yourself
Exercises
What to Remember
- Fetch data in the component script with
await. Static pages fetch once per build. - Check
res.ok, catch errors, and show a fallback. - Custom loaders put API data into collections.
- Keep secrets in
.env. OnlyPUBLIC_variables reach the browser. astro:envchecks every variable before the build.
Checkpoint: Asha's site is now a complete static blog. It has:
- Markdown posts in a checked collection,
- a page per post, pagination and tag pages,
- optimised images and a custom font,
- data from an API.
Next: How Astro Renders Pages, where you learn exactly when and where your pages are built.