Skip to main content

Resource()

Defines a custom resource provider that handles the complete lifecycle (create, update, delete) of a cloud resource. Resources are the building blocks of Alchemy applications.

Signature

function Resource<Type extends string, Handler extends ResourceLifecycleHandler>(
  type: Type,
  handler: Handler
): ResourceProvider<Type, Handler>

function Resource<Type extends string, Handler extends ResourceLifecycleHandler>(
  type: Type,
  options: Partial<ProviderOptions>,
  handler: Handler
): ResourceProvider<Type, Handler>

Parameters

type
string
required
The unique type identifier for this resource, conventionally in the format "provider::ResourceName" (e.g., "cloudflare::Worker", "aws::S3Bucket").
options
ProviderOptions
Optional configuration for the resource provider.
options.alwaysUpdate
boolean
default:"false"
If true, the resource will be updated even if the inputs have not changed.
options.destroyStrategy
'sequential' | 'parallel'
default:"'sequential'"
The strategy to use when destroying this resource and its dependencies.
handler
ResourceLifecycleHandler
required
The async function that manages the resource lifecycle. Receives a Context object as this, along with id and props parameters.
type ResourceLifecycleHandler = (
  this: Context<Output, Props>,
  id: string,
  props: Props
) => Promise<Output>

Returns

ResourceProvider
function
A callable function that creates instances of the resource. When called with an id and props, it returns a Promise that resolves to the resource output.
const MyResource = Resource("provider::MyResource", handler);
const instance = await MyResource("my-id", { prop: "value" });

Context API

The resource handler function receives a Context object as this with the following properties and methods:
this.phase
'create' | 'update' | 'delete'
The current lifecycle phase of the resource.
this.output
Output | undefined
The previous output of the resource (undefined during creation).
this.props
Props | undefined
The previous props of the resource (undefined during creation).
this.scope
Scope
The current scope containing this resource.
this.id
string
The resource identifier.
this.fqn
string
The fully qualified name of the resource.
this.destroy()
function
Terminates the resource lifecycle and marks it for deletion. Used in the delete phase.
if (this.phase === "delete") {
  await api.deleteResource(this.output.id);
  return this.destroy();
}
this.replace()
function
Signals that the resource should be replaced (deleted and recreated). Used when an immutable property changes.
if (this.output.name !== props.name) {
  return this.replace(); // Triggers delete → create
}
this.get(key)
function
Retrieves a value from the resource’s internal state storage.
this.set(key, value)
function
Stores a value in the resource’s internal state storage.
this.onCleanup(fn)
function
Registers a cleanup function to run when the process exits.
const proc = spawn('my-command');
this.onCleanup(async () => {
  proc.kill();
  await waitForExit(proc);
});

Examples

Basic Resource Provider

import { Resource, Context } from "alchemy";

export interface MyResourceProps {
  name?: string;
  region: string;
}

export interface MyResource {
  id: string;
  name: string;
  region: string;
  resourceId: string;
}

export const MyResource = Resource(
  "provider::MyResource",
  async function (
    this: Context<MyResource>,
    id: string,
    props: MyResourceProps
  ): Promise<MyResource> {
    const name = props.name ?? this.scope.createPhysicalName(id);

    if (this.phase === "delete") {
      if (this.output?.resourceId) {
        await api.delete(`/resources/${this.output.resourceId}`);
      }
      return this.destroy();
    }

    // Create or update the resource
    const result = this.output?.resourceId
      ? await api.put(`/resources/${this.output.resourceId}`, { name, region: props.region })
      : await api.post("/resources", { name, region: props.region });

    return {
      id,
      name: result.name,
      region: props.region,
      resourceId: result.id
    };
  }
);

Resource with Secret Handling

import { Resource, Context, Secret } from "alchemy";

export interface DatabaseProps {
  name?: string;
  password: string | Secret;
}

export interface Database {
  id: string;
  name: string;
  connectionString: string;
  password: Secret;
}

export const Database = Resource(
  "provider::Database",
  async function (
    this: Context<Database>,
    id: string,
    props: DatabaseProps
  ): Promise<Database> {
    const name = props.name ?? this.scope.createPhysicalName(id);

    if (this.phase === "delete") {
      if (this.output?.name) {
        await api.deleteDatabase(this.output.name);
      }
      return this.destroy();
    }

    // Unwrap secret for API call
    const result = await api.createOrUpdateDatabase({
      name,
      password: Secret.unwrap(props.password)
    });

    // Wrap secret in output
    return {
      id,
      name: result.name,
      connectionString: result.connectionString,
      password: Secret.wrap(props.password)
    };
  }
);

// Usage
const db = await Database("my-db", {
  password: alchemy.secret(process.env.DB_PASSWORD)
});

Resource with Adoption

export const MyResource = Resource(
  "provider::MyResource",
  async function (
    this: Context<MyResource>,
    id: string,
    props: MyResourceProps & { adopt?: boolean }
  ): Promise<MyResource> {
    const name = props.name ?? this.scope.createPhysicalName(id);
    const adopt = props.adopt ?? this.scope.adopt;

    if (this.phase === "delete") {
      if (this.output?.resourceId) {
        await api.delete(`/resources/${this.output.resourceId}`);
      }
      return this.destroy();
    }

    let result;
    if (this.output?.resourceId) {
      // Update existing resource
      result = await api.put(`/resources/${this.output.resourceId}`, { name });
    } else {
      try {
        // Try to create new resource
        result = await api.post("/resources", { name });
      } catch (error) {
        if (error.code === "ALREADY_EXISTS" && adopt) {
          // Adopt existing resource
          const existing = await api.findByName(name);
          result = await api.put(`/resources/${existing.id}`, { name });
        } else {
          throw error;
        }
      }
    }

    return {
      id,
      name: result.name,
      resourceId: result.id
    };
  }
);

Resource with Immutable Properties

export const ImmutableResource = Resource(
  "provider::ImmutableResource",
  async function (
    this: Context<ImmutableResource>,
    id: string,
    props: ImmutableResourceProps
  ): Promise<ImmutableResource> {
    const name = props.name ?? this.scope.createPhysicalName(id);

    if (this.phase === "delete") {
      if (this.output?.resourceId) {
        await api.delete(`/resources/${this.output.resourceId}`);
      }
      return this.destroy();
    }

    // Check if immutable property changed
    if (this.phase === "update" && this.output.region !== props.region) {
      return this.replace(); // Triggers delete → create
    }

    const result = this.output?.resourceId
      ? await api.put(`/resources/${this.output.resourceId}`, { name })
      : await api.post("/resources", { name, region: props.region });

    return {
      id,
      name: result.name,
      region: props.region,
      resourceId: result.id
    };
  }
);

Build docs developers (and LLMs) love