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

# Send a request

> Send opaque payloads to Bitfield targets through the public request function.

<div className="bf-article">
  <p className="bf-lead">
    Use `sendRequestToBitfieldTarget(...)` when app code needs to ask a named Bitfield target to do work.
  </p>

  ## What this is

  A target is a public callable name. Your app sends a payload to that name. Runtime Kit routes the payload to the package code registered for that target. Your app receives reply bytes.

  ```ts theme={null}
  import { sendRequestToBitfieldTarget } from '@bitfield/runtime-kit';
  ```

  ## The mental model

  Your app should not care how the work is performed. It should care about the public action name and the payload it sends.

  If a user clicks a search button, your app can call `product.search`. The package behind that target can change later. Your button can stay the same as long as the action reply shape stays the same.

  That is the value: app code calls the public target name, not the private implementation.

  ## How it works technically

  ```text theme={null}
  app action
    -> sendRequestToBitfieldTarget({ target, payload })
    -> Runtime Kit encodes payload as bytes
    -> named target receives the request
    -> target replies with bytes
    -> app decodes bytes according to the public action reply shape
  ```

  <div className="bf-flow" aria-label="Read versus request split">
    <div className="bf-flow-node">
      <span className="bf-flow-step">read</span>
      <strong>Named data</strong>
      <p>`useBitfieldData(...)` reads values already prepared for the surface.</p>
    </div>

    <div className="bf-flow-arrow" aria-hidden="true">|</div>

    <div className="bf-flow-node">
      <span className="bf-flow-step">act</span>
      <strong>Target request</strong>
      <p>`sendRequestToBitfieldTarget(...)` sends bytes to a named target.</p>
    </div>

    <div className="bf-flow-arrow" aria-hidden="true">→</div>

    <div className="bf-flow-node">
      <span className="bf-flow-step">reply</span>
      <strong>Reply bytes</strong>
      <p>The app decodes only what the action reply shape promises.</p>
    </div>
  </div>

  The request shape is:

  ```ts theme={null}
  type BitfieldTargetRequest = {
    target: string;
    payload?: unknown;
  };

  type BitfieldTargetReply = {
    payload: Uint8Array;
  };
  ```

  For the exact request type, payload conversion table, reply type, cancellation behavior, and invalid examples, read [Runtime Kit API](/reference/runtime-kit-api).

  ## Send JSON-like data

  ```ts theme={null}
  const reply = await sendRequestToBitfieldTarget({
    target: 'product.search',
    payload: { query: 'blue jacket' },
  });

  const text = new TextDecoder().decode(reply.payload);
  const result = JSON.parse(text);
  ```

  Use JSON-like payloads when your action reply shape is ordinary app data.

  ## Send text

  ```ts theme={null}
  await sendRequestToBitfieldTarget({
    target: 'note.analyze',
    payload: 'Look for missing details.',
  });
  ```

  Runtime Kit encodes strings as bytes before sending.

  ## Send bytes

  ```ts theme={null}
  const reply = await sendRequestToBitfieldTarget({
    target: 'image.thumbnail',
    payload: imageBytes,
  });
  ```

  If `payload` is already a `Uint8Array`, Runtime Kit sends those bytes.

  ## Understand payload encoding

  | Payload value                     | Public behavior           |
  | --------------------------------- | ------------------------- |
  | `Uint8Array`                      | Sent as bytes.            |
  | `string`                          | Encoded as text bytes.    |
  | object, array, number, or boolean | Serialized as JSON bytes. |
  | `null` or omitted payload         | Sent as empty bytes.      |

  The target decides what those bytes mean. Your app and target should agree on a public contract, such as JSON in and JSON out.

  If a target needs JSON with a `null` value, send an object that contains the null field:

  ```ts theme={null}
  await sendRequestToBitfieldTarget({
    target: 'settings.update',
    payload: { value: null },
  });
  ```

  ## Decode the reply

  The reply is always bytes because targets are not forced into one format.

  ```ts theme={null}
  const text = new TextDecoder().decode(reply.payload);
  ```

  For JSON replies:

  ```ts theme={null}
  const data = JSON.parse(new TextDecoder().decode(reply.payload));
  ```

  For binary replies, keep the `Uint8Array`:

  ```ts theme={null}
  const thumbnailBytes = reply.payload;
  ```

  ## Cancel a request

  Use an `AbortController` when the user leaves a screen, changes a search query, or cancels work.

  ```ts theme={null}
  let currentSearch: AbortController | null = null;

  async function runSearch(query: string) {
    currentSearch?.abort();
    currentSearch = new AbortController();

    return sendRequestToBitfieldTarget(
      { target: 'product.search', payload: { query } },
      currentSearch.signal,
    );
  }
  ```

  ## Handle errors

  Wrap user-triggered requests so the UI can recover.

  ```ts theme={null}
  try {
    const reply = await sendRequestToBitfieldTarget({
      target: 'product.search',
      payload: { query },
    });
    return JSON.parse(new TextDecoder().decode(reply.payload));
  } catch (error) {
    return { error: error instanceof Error ? error.message : 'Request failed' };
  }
  ```

  An error means the public request did not complete. It does not mean the component should learn the private target implementation.

  ## Before / after

  | Before                                               | After                                               |
  | ---------------------------------------------------- | --------------------------------------------------- |
  | Button imports the feature implementation directly.  | Button calls a named target.                        |
  | Replacing the feature requires rewiring app imports. | Replacing the package keeps the target name stable. |
  | App code imports implementation details.             | App code uses only the payload and reply contract.  |
  | A caller has to understand private execution code.   | A caller sends a request to a public target name.   |

  ## Common mistakes

  **Using target names as storage addresses**

  `product.search` is a callable target name. It is not a storage path.

  **Parsing every reply the same way**

  The reply is bytes. Decode according to the action reply shape. Do not assume every target returns JSON unless that target says it does.

  **Calling private implementation code directly**

  If a component imports the target implementation, the request boundary has been bypassed.

  **Forgetting cancellation**

  Search boxes, typeahead, and long tasks should use an abort signal so stale work can stop.

  ## Quick reference

  ```ts theme={null}
  const reply = await sendRequestToBitfieldTarget({
    target: 'target.name',
    payload: { ok: true },
  });
  ```

  With cancellation:

  ```ts theme={null}
  const controller = new AbortController();
  await sendRequestToBitfieldTarget(request, controller.signal);
  ```

  Decode JSON:

  ```ts theme={null}
  const json = JSON.parse(new TextDecoder().decode(reply.payload));
  ```

  ## Now build the bigger version

  Write the action reply shape before the button.

  ```ts theme={null}
  type SearchRequest = {
    query: string;
  };

  type SearchReply = {
    items: Array<{ id: string; name: string }>;
  };
  ```

  Then make the button use only that contract:

  ```ts theme={null}
  async function search(query: string): Promise<SearchReply> {
    const reply = await sendRequestToBitfieldTarget({
      target: 'product.search',
      payload: { query } satisfies SearchRequest,
    });

    return JSON.parse(new TextDecoder().decode(reply.payload)) as SearchReply;
  }
  ```

  For binary work, keep the same shape. The target name stays public, the payload contract stays explicit, and the reply stays bytes:

  ```ts theme={null}
  const reply = await sendRequestToBitfieldTarget({
    target: 'image.thumbnail',
    payload: imageBytes,
  });

  const thumbnailBytes = reply.payload;
  ```
</div>
