> ## Documentation Index
> Fetch the complete documentation index at: https://photonhealth.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript client

> @photon-rx/core: the client, sign-in and workflow rules under Prescribe, for any framework or Node.

`@photon-rx/core` sits underneath Prescribe. It handles the prescriber's sign-in and tokens, calls the API, and enforces the workflow rules: which drug was meant, when a draft is created, and when an order is ready to send. Use it when you're building your own UI in another framework, or when your backend wants typed calls.

It has no dependencies and runs in browsers and Node.

<Note>
  Building your own prescribing UI on the client, with your own screens for drafting, signing and sending, is part of the [Grow plan](/docs/use-cases#platforms-and-marketplaces). On Launch, embed [Prescribe](/docs/prescribe/embed) for the prescriber, and use the client to build links and make typed calls.
</Note>

```bash theme={"dark"}
npm install @photon-rx/core
```

⚠️ Not published to npm yet.

## Create a client

```ts theme={"dark"}
import { photon } from "@photon-rx/core";

// In a browser: Photon signs the prescriber in
const ph = photon({ environment: "neutron", auth: { clientId: "YOUR_CLIENT_ID" } });

// Or, if you already sign them in with Auth0:
// photon({ environment: "neutron", auth: { getToken: () => auth0.getTokenSilently() } });
```

<Card title="Try it" icon="play" href="https://rx.neutron.health/integrate/order/js" horizontal arrow>
  The live objects in /integrate, drawn with no framework: sync, sign and send, beside their code.
</Card>

With `clientId`, `ph.session` handles sign-in: `start()` on load, `signIn()` from a click, and `signOut()`. Its `status` and `user` show who is signed in. Create one client per signed-in person. On a server, that means one per request, never one shared module-wide.

<Note>
  With `clientId`, the access token stays in memory and the refresh token is kept in the page's `localStorage`, so a reload signs back in. Any script on the page can read it, so treat script injection there as a token leak, and set a Content-Security-Policy. Auth0 rotates the refresh token on every use and revokes it as soon as an old one is reused. [How Prescribe is built](/docs/prescribe/architecture#sign-in) has the details.
</Note>

## Get, sync, update

`ph.patient`, `ph.prescription` and `ph.order` each have the [same three calls](/docs/architecture#get-sync-and-update):

```ts theme={"dark"}
const existing = await ph.patient.get("pat_01M3WQQHRB1T0K1469KD18BAWX"); // read, change nothing

const { patient, candidates, changes } = await ph.patient.sync({
  demographic: { name: { first: "Paige", last: "Turner" }, dateOfBirth: "1990-01-01", sex: "FEMALE" },
}); // find, match or add

await ph.patient.update(patient.id, { demographic: { phone: "+13175550142" } }); // just these fields
```

`sync` and `update` return the live object, any `candidates` when Photon isn't sure which record you meant, and the `changes` it reported. The client never picks a candidate for you. Pick one with `patient.pick(id)`.

## Live objects

Each record is a live object. `state` is the record, and `status` is `idle`, `loading`, `saving` or `error`. `subscribe` calls you on every change, and every method waits for the call it makes. There's one object per record per client, so a patient opened twice is the same object.

```ts theme={"dark"}
const { prescription: rx } = await ph.prescription.sync({
  patient: { id: patient.id },
  templateId: "YOUR_TEMPLATE_ID",
});

rx.subscribe(() => render(rx.state)); // re-render on every change

await rx.update({ instructions: "Take 1 tablet by mouth daily", dispense: { quantity: 30, daysSupply: 30 } });
await rx.sign(); // the prescriber's approval of exactly what they saw

const { order } = await ph.order.sync({ patient: { id: patient.id }, prescriptions: { add: [{ id: rx.id }] } });
await order.submit(); // the patient chooses a pharmacy
```

The rules live in the objects, so every UI built on them behaves the same:

* A new prescription without a patient only looks the drug up and never creates anything.
* The newest response always wins, so a slow early reply can't overwrite a later one.
* `rx.sign()` waits for saves in flight, then signs only what's stored.
* `order.readyToSend` is true once the patient is found, every prescription is signed, and nothing is still saving.
* `order.submit()` refuses until it's ready, and never sets a pharmacy.

## Choices

When Photon offers options, such as which of several drugs a name meant, `choicesIn(obj.changes)` lists them. `obj.choose(option)` applies one.

```ts theme={"dark"}
import { choicesIn } from "@photon-rx/core";

const [which] = choicesIn(rx.changes); // e.g. "which lisinopril?"
if (which) await rx.choose(which.options[0]); // or let the prescriber pick
```

A drug option carries its `treatment` in parts, so you can group drugs by `brandName`, then `form`, then `strength` without parsing `displayName`. It also has `genericName`, `route`, `schedule` (the DEA schedule) and `recommendedDispenseUnits`, the values `dispense.unit` takes. Once picked, the same parts are `rx.treatment`:

```ts theme={"dark"}
for (const option of which?.options ?? []) {
  const { brandName, form, strength } = option.treatment ?? {}; // "Lisinopril", "Tablet", "10 MG"
}
// After rx.choose(option):
rx.treatment?.recommendedDispenseUnits; // ["Bottle", "Tablet"]: offer these for dispense.unit
```

## Errors

Anything Photon answers about your data comes back as a result. Only failures that different data can't fix throw a `PhotonError`, and its `kind` says which:

| `kind` | Meaning |
| - | - |
| `network` | Couldn't reach Photon. Retry. |
| `unauthorized` | The token was refused. Sign in again. |
| `upstream` | A Photon service failed. `retryable` says whether to retry. |
| `invalid` | Photon rejected the request's shape. This is a bug. |
| `refused` | A workflow rule said no, for example sending before everything is signed. |

## Links

`url()` builds a [deep link](/docs/prescribe/app/deep-links) and `frame()` builds an [iframe](/docs/prescribe/embed/iframe) URL. Each record's calls have both, and so does every live object:

```ts theme={"dark"}
ph.patient.url("pat_01M3WQQHRB1T0K1469KD18BAWX"); // https://rx.neutron.health/patient/pat_01M3WQQHRB1T0K1469KD18BAWX
ph.order.frame({ patient: { id: "pat_01M3WQQHRB1T0K1469KD18BAWX" } }); // https://rx.neutron.health/order/embed?patient=pat_01M3WQQHRB1T0K1469KD18BAWX&client=YOUR_CLIENT_ID
order.url(); // a live order you already have
ph.url.parse(href); // what a link opens, read back
```

* Links carry Photon ids only (`pat_`, `rx_`, `ord_`, `med_`, `tmp_`, `org_`), in the clear. `url()` and `frame()` throw `invalid` for anything else, such as patient details or a drug's name. Sync those first, then link by id. See [deep links](/docs/prescribe/app/deep-links) for every parameter.
* A live object's link is its record's id. Before it has a record, the link uses the ids the object has: an order's patient and lines, or a prescription's patient and its template or drug. A patient with no record has no link, so `url()` throws.
* Links point to the environment's Prescribe app: `rx.neutron.health` in the sandbox, or `rx.new` in production. Pass `appUrl` to `photon()` to point them somewhere else.
* `url()` is for sharing, so it never carries a `client_id`. The Prescribe app signs the prescriber in on its own.
* `frame()` puts your Application `client_id` in the URL, from `auth: { clientId }`, and throws without one. With `auth: { getToken }`, pass it in the link: `ph.frame({ flow, …, auth: { clientId } })`.
* Pass `organization` to `photon()`, and every link carries it, so the prescriber signs straight in to that organization.

## Secrets

A client secret never belongs in a browser. If you pass a `clientSecret` to `photon()` in a page, it throws before doing anything else. Treat that secret as leaked and rotate it. Links never carry a secret.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.