diff --git a/.build/check-broken-links.sh b/.build/check-broken-links.sh
index 6df4e2df..e3be0786 100755
--- a/.build/check-broken-links.sh
+++ b/.build/check-broken-links.sh
@@ -90,6 +90,9 @@ if ! blc -rv
--exclude 'https://marketplace.visualstudio.com/items?itemName=BytecodeAlliance.starlingmonkey-debugger' \
--exclude 'http://localhost:16686/' \
--exclude 'http://localhost:5050/explore' \
+ `## Spin 4 bits not yet published` \
+ --exclude 'https://github.com/spinframework/spin-docs/blob/main/content/v4' \
+ --exclude 'https://docs.rs/spin-sdk/latest/spin_sdk' \
http://127.0.0.1:3000/v3/javascript-components \
| tee "${report}"
then
diff --git a/content/blog/announcing-spin-3-5.md b/content/blog/announcing-spin-3-5.md
index 48aec9bd..4fcbf4a9 100644
--- a/content/blog/announcing-spin-3-5.md
+++ b/content/blog/announcing-spin-3-5.md
@@ -54,7 +54,7 @@ One thing you’ll notice the moment you try out the new SDK is that you’re us
-But that’s not where the interoperability story ends. Because of the design of the SDK in conjunction with the async support provided by WASIp3, it is now more seamless to bring your own web frameworks to use in Spin components. See for instance the Axum example [here](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/wasip3-http-axum-router). Historically, this was possible to do but required management of more lower level types. See the Axum example with WASIp2 [here](https://github.com/fermyon/wasi-hyperium/blob/main/examples/axum-server/src/lib.rs) for comparison. We’d love to hear more feedback on this experience through [GitHub issues](https://github.com/spinframework/spin-rust-sdk/issues), on [Slack](https://cloud-native.slack.com/archives/C089NJ9G1V0), or during [project meetings](https://github.com/spinframework/spin#getting-involved-and-contributing).
+But that’s not where the interoperability story ends. Because of the design of the SDK in conjunction with the async support provided by WASIp3, it is now more seamless to bring your own web frameworks to use in Spin components. See for instance the Axum example [here](https://github.com/spinframework/spin-rust-sdk/tree/v5.2.0/examples/wasip3-http-axum-router). Historically, this was possible to do but required management of more lower level types. See the Axum example with WASIp2 [here](https://github.com/fermyon/wasi-hyperium/blob/main/examples/axum-server/src/lib.rs) for comparison. We’d love to hear more feedback on this experience through [GitHub issues](https://github.com/spinframework/spin-rust-sdk/issues), on [Slack](https://cloud-native.slack.com/archives/C089NJ9G1V0), or during [project meetings](https://github.com/spinframework/spin#getting-involved-and-contributing).
#### More on Language Support
@@ -84,7 +84,7 @@ You should now see a `hello-p3` directory containing a basic p3 application usin
### Deep Dive Into Example
-The Rust SDK repo provides examples of things you can now do with p3 support. Let’s dive into one of them. With WASIp3 support in Spin, you can make concurrent outbound HTTP requests from within a component. See full example [here](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/wasip3-concurrent-outbound-http-calls).
+The Rust SDK repo provides examples of things you can now do with p3 support. Let’s dive into one of them. With WASIp3 support in Spin, you can make concurrent outbound HTTP requests from within a component. See full example [here](https://github.com/spinframework/spin-rust-sdk/tree/v5.2.0/examples/wasip3-concurrent-outbound-http-calls).
The first step of using the SDK is opting into the `wasip3-unstable` feature as is done on the last line of the `Cargo.toml` file above. Also note Rust 1.90 and the `wasm32-wasip2` target are required to build WASIp3 components. (WASIp2 and p3 have the same binary representation, with p3 adding additional features. The `wasm32-wasip2` target causes the Rust compiler to emit the right binary format, with the SDK adding the additional p3 features.)
diff --git a/content/v2/http-outbound.md b/content/v2/http-outbound.md
index 2009aa8c..7ea8c9d8 100644
--- a/content/v2/http-outbound.md
+++ b/content/v2/http-outbound.md
@@ -88,7 +88,7 @@ async fn handle_request(_req: Request) -> anyhow::Result {
}
```
-For an example of receiving the response in a streaming style, [see this example in the Spin repository](https://github.com/spinframework/spin-rust-sdk/blob/main/examples/wasi-http-streaming-outgoing-body/src/lib.rs).
+For an example of receiving the response in a streaming style, [see this example in the Spin repository](https://github.com/spinframework/spin-rust-sdk/tree/v5.2.0/examples/wasi-http-streaming-outgoing-body/src/lib.rs).
{{ blockEnd }}
diff --git a/content/v2/rust-components.md b/content/v2/rust-components.md
index ce8250e3..19bcfa67 100644
--- a/content/v2/rust-components.md
+++ b/content/v2/rust-components.md
@@ -295,7 +295,7 @@ proxies or URL shorteners.
-The Rust SDK [provides a router](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/http-router) that makes it easier to handle routing within a component:
+The Rust SDK [provides a router](https://github.com/spinframework/spin-rust-sdk/tree/v5.2.0/examples/http-router) that makes it easier to handle routing within a component:
```rust
use anyhow::Result;
diff --git a/content/v3/http-outbound.md b/content/v3/http-outbound.md
index f4951426..adec3c6e 100644
--- a/content/v3/http-outbound.md
+++ b/content/v3/http-outbound.md
@@ -28,18 +28,18 @@ The outbound HTTP interface depends on your language.
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/http/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/index.html)
-To send requests, use the [`spin_sdk::http::send`](https://docs.rs/spin-sdk/latest/spin_sdk/http/fn.send.html) function. This takes a request argument and returns a response (or error). It is `async`, so within an async inbound handler you can have multiple outbound `send`s running concurrently.
+To send requests, use the [`spin_sdk::http::send`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/fn.send.html) function. This takes a request argument and returns a response (or error). It is `async`, so within an async inbound handler you can have multiple outbound `send`s running concurrently.
> Support for streaming request and response bodies is **experimental**. We currently recommend that you stick with the simpler non-streaming interfaces if you don't require streaming.
`send` is quite flexible in its request and response types. The request may be:
* [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) - typically constructed via `http::Request::builder()`
-* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Request.html) - typically constructed via `spin_sdk::http::Request::get()`, `spin_sdk::http::Request::post()`, or `spin_sdk::http::Request::builder()`
- * You can also use the [builder type](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.RequestBuilder.html) directly - `build` will be called automatically for you
-* [`spin_sdk::http::OutgoingRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.OutgoingRequest.html) - constructed via `OutgoingRequest::new()`
+* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Request.html) - typically constructed via `spin_sdk::http::Request::get()`, `spin_sdk::http::Request::post()`, or `spin_sdk::http::Request::builder()`
+ * You can also use the [builder type](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.RequestBuilder.html) directly - `build` will be called automatically for you
+* [`spin_sdk::http::OutgoingRequest`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.OutgoingRequest.html) - constructed via `OutgoingRequest::new()`
* Any type for which you have implemented the `TryInto` trait
Generally, you should use `OutgoingRequest` when you need to stream the outbound request body; otherwise, the `Request` types are usually simpler.
@@ -47,9 +47,9 @@ Generally, you should use `OutgoingRequest` when you need to stream the outbound
The response may be:
* [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html)
-* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Response.html)
-* [`spin_sdk::http::IncomingResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.IncomingResponse.html)
-* Any type for which you have implemented the [spin_sdk::http::conversions::TryFromIncomingResponse](https://docs.rs/spin-sdk/latest/spin_sdk/http/conversions/trait.TryFromIncomingResponse.html) trait
+* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Response.html)
+* [`spin_sdk::http::IncomingResponse`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.IncomingResponse.html)
+* Any type for which you have implemented the [spin_sdk::http::conversions::TryFromIncomingResponse](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/conversions/trait.TryFromIncomingResponse.html) trait
Generally, you should use `IncomingResponse` when you need to stream the response body; otherwise, the `Response` types are usually simpler.
@@ -87,7 +87,7 @@ async fn handle_request(_req: Request) -> anyhow::Result {
}
```
-For an example of receiving the response in a streaming style, [see this example in the Spin repository](https://github.com/spinframework/spin-rust-sdk/blob/main/examples/wasi-http-streaming-outgoing-body/src/lib.rs).
+For an example of receiving the response in a streaming style, [see this example in the Spin repository](https://github.com/spinframework/spin-rust-sdk/tree/v5.2.0/examples/wasi-http-streaming-outgoing-body/src/lib.rs).
{{ blockEnd }}
diff --git a/content/v3/http-trigger.md b/content/v3/http-trigger.md
index 40aa0fd4..ff5f0e15 100644
--- a/content/v3/http-trigger.md
+++ b/content/v3/http-trigger.md
@@ -154,9 +154,9 @@ The exact signature of the HTTP handler, and how a function is identified to be
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/http/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/index.html)
-In Rust, the handler is identified by the [`#[spin_sdk::http_component]`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_component.html) attribute. The handler function can have one of two forms: _request-response_ or _input-output parameter_.
+In Rust, the handler is identified by the [`#[spin_sdk::http_component]`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/attr.http_component.html) attribute. The handler function can have one of two forms: _request-response_ or _input-output parameter_.
**Request-Response Handlers**
@@ -174,15 +174,15 @@ In this form, nothing is sent to the client until the entire response is ready.
You have some flexibility in choosing the types of the request and response. The request may be:
* [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html)
-* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Request.html)
-* [`spin_sdk::http::IncomingRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.IncomingRequest.html)
-* Any type for which you have implemented the [`spin_sdk::http::conversions::TryFromIncomingRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/conversions/trait.TryFromIncomingRequest.html) trait
+* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Request.html)
+* [`spin_sdk::http::IncomingRequest`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.IncomingRequest.html)
+* Any type for which you have implemented the [`spin_sdk::http::conversions::TryFromIncomingRequest`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/conversions/trait.TryFromIncomingRequest.html) trait
The response may be:
* [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html) - typically constructed via `Response::builder()`
-* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Response.html) - typically constructed via a [`ResponseBuilder`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.ResponseBuilder.html)
-* Any type for which you have implemented the [`spin_sdk::http::IntoResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.IntoResponse.html) trait
+* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Response.html) - typically constructed via a [`ResponseBuilder`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.ResponseBuilder.html)
+* Any type for which you have implemented the [`spin_sdk::http::IntoResponse`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/trait.IntoResponse.html) trait
* A `Result` where the success type is one of the above and the error type is `anyhow::Error` or another error type for which you have implemented `spin_sdk::http::IntoResponse` (such as `anyhow::Result`)
For example:
@@ -208,11 +208,11 @@ To extract data from the request, specify a body type as the generic parameter f
**Input-Output Parameter Handlers**
-In this form, the handler function receives the request as an argument of type [`spin_sdk::http::IncomingRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.IncomingRequest.html). It also receives an argument of type [`spin_sdk::http::ResponseOutparam`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.ResponseOutparam.html), through which is sends the response. The function does not return a value. This form is recommended for streaming responses.
+In this form, the handler function receives the request as an argument of type [`spin_sdk::http::IncomingRequest`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.IncomingRequest.html). It also receives an argument of type [`spin_sdk::http::ResponseOutparam`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.ResponseOutparam.html), through which is sends the response. The function does not return a value. This form is recommended for streaming responses.
To send a response:
-1. Create a [`spin_sdk::http::OutgoingResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.OutgoingResponse.html).
+1. Create a [`spin_sdk::http::OutgoingResponse`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.OutgoingResponse.html).
2. Call `take_body()` on the `OutgoingResponse` - this gives you a [`futures::Sink`](https://docs.rs/futures/latest/futures/sink/trait.Sink.html) that you can later use to send data via the response.
3. Call `set` on the `ResponseOutparam`, passing the `OutgoingResponse`.
4. Call `send` on the `Sink` as many times as you like. Each send is carried out as you call it, so you can send the first part of the response without waiting for the whole response to be ready.
@@ -251,7 +251,7 @@ async fn handle_hello_rust(_req: IncomingRequest, response_out: ResponseOutparam
}
```
-For a full Rust SDK reference, see the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html).
+For a full Rust SDK reference, see the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html).
{{ blockEnd }}
diff --git a/content/v3/kv-store-api-guide.md b/content/v3/kv-store-api-guide.md
index aa6711fd..90990cf0 100644
--- a/content/v3/kv-store-api-guide.md
+++ b/content/v3/kv-store-api-guide.md
@@ -40,7 +40,7 @@ The exact detail of calling these operations from your application depends on yo
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/key_value/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/key_value/index.html)
Key value functions are available in the `spin_sdk::key_value` module. The function names match the operations above. For example:
diff --git a/content/v3/language-support-overview.md b/content/v3/language-support-overview.md
index c633fac4..00068b6b 100644
--- a/content/v3/language-support-overview.md
+++ b/content/v3/language-support-overview.md
@@ -13,7 +13,7 @@ This page contains information about language support for Spin features:
{{ startTab "Rust"}}
-**[📄 Visit the Rust Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/) to see specific modules, functions, variables and syntax relating to the following Rust features.**
+**[📄 Visit the Rust Spin SDK reference documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/) to see specific modules, functions, variables and syntax relating to the following Rust features.**
| Feature | SDK Supported? |
|-----|-----|
diff --git a/content/v3/mqtt-outbound.md b/content/v3/mqtt-outbound.md
index 0bba8769..7dc1e06e 100644
--- a/content/v3/mqtt-outbound.md
+++ b/content/v3/mqtt-outbound.md
@@ -32,7 +32,7 @@ The exact detail of calling these operations from your application depends on yo
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/mqtt/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/mqtt/index.html)
MQTT functions are available in the `spin_sdk::mqtt` module.
@@ -49,7 +49,7 @@ let cat_picture: Vec = request.body().to_vec();
connection.publish("pets", &cat_picture, spin_sdk::mqtt::Qos::AtLeastOnce)?;
```
-For full details of the MQTT API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/mqtt/index.html);
+For full details of the MQTT API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/mqtt/index.html);
You can find a complete Rust code example for using outbound MQTT from an HTTP component in the [Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/mqtt-outbound).
diff --git a/content/v3/rdbms-storage.md b/content/v3/rdbms-storage.md
index bff26c18..6b0a6fbe 100644
--- a/content/v3/rdbms-storage.md
+++ b/content/v3/rdbms-storage.md
@@ -37,7 +37,7 @@ The exact detail of calling these operations from your application depends on yo
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html)
MySQL functions are available in the `spin_sdk::mysql` module, and PostgreSQL functions in the `spin_sdk::pg4` module.
@@ -73,7 +73,7 @@ match rowset.rows.first() {
You can find complete examples for using relational databases in the Spin repository on GitHub ([MySQL](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/mysql), [PostgreSQL](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/postgres)).
-For full information about the MySQL and PostgreSQL APIs, see [the Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html).
+For full information about the MySQL and PostgreSQL APIs, see [the Spin SDK reference documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html).
{{ blockEnd }}
diff --git a/content/v3/redis-outbound.md b/content/v3/redis-outbound.md
index 944b1297..5f14ab60 100644
--- a/content/v3/redis-outbound.md
+++ b/content/v3/redis-outbound.md
@@ -41,7 +41,7 @@ The exact detail of calling these operations from your application depends on yo
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/redis/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/redis/index.html)
Redis functions are available in the `spin_sdk::redis` module.
@@ -58,7 +58,7 @@ connection.set("my-key", &"my-value".into());
let data = connection.get("my-key")?;
```
-For full details of the Redis API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/redis/index.html);
+For full details of the Redis API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/redis/index.html);
**General Notes**
diff --git a/content/v3/redis-trigger.md b/content/v3/redis-trigger.md
index 0f44773d..90b8f8ed 100644
--- a/content/v3/redis-trigger.md
+++ b/content/v3/redis-trigger.md
@@ -73,7 +73,7 @@ The exact signature of the Redis handler, and how a function is identified to be
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html)
In Rust, the handler is identified by the `#[spin_sdk::redis_component]` attribute. It takes a `bytes::Bytes`, representing the raw payload of the Redis message, and returns an `anyhow::Result` indicating success or an error with details. This example just logs the payload as a string:
diff --git a/content/v3/rust-components.md b/content/v3/rust-components.md
index f8f7ec36..8a36b076 100644
--- a/content/v3/rust-components.md
+++ b/content/v3/rust-components.md
@@ -35,7 +35,7 @@ official resources for learning Rust](https://www.rust-lang.org/learn).
> All examples from this page can be found in [the Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples).
-[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html)
+[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html)
## Prerequisites
@@ -100,7 +100,7 @@ for writing Spin components with the Spin Rust SDK.
> Make sure to read [the page describing the HTTP trigger](./http-trigger.md) for more
> details about building HTTP applications.
-Building a Spin HTTP component using the Rust SDK means writing a single function decorated with the [`#[http_component]`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_component.html) attribute. The function can have one of two forms:
+Building a Spin HTTP component using the Rust SDK means writing a single function decorated with the [`#[http_component]`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/attr.http_component.html) attribute. The function can have one of two forms:
* takes an HTTP request as a parameter, and returns an HTTP response — shown below
* taken as parameters _both_ the HTTP request and an object through which to write a response - see [the HTTP trigger page](./http-trigger#authoring-http-components) for an example.
@@ -122,9 +122,9 @@ async fn handle_hello_rust(_req: Request) -> anyhow::Result {
The important things to note in the implementation above:
-- the [`spin_sdk::http_component`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_component.html) macro marks the function as the entry point for the Spin component
+- the [`spin_sdk::http_component`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/attr.http_component.html) macro marks the function as the entry point for the Spin component
- the function signature — `fn hello_world(req: Request) -> Result` —
- the Spin HTTP component allows for a flexible set of response types via the [`IntoResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.IntoResponse.html) trait, including the SDK's `Response` type and the `Response` type from the Rust [`http` crate](https://crates.io/crates/http). See the section on [using the `http` crate](#using-the-http-crate) for more information.
+ the Spin HTTP component allows for a flexible set of response types via the [`IntoResponse`](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/trait.IntoResponse.html) trait, including the SDK's `Response` type and the `Response` type from the Rust [`http` crate](https://crates.io/crates/http). See the section on [using the `http` crate](#using-the-http-crate) for more information.
> If you're familiar with Spin 1.x, you will see some changes when upgrading to the Spin 2 SDK. Mostly these provide more flexibility, but you will likely need to change some details such as module paths. If you don't want to modify your code, you can continue using the 1.x SDK - your components will still run.
@@ -310,7 +310,7 @@ proxies or URL shorteners.
-The Rust SDK [provides a router](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/http-router) that makes it easier to handle routing within a component:
+The Rust SDK [provides a router](https://github.com/spinframework/spin-rust-sdk/tree/v5.2.0/examples/http-router) that makes it easier to handle routing within a component:
```rust
use anyhow::Result;
@@ -347,7 +347,7 @@ mod api {
Handlers within a `Router` can be sync or async. Use `Router`'s "plain" methods (e.g. `get`, `post`) to assign synchronous handlers, and its "async" methods (e.g. `get_async`, `post_async`) for asynchronous handlers. You can mix sync and async handlers in the same `Router`, and can use `handle` or `handle_async` to invoke `Router` processing, regardless of whether invididual handlers are sync or async.
-> For further reference, see the [Spin SDK HTTP router](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Router.html).
+> For further reference, see the [Spin SDK HTTP router](https://docs.rs/spin-sdk/5.2.0/spin_sdk/http/struct.Router.html).
## Storing Data in Redis From Rust Components
@@ -506,7 +506,7 @@ HTTP/1.1 200 OK
0x1234
```
-For more information on the Rust key-value API see [the Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/key_value/index.html).
+For more information on the Rust key-value API see [the Spin SDK documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/key_value/index.html).
## Storing Data in SQLite
@@ -611,4 +611,4 @@ spin-sdk = { git = "https://github.com/spinframework/spin" }
## Read the Rust Spin SDK Documentation
-Although you learned a lot by following the concepts and samples shown here, you can dive even deeper and read the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html).
+Although you learned a lot by following the concepts and samples shown here, you can dive even deeper and read the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/5.2.0/spin_sdk/index.html).
diff --git a/content/v3/serverless-ai-api-guide.md b/content/v3/serverless-ai-api-guide.md
index 47bc168b..f4b053cc 100644
--- a/content/v3/serverless-ai-api-guide.md
+++ b/content/v3/serverless-ai-api-guide.md
@@ -68,7 +68,7 @@ The exact detail of calling these operations from your application depends on yo
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/llm/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/llm/index.html)
To use Serverless AI functions, the `llm` module from the Spin SDK provides the methods. The following snippet is from the [Rust code generation example](https://github.com/fermyon/ai-examples/tree/main/code-generator-rs):
diff --git a/content/v3/sqlite-api-guide.md b/content/v3/sqlite-api-guide.md
index 6088f519..e3eeee66 100644
--- a/content/v3/sqlite-api-guide.md
+++ b/content/v3/sqlite-api-guide.md
@@ -54,7 +54,7 @@ serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
```
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/sqlite3/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/sqlite3/index.html)
SQLite functions are available in the `spin_sdk::sqlite3` module
diff --git a/content/v3/variables.md b/content/v3/variables.md
index 181320a9..3ba08f87 100644
--- a/content/v3/variables.md
+++ b/content/v3/variables.md
@@ -103,7 +103,7 @@ The exact details of calling the config SDK from a Spin application depends on t
{{ startTab "Rust"}}
-> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/variables/index.html)
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/5.2.0/spin_sdk/variables/index.html)
The interface is available in the `spin_sdk::variables` module and is named `get`.
diff --git a/content/v4/api-guides-overview.md b/content/v4/api-guides-overview.md
new file mode 100644
index 00000000..5f9cd691
--- /dev/null
+++ b/content/v4/api-guides-overview.md
@@ -0,0 +1,55 @@
+title = "API Support Overview"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/api-guides-overview.md"
+
+---
+- [Asynchronous and Blocking APIs](#asynchronous-and-blocking-apis)
+- [Targeting a Deployment Environment](#targeting-a-deployment-environment)
+
+The following table shows the status of the interfaces Spin provides to applications.
+
+| Host Capabilities/Interfaces | Stability | Async? (see NOTE) |
+|----------------------------------------------|--------------|---------------------|
+| [HTTP Trigger](./http-trigger) | Stable | Async entry point |
+| [Redis Trigger](./redis-trigger) | Stable | Async entry point |
+| [Cron Trigger](./triggers) | Experimental | Sync entry point |
+| [Outbound HTTP](./http-outbound) | Stable | Async |
+| [Outbound Redis](./redis-outbound) | Stable | Async |
+| [Configuration Variables](./variables) | Stable | Async |
+| [PostgreSQL](./rdbms-storage) | Stable | Async |
+| [MySQL](./rdbms-storage) | Experimental | Blocking |
+| [Key-value Storage](./kv-store-api-guide) | Stable | Async |
+| [Serverless AI](./serverless-ai-api-guide) | Experimental | Blocking |
+| [SQLite Storage](./sqlite-api-guide) | Stable | Async |
+| [MQTT Messaging](./mqtt-outbound) | Experimental | Async |
+
+NOTE: Blocking versions of async APIs and entry points are still available for backward compatibility.
+
+For more information about what is possible in the programming language of your choice, please see our [Language Support Overview](./language-support-overview).
+
+## Asynchronous and Blocking APIs
+
+In Spin 3.x, all APIs and entry points were blocking. These blocking APIs and entry points are still supported in Spin 4.x, but Spin 4.x also supports asynchronous versions of most of them.
+
+Generally, you can mix async and blocking APIs as necessary. There is one important restriction: a sync entry point _must not_ call an async API. If you are using a sync trigger (as shown above), you _must_ call _only_ blocking APIs. Depending on your language and Spin SDK version, blocking APIs may be available in your SDK, or you may need to switch to an older SDK.
+
+Older builds of trigger plugins may be sync even if marked async above. Check the plugin documentation, and make sure you are running an up-to-date version, if you want to use async APIs.
+
+## Targeting a Deployment Environment
+
+Some Spin runtimes may support a different set of APIs from those listed above, or may support only older versions. For example, some runtimes might not support SQLite, or Serverless AI; or Spin 3.x-based runtimes don't support the async API versions introduced in Spin 4.0. This is an important consideration when writing a Spin application that will run in a different environment from your development environment: you do not want to depend on SQLite if you will have to deploy to an environment without it.
+
+You can tell the Spin CLI about the environment (or environments) that you plan to deploy into using the `application.targets` field in `spin.toml`. If you do this, `spin build` verifies the set of APIs used by your components against each listed environment. If a component uses APIs that wouldn't be supported, `spin build` will warn you.
+
+For example, here is how to specify that you want your application to be compatible with `spin up` version 3.2:
+
+```toml
+# spin.toml
+
+[application]
+targets = ["spin-up:3.2"]
+```
+
+For the other runtime environments such as SpinKube or commercial clouds, see the documentation for those projects for their environment IDs.
diff --git a/content/v4/build.md b/content/v4/build.md
new file mode 100644
index 00000000..5fa4cf5d
--- /dev/null
+++ b/content/v4/build.md
@@ -0,0 +1,243 @@
+title = "Building Spin Application Code"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/build.md"
+
+---
+
+- [Setting Up for `spin build`](#setting-up-for-spin-build)
+- [Running `spin build`](#running-spin-build)
+- [Running the Application After Build](#running-the-application-after-build)
+- [Overriding the Working Directory](#overriding-the-working-directory)
+- [Building With Profiles](#building-with-profiles)
+- [Next Steps](#next-steps)
+
+A Spin application is made up of one or more components. Components are binary Wasm modules; _building_ refers to the process of converting your source code into those modules.
+
+> Even languages that don't require a compile step when used 'natively' may still require a build step to adapt them to work as Wasm modules.
+
+Because most compilers don't target Wasm by default, building Wasm modules often requires special command options, which you may not have at your fingertips.
+What's more, when developing a multi-component application, you may need to issue such commands for several components on each iteration.
+Doing this manually can be tedious and error-prone.
+
+To make the build process easier, the `spin build` command allows you to build all the components in one command.
+
+> You don't have to use `spin build` to manage your builds. If you prefer to use a Makefile or other build system, you can! `spin build` is just there to provide an 'out of the box' solution.
+
+
+## Setting Up for `spin build`
+
+To use `spin build`, each component that you want to build must specify the command used to build it in `spin.toml`, as part of its `component.(name).build` table:
+
+```toml
+[component.hello]
+# This is the section you need for `spin build`
+[component.hello.build]
+command = "npm run build"
+```
+
+If you generated the component from a Spin Framework project-supplied template, the `build` section should be set up correctly for you. You don't need to change or add anything.
+
+> Different components may be built from different languages, and so each component can have its own build command. In addition, some components may be precompiled into Wasm modules, and don't need a build command at all. If a component doesn't have a build command, `spin build` just skips it.
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+For Rust applications, you must have the `wasm32-wasip2` target installed:
+
+
+
+```bash
+$ rustup target add wasm32-wasip2
+```
+
+The build command typically runs `cargo build` with the `wasm32-wasip2` target and the `--release` option:
+
+
+
+```toml
+[component.hello.build]
+command = "cargo build --target wasm32-wasip2 --release"
+```
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript" }}
+
+For JavaScript and TypeScript applications, you must have [Node.js](https://nodejs.org).
+
+
+It's normally convenient to put the detailed build instructions in `package.json`. The build script looks like:
+
+
+
+```json
+{
+ "scripts": {
+ "build": "node build.mjs && mkdirp dist && j2w -i build/bundle.js --initLocation http://hello.localhost -o dist/hello.wasm"
+ }
+}
+```
+
+The build command can then call the NPM script:
+
+
+
+```toml
+[component.hello.build]
+command = "npm run build"
+```
+
+{{ blockEnd }}
+
+{{ startTab "Python" }}
+
+Ensure that you have Python 3.10 or later installed on your system. You can check your Python version by running:
+
+```bash
+python3 --version
+```
+
+If you do not have Python 3.10 or later, you can install it by following the instructions [here](https://www.python.org/downloads/).
+
+For Python applications, you must have [`componentize-py`](https://pypi.org/project/componentize-py/) installed:
+
+
+
+```bash
+$ pip3 install componentize-py
+```
+
+The build command then calls `componentize-py` on your application file:
+
+
+
+```toml
+[component.hello.build]
+command = "componentize-py -w spin-http componentize app -o app.wasm"
+```
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo" }}
+
+For Go applications, you must use the TinyGo compiler, as the standard Go compiler does not yet support the WASI standard. See the [TinyGo installation guide](https://tinygo.org/getting-started/install/).
+
+The build command calls TinyGo with the WASI backend and appropriate options:
+
+
+
+```toml
+[component.hello.build]
+command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ."
+```
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+> The output of the build command _must_ match the component's `source` path. If you change the `build` or `source` attributes, make sure to keep them in sync.
+
+
+## Running `spin build`
+
+Once the build commands are set up, running `spin build` will execute, sequentially, each build command:
+
+
+
+```bash
+$ spin build
+Building component hello with `cargo build --target wasm32-wasip2 --release`
+ Updating crates.io index
+ Updating git repository `https://github.com/spinframework/spin`
+
+ //--snip--
+
+ Compiling hello v0.1.0 (hello)
+ Finished release [optimized] target(s) in 39.05s
+Finished building all Spin components
+```
+
+> If your build doesn't work, and your source code looks okay, you can [run `spin doctor`](./troubleshooting-application-dev.md) to check for problems with your Spin configuration and tools.
+
+## Running the Application After Build
+
+You can pass the `--up` option to `spin build` to start the application as soon as the build process completes successfully.
+
+This is equivalent to running `spin up` immediately after `spin build`. It accepts all the same flags and options that `up` does. See [Running Applications](running-apps) for details.
+
+## Overriding the Working Directory
+
+By default, the `command` to build a component is executed in the directory containing the `spin.toml` file. If a component's entire build source is under a subdirectory, it is often more convenient to build in that subdirectory rather than try to pass the path to the build command. You can do this by setting the `workdir` option in the `component.(id).build` table.
+
+For example, consider this Rust component located in subdirectory `deep`:
+
+
+
+```bash
+.
+├── deep
+│ ├── Cargo.toml
+│ └── src
+│ └── lib.rs
+└── spin.toml
+```
+
+To have the Rust build `command` run in directory `deep`, we can set the component's `workdir`:
+
+
+
+```toml
+[component.deep.build]
+# `command` is the normal build command for this language
+command = "cargo build --target wasm32-wasip2 --release"
+# This tells Spin to run it in the directory of the build file (in this case Cargo.toml)
+workdir = "deep"
+```
+
+> `workdir` must be a relative path, and it is relative to the directory containing `spin.toml`. Specifying an absolute path leads to an error.
+
+## Building With Profiles
+
+A component can define _build profiles_, which override certain component settings to allow for different usages. For example, a component might define a debug profile, which compiles the binary with debugging information. A profile can also override environment variables and dependencies.
+
+To define a profile, create a `profile.` entry in the component TOML. For example:
+
+```toml
+[component.example]
+source = "./out/release/example.wasm"
+[component.example.build]
+command = "make release"
+[component.example.profile.debug]
+source = "./out/debug/example.wasm"
+environment = { TRACE_LEVEL = "full" }
+[component.example.profile.debug.build]
+command = "make debug"
+```
+
+To use a build profile, pass the `--profile ` flag to the Spin command you're running. For example, `spin build --profile debug` or `spin up --profile debug`.
+
+> When you have build profiles in play, you run the risk of accidentally running `spin build` with a profile and then running `spin up` or `spin registry push` without a profile, not realising that you are running or pushing the default profile rather than the one you just built! Spin will warn you if you do this. But a safer technique is to provide `--build` as part of the `up` or `registry` push command, e.g. `spin up --profile debug --build`, `spin registry push --profile publish --build`. This guarantees that the right profile has been freshly built. You can set the `SPIN_ALWAYS_BUILD` environment variable to tell Spin to _always_ use the `--build` option.
+
+If a component doesn't define a profile (or doesn't override a particular field in its profile), Spin will fall back to the 'base' value. You only need to override the specific components and fields where the profile differs from the base. For example:
+
+```toml
+[component.example1]
+source = "./out/example1.wasm" # source will be the same with or without `--profile debug`
+[component.example1.build]
+command = "make release1"
+[component.example1.profile.debug.build]
+command = "make debug1"
+
+[component.example2] # everything will be the same with our without `--profile debug`
+source = "./out/example2.wasm"
+[component.example2.build]
+command = "make example2"
+```
+
+## Next Steps
+
+- Try [running your application locally](running-apps)
diff --git a/content/v4/cache.md b/content/v4/cache.md
new file mode 100644
index 00000000..49c66891
--- /dev/null
+++ b/content/v4/cache.md
@@ -0,0 +1,95 @@
+title = "Spin Internal Data Layout"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/cache.md"
+
+---
+
+- [Base Directories](#base-directories)
+- [Plugins](#plugins)
+- [Templates](#templates)
+- [Application Cache](#application-cache)
+ - [Inside the Application Cache](#inside-the-application-cache)
+
+This page describes how Spin lays out its internal data on disk.
+
+> This document is provided as a reference for users wanting to diagnose problems or to reset Spin state. Don't modify the contents of these directories. Spin updates these directories as you issue the relevant commands on the command line.
+
+> No stability guarantees apply to the internal layout. It may change between Spin versions.
+
+## Base Directories
+
+Spin uses similar layouts across Linux, MacOS and Windows platforms, but the paths to various areas of the user's home directory differ across the platforms. On this page, the following terms have the following meanings:
+
+| Name | Linux | MacOS | Windows |
+|---------------------|------------------------------------------|--------------------------------------|-------------------|
+| `DATA_DIR` | `$XDG_DATA_HOME` or `$HOME/.local/share` | `$HOME/Library/Application Support`, or `$HOMEBREW_PREFIX/etc/fermyon-spin` for Spin =v3.2 if installed using Homebrew | `%LOCALAPPDATA%` or `%USERPROFILE%\AppData\Local` |
+| `CACHE_DIR` | `$XDG_CACHE_HOME` or `$HOME/.cache` | `$HOME/Library/Caches` | `%LOCALAPPDATA%` or `%USERPROFILE%\AppData\Local` |
+
+These directories are based on the [XDG specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), and specifically on the cross-platform implementation in the [Rust `dirs` crate](https://docs.rs/dirs/latest/dirs/).
+
+> If Spin cannot resolve a base directory as listed above, it falls back to `$HOME/.spin` (`%USERPROFILE%\.spin` on Windows).
+
+> Spin's data directory (`DATA_DIR`) can be overridden via the `SPIN_DATA_DIR` environment variable.
+
+## Plugins
+
+Installed plugins are stored in `(DATA_DIR)/spin/plugins`. A snapshot of the plugins registry is also stored under that directory at `(DATA_DIR)/spin/plugins/.spin-plugins`; this is structured as a Git repository.
+
+> Note: If you [install Spin](./install) using Homebrew, the plugins are stored at `$HOMEBREW_PREFIX/spinframework-spin/plugins` (previously `$HOMEBREW_PREFIX/fermyon-spin/plugins`).
+
+If you delete the plugins directory, you will no longer be able to run your plugins (until you reinstall them), but other Spin operations will be unaffected.
+
+## Templates
+
+Installed templates are stored in `(DATA_DIR)/spin/templates`.
+
+> Note: If you [install Spin](install) using Homebrew, the templates are stored at at `$HOMEBREW_PREFIX/spinframework-spin/templates` (previously `$HOMEBREW_PREFIX/fermyon-spin/templates`).
+
+If you delete the templates directory, you will lose access to your installed templates (until you reinstall them), but other Spin operations will be unaffected.
+
+## Application Cache
+
+Downloaded application data, such as applications downloaded from registries or Wasm modules downloaded from URLs, are stored in `(CACHE_DIR)/spin/registry`.
+
+If you delete the application cache directory, Spin will automatically re-download the files as needed. Spin operations will be otherwise unaffected.
+
+### Inside the Application Cache
+
+> **Reminder:** This information is provided for diagnostic and entertainment purposes only, and may change across Spin versions. The only operation a user can safely undertake is to delete the entire `registry` directory.
+
+The application cache is divided into three subdirectories, `data`, `manifests`, and `wasm`.
+
+The `data` directory contains all static assets referenced from applications distributed with remote registries. The `wasm` directory contains all component sources referenced either in applications distributed with remote registries, or component sources from HTTP endpoints, directly referenced in `spin.toml`.
+
+> The `data` and `wasm` directories are content addressable. This means that if multiple applications reference the same static file or component source, Spin will be able to determine if it has already been pulled (on that users operating system), based on its digest. This also means that if an application has an update, Spin will only pull the changes in the component sources and static assets.
+
+The `manifests` directory contains the registry manifests for entire apps distributed with remote registries. They are placed in subdirectories that identify the application based on the registry, repository, and digest (or tag).
+
+The following `tree` command shows a typical (abbreviated) cache directory:
+
+
+
+```console
+$ tree ~/Library/Caches/spin/registry/
+
+├── data
+│ ├── sha256:41a4649a8a8c176133792119cb45a7686767d3fa376ffd656e2ff76a6071fb07
+│ └── sha256:da3fda2db338a73483068072e22f7e7eef27afdbae3db824e130932adce703ba
+├── manifests
+│ └── ghcr.io
+│ └── radu-matei
+│ ├── hello-registries
+│ │ └── latest
+│ │ ├── config.json
+│ │ └── manifest.json
+│ └── spin-openai-demo
+│ └── v1
+│ ├── config.json
+│ └── manifest.json
+└── wasm
+ ├── sha256:0b985e7d43e719f34cbb54849759a2f8e7913c0f9b17bf7cb2b3d2458d33859e
+ └── sha256:d5f9e1f6b61b90f7404e3800285f7860fe2cfc7d0116023efc370adbb403fe87
+```
diff --git a/content/v4/cli-reference.md b/content/v4/cli-reference.md
new file mode 100644
index 00000000..12cd19a8
--- /dev/null
+++ b/content/v4/cli-reference.md
@@ -0,0 +1,611 @@
+title = "Command Line Reference"
+template = "main"
+date = "2025-01-01T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/cli-reference.md"
+
+---
+# Command-Line Help for `spin`
+
+This document contains the help content for the `spin` command-line program.
+
+**Command Overview:**
+
+* [`spin`↴](#spin)
+* [`spin add`↴](#spin-add)
+* [`spin build`↴](#spin-build)
+* [`spin deploy`↴](#spin-deploy)
+* [`spin doctor`↴](#spin-doctor)
+* [`spin login`↴](#spin-login)
+* [`spin new`↴](#spin-new)
+* [`spin plugins`↴](#spin-plugins)
+* [`spin plugins install`↴](#spin-plugins-install)
+* [`spin plugins list`↴](#spin-plugins-list)
+* [`spin plugins search`↴](#spin-plugins-search)
+* [`spin plugins show`↴](#spin-plugins-show)
+* [`spin plugins uninstall`↴](#spin-plugins-uninstall)
+* [`spin plugins update`↴](#spin-plugins-update)
+* [`spin plugins upgrade`↴](#spin-plugins-upgrade)
+* [`spin registry`↴](#spin-registry)
+* [`spin registry login`↴](#spin-registry-login)
+* [`spin registry pull`↴](#spin-registry-pull)
+* [`spin registry push`↴](#spin-registry-push)
+* [`spin templates`↴](#spin-templates)
+* [`spin templates install`↴](#spin-templates-install)
+* [`spin templates list`↴](#spin-templates-list)
+* [`spin templates uninstall`↴](#spin-templates-uninstall)
+* [`spin templates upgrade`↴](#spin-templates-upgrade)
+* [`spin up`↴](#spin-up)
+* [`spin watch`↴](#spin-watch)
+
+## `spin`
+
+The Spin CLI
+
+**Usage:** `USAGE:
+ spin `
+
+###### **Subcommands:**
+
+* `add` — Scaffold a new component into an existing application
+* `build` — Build the Spin application
+* `deploy` — Package and upload an application to a deployment environment.
+* `doctor` — Detect and fix problems with Spin applications
+* `login` — Log into a deployment environment.
+* `new` — Scaffold a new application based on a template
+* `plugins` — Install/uninstall Spin plugins
+* `registry` — Commands for working with OCI registries to distribute applications
+* `templates` — Commands for working with WebAssembly component templates
+* `up` — Start the Spin application
+* `watch` — Build and run the Spin application, rebuilding and restarting it when files change
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin add`
+
+Scaffold a new component into an existing application
+
+**Usage:** `spin USAGE:
+ add [OPTIONS] [NAME]`
+
+###### **Arguments:**
+
+* `` — The name of the new application or component
+* `` — The name of the new application or component. If present, `name` is instead treated as the template ID. This provides backward compatibility with Spin 1.x syntax, so that existing content continues to work
+
+###### **Options:**
+
+* `-a`, `--accept-defaults ` — An optional argument that allows to skip prompts for the manifest file by accepting the defaults if available on the template
+* `--allow-overwrite ` — If the output directory already contains files, generate the new files into it without confirming, overwriting any existing files with the same names
+* `-f`, `--file ` — Path to spin.toml
+* `--help ` — Print help information
+* `--init ` — Create the new application or component in the current directory
+* `--no-vcs ` — An optional argument that allows to skip creating .gitignore
+* `-o`, `--output ` — The directory in which to create the new application or component. The default is the name argument
+* `-t`, `--template ` — The template from which to create the new application or component. Run `spin templates list` to see available options
+* `--tag ` — Filter templates to select by tags
+* `-v`, `--value ` — Parameter values to be passed to the template (in name=value format)
+* `--values-file ` — A TOML file which contains parameter values in name = "value" format. Parameters passed as CLI option overwrite parameters specified in the file
+* `--version ` — Print version information
+
+
+
+## `spin build`
+
+Build the Spin application
+
+**Usage:** `spin USAGE:
+ build [OPTIONS] [UP_ARGS]...`
+
+###### **Arguments:**
+
+* ``
+
+###### **Options:**
+
+* `-c`, `--component-id ` — Component ID to build. This can be specified multiple times. The default is all components
+* `-f`, `--from ` — The application to build. This may be a manifest (spin.toml) file, or a directory containing a spin.toml file. If omitted, it defaults to "spin.toml"
+* `--help ` — Print help information
+* `--skip-target-checks ` — By default, if the application manifest specifies one or more deployment targets, Spin checks that all components are compatible with those deployment targets. Specify this option to bypass those target checks
+* `-u`, `--up ` — Run the application after building
+* `--version ` — Print version information
+
+
+
+## `spin deploy`
+
+Package and upload an application to a deployment environment.
+
+**Usage:** `spin USAGE:
+ deploy`
+
+###### **Arguments:**
+
+* `` — All args to be passed through to the plugin
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin doctor`
+
+Detect and fix problems with Spin applications
+
+**Usage:** `spin USAGE:
+ doctor [OPTIONS]`
+
+###### **Options:**
+
+* `-f`, `--from ` — The application to check. This may be a manifest (spin.toml) file, or a directory containing a spin.toml file. If omitted, it defaults to "spin.toml"
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin login`
+
+Log into a deployment environment.
+
+**Usage:** `spin USAGE:
+ login`
+
+###### **Arguments:**
+
+* `` — All args to be passed through to the plugin
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin new`
+
+Scaffold a new application based on a template
+
+**Usage:** `spin USAGE:
+ new [OPTIONS] [NAME]`
+
+###### **Arguments:**
+
+* `` — The name of the new application or component
+* `` — The name of the new application or component. If present, `name` is instead treated as the template ID. This provides backward compatibility with Spin 1.x syntax, so that existing content continues to work
+
+###### **Options:**
+
+* `-a`, `--accept-defaults ` — An optional argument that allows to skip prompts for the manifest file by accepting the defaults if available on the template
+* `--allow-overwrite ` — If the output directory already contains files, generate the new files into it without confirming, overwriting any existing files with the same names
+* `--help ` — Print help information
+* `--init ` — Create the new application or component in the current directory
+* `--no-vcs ` — An optional argument that allows to skip creating .gitignore
+* `-o`, `--output ` — The directory in which to create the new application or component. The default is the name argument
+* `-t`, `--template ` — The template from which to create the new application or component. Run `spin templates list` to see available options
+* `--tag ` — Filter templates to select by tags
+* `-v`, `--value ` — Parameter values to be passed to the template (in name=value format)
+* `--values-file ` — A TOML file which contains parameter values in name = "value" format. Parameters passed as CLI option overwrite parameters specified in the file
+* `--version ` — Print version information
+
+
+
+## `spin plugins`
+
+Install/uninstall Spin plugins
+
+**Usage:** `spin USAGE:
+ plugins `
+
+###### **Subcommands:**
+
+* `install` — Install plugin from a manifest
+* `list` — List available or installed plugins
+* `search` — Search for plugins by name
+* `show` — Print information about a plugin
+* `uninstall` — Remove a plugin from your installation
+* `update` — Fetch the latest Spin plugins from the spin-plugins repository
+* `upgrade` — Upgrade one or all plugins
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin plugins install`
+
+Install plugin from a manifest.
+
+The binary file and manifest of the plugin is copied to the local Spin plugins directory.
+
+**Usage:** `spin plugins USAGE:
+ install [OPTIONS] [PLUGIN_NAME]`
+
+###### **Arguments:**
+
+* `` — Name of Spin plugin
+
+###### **Options:**
+
+* `--auth-header-value ` — Provide the value for the authorization header to be able to install a plugin from a private repository. (e.g) --auth-header-value "Bearer "
+* `-f`, `--file ` — Path to local plugin manifest
+* `--help ` — Print help information
+* `--override-compatibility-check ` — Overrides a failed compatibility check of the plugin with the current version of Spin
+* `-u`, `--url ` — URL of remote plugin manifest to install
+* `-v`, `--version ` — Specific version of a plugin to be install from the centralized plugins repository
+* `--version ` — Print version information
+* `-y`, `--yes ` — Skips prompt to accept the installation of the plugin
+
+
+
+## `spin plugins list`
+
+List available or installed plugins
+
+**Usage:** `spin plugins USAGE:
+ list [OPTIONS]`
+
+###### **Options:**
+
+* `--all ` — List all versions of plugins. This is the default behaviour
+* `--filter ` — Filter the list to plugins containing this string
+* `--format ` — The format in which to list the templates
+
+ Default value: `plain`
+* `--help ` — Print help information
+* `--installed ` — List only installed plugins
+* `--summary ` — List latest and installed versions of plugins
+* `--version ` — Print version information
+
+
+
+## `spin plugins search`
+
+Search for plugins by name
+
+**Usage:** `spin plugins USAGE:
+ search [OPTIONS] [FILTER]`
+
+###### **Arguments:**
+
+* `` — The text to search for. If omitted, all plugins are returned
+
+###### **Options:**
+
+* `--format ` — The format in which to list the plugins
+
+ Default value: `plain`
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin plugins show`
+
+Print information about a plugin
+
+**Usage:** `spin plugins USAGE:
+ show `
+
+###### **Arguments:**
+
+* `` — Name of Spin plugin
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin plugins uninstall`
+
+Remove a plugin from your installation
+
+**Usage:** `spin plugins USAGE:
+ uninstall `
+
+###### **Arguments:**
+
+* `` — Name of Spin plugin
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin plugins update`
+
+Fetch the latest Spin plugins from the spin-plugins repository
+
+**Usage:** `spin plugins USAGE:
+ update`
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin plugins upgrade`
+
+Upgrade one or all plugins
+
+**Usage:** `spin plugins USAGE:
+ upgrade [OPTIONS] [PLUGIN_NAME]`
+
+###### **Arguments:**
+
+* `` — Name of Spin plugin to upgrade
+
+###### **Options:**
+
+* `-a`, `--all ` — Upgrade all plugins
+* `--auth-header-value ` — Provide the value for the authorization header to be able to install a plugin from a private repository. (e.g) --auth-header-value "Bearer "
+* `-d`, `--downgrade ` — Allow downgrading a plugin's version
+* `-f`, `--file ` — Path to local plugin manifest
+* `--help ` — Print help information
+* `--override-compatibility-check ` — Overrides a failed compatibility check of the plugin with the current version of Spin
+* `-u`, `--url ` — Path to remote plugin manifest
+* `-v`, `--version ` — Specific version of a plugin to be install from the centralized plugins repository
+* `--version ` — Print version information
+* `-y`, `--yes ` — Skips prompt to accept the installation of the plugin[s]
+
+
+
+## `spin registry`
+
+Commands for working with OCI registries to distribute applications
+
+**Usage:** `spin USAGE:
+ registry `
+
+###### **Subcommands:**
+
+* `login` — Log in to a registry
+* `pull` — Pull a Spin application from a registry
+* `push` — Push a Spin application to a registry
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin registry login`
+
+Log in to a registry
+
+**Usage:** `spin registry USAGE:
+ login [OPTIONS] `
+
+###### **Arguments:**
+
+* `` — OCI registry server (e.g. ghcr.io)
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `-p`, `--password ` — Password for the registry
+* `--password-stdin ` — Take the password from stdin
+* `-u`, `--username ` — Username for the registry
+* `--version ` — Print version information
+
+
+
+## `spin registry pull`
+
+Pull a Spin application from a registry
+
+**Usage:** `spin registry USAGE:
+ pull [OPTIONS] `
+
+###### **Arguments:**
+
+* `` — Reference in the registry of the published Spin application. This is a string whose format is defined by the registry standard, and generally consists of //:. E.g. ghcr.io/ogghead/spin-test-app:0.1.0
+
+###### **Options:**
+
+* `--cache-dir ` — Cache directory for downloaded registry data
+* `--help ` — Print help information
+* `-k`, `--insecure ` — Ignore server certificate errors
+* `--version ` — Print version information
+
+
+
+## `spin registry push`
+
+Push a Spin application to a registry
+
+**Usage:** `spin registry USAGE:
+ push [OPTIONS] `
+
+###### **Arguments:**
+
+* `` — Reference in the registry of the Spin application. This is a string whose format is defined by the registry standard, and generally consists of //:. E.g. ghcr.io/ogghead/spin-test-app:0.1.0
+
+###### **Options:**
+
+* `--annotation ` — Specifies the OCI image manifest annotations (in key=value format). Any existing value will be overwritten. Can be used multiple times
+* `--build ` — Specifies to perform `spin build` (with the default options) before pushing the application
+* `--cache-dir ` — Cache directory for downloaded registry data
+* `--compose ` — Compose component dependencies before pushing the application.
+
+ The default is to compose before pushing, which maximises compatibility with different Spin runtime hosts. Turning composition off can optimise bandwidth for shared dependencies, but makes the pushed image incompatible with hosts that cannot carry out composition themselves.
+
+ Default value: `true`
+* `-f`, `--from ` — The application to push. This may be a manifest (spin.toml) file, or a directory containing a spin.toml file. If omitted, it defaults to "spin.toml"
+* `--help ` — Print help information
+* `-k`, `--insecure ` — Ignore server certificate errors
+* `--version ` — Print version information
+
+
+
+## `spin templates`
+
+Commands for working with WebAssembly component templates
+
+**Usage:** `spin USAGE:
+ templates `
+
+###### **Subcommands:**
+
+* `install` — Install templates from a Git repository or local directory
+* `list` — List the installed templates
+* `uninstall` — Remove a template from your installation
+* `upgrade` — Upgrade templates to match your current version of Spin
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin templates install`
+
+Install templates from a Git repository or local directory.
+
+The files of the templates are copied to the local template store: a directory in your data or home directory.
+
+**Usage:** `spin templates USAGE:
+ install [OPTIONS]`
+
+###### **Options:**
+
+* `--branch ` — The optional branch of the git repository
+* `--dir ` — Local directory containing the template(s) to install
+* `--git ` — The URL of the templates git repository. The templates must be in a git repository in a "templates" directory
+* `--help ` — Print help information
+* `--tar ` — URL to a tarball in .tar.gz format containing the template(s) to install
+* `--upgrade ` — If present, updates existing templates instead of skipping
+* `--version ` — Print version information
+
+
+
+## `spin templates list`
+
+List the installed templates
+
+**Usage:** `spin templates USAGE:
+ list [OPTIONS]`
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--tag ` — Filter templates matching all provided tags
+* `--verbose ` — Whether to show additional template details in the list
+* `--version ` — Print version information
+
+
+
+## `spin templates uninstall`
+
+Remove a template from your installation
+
+**Usage:** `spin templates USAGE:
+ uninstall `
+
+###### **Arguments:**
+
+* `` — The template to uninstall
+
+###### **Options:**
+
+* `--help ` — Print help information
+* `--version ` — Print version information
+
+
+
+## `spin templates upgrade`
+
+Upgrade templates to match your current version of Spin.
+
+The files of the templates are copied to the local template store: a directory in your data or home directory.
+
+**Usage:** `spin templates USAGE:
+ upgrade [OPTIONS]`
+
+###### **Options:**
+
+* `--all ` — By default, Spin displays the list of installed repositories and prompts you to choose which to upgrade. Pass this flag to upgrade all repositories without prompting
+* `--branch ` — The optional branch of the git repository, if a specific repository is given
+* `--help ` — Print help information
+* `--repo ` — By default, Spin displays the list of installed repositories and prompts you to choose which to upgrade. Pass this flag to upgrade only the specified repository without prompting
+* `--version ` — Print version information
+
+
+
+## `spin up`
+
+Start the Spin application
+
+**Usage:** `spin USAGE:
+ up [OPTIONS]`
+
+###### **Arguments:**
+
+* `` — All other args, to be passed through to the trigger
+
+###### **Options:**
+
+* `--build ` — For local apps, specifies to perform `spin build` (with the default options) before running the application.
+
+ This is ignored on remote applications, as they are already built.
+* `-c`, `--component-id ` — [Experimental] Component ID to run. This can be specified multiple times. The default is all components
+* `--cache-dir ` — Cache directory for downloaded components and assets
+* `--direct-mounts ` — For local apps with directory mounts and no excluded files, mount them directly instead of using a temporary directory.
+
+ This allows you to update the assets on the host filesystem such that the updates are visible to the guest without a restart. This cannot be used with registry apps or apps which use file patterns and/or exclusions.
+* `-e`, `--env ` — Pass an environment variable (key=value) to all components of the application
+* `-f`, `--from ` — The application to run. This may be a manifest (spin.toml) file, a directory containing a spin.toml file, a remote registry reference, or a Wasm module (a .wasm file). If omitted, it defaults to "spin.toml"
+* `-h`, `--help `
+* `--help ` — Print help information
+* `-k`, `--insecure ` — Ignore server certificate errors from a registry
+* `--temp ` — Temporary directory for the static assets of the components
+* `--version ` — Print version information
+
+
+
+## `spin watch`
+
+Build and run the Spin application, rebuilding and restarting it when files change
+
+**Usage:** `spin USAGE:
+ watch [OPTIONS] [UP_ARGS]...`
+
+###### **Arguments:**
+
+* `` — Arguments to be passed through to spin up
+
+###### **Options:**
+
+* `-c`, `--clear ` — Clear the screen before each run
+* `-d`, `--debounce ` — Set the timeout between detected change and re-execution, in milliseconds
+
+ Default value: `100`
+* `-f`, `--from ` — The application to watch. This may be a manifest (spin.toml) file, or a directory containing a spin.toml file. If omitted, it defaults to "spin.toml"
+* `--help ` — Print help information
+* `--skip-build ` — Only run the Spin application, restarting it when build artifacts change
+* `--version ` — Print version information
+
+
+
+
+
+
+ This document was generated automatically by
+ clap-markdown.
+
diff --git a/content/v4/contributing-docs.md b/content/v4/contributing-docs.md
new file mode 100644
index 00000000..1fb21ca6
--- /dev/null
+++ b/content/v4/contributing-docs.md
@@ -0,0 +1,526 @@
+title = "Contributing to Docs"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/contributing-docs.md"
+keywords = "contribute contributing"
+
+---
+
+- [Technical Documentation Types](#technical-documentation-types)
+ - [1. How-To Guides](#1-how-to-guides)
+ - [2. Reference](#2-reference)
+ - [3. Explanation](#3-explanation)
+- [Technical Documentation Procedure](#technical-documentation-procedure)
+ - [1. Fork the Repository](#1-fork-the-repository)
+ - [2. Clone the Fork](#2-clone-the-fork)
+ - [3. Create New Branch](#3-create-new-branch)
+ - [4. Add Upstream](#4-add-upstream)
+ - [5. Code Blocks, Annotations and Table of Contents (ToC)](#5-code-blocks-annotations-and-table-of-contents-toc)
+ - [6.1 Checking Your Content - Using NPM](#61-checking-your-content---using-npm)
+ - [6.2 Indexing Your Content](#62-indexing-your-content)
+ - [6.3 Increasing Search Visibility For Your Content](#63-increasing-search-visibility-for-your-content)
+ - [6.4 The Edit On GitHub Button](#64-the-edit-on-github-button)
+ - [6.5 How To Properly Edit CSS Styles](#65-how-to-properly-edit-css-styles)
+ - [6.6 Checking Your Content - Using Bartholomew's CLI](#66-checking-your-content---using-bartholomews-cli)
+ - [6.7 Checking Your Content - Preview a Documentation Page on Localhost](#67-checking-your-content---preview-a-documentation-page-on-localhost)
+ - [6.8 Scheduling Menu Items for Timed Release](#68-scheduling-menu-items-for-timed-release)
+ - [7. Checking Web Pages](#7-checking-web-pages)
+ - [8. Add Changes](#8-add-changes)
+ - [9. Commit Changes](#9-commit-changes)
+ - [10. Push Changes](#10-push-changes)
+ - [11. Create a Pull Request](#11-create-a-pull-request)
+
+We are delighted that you are interested in making our documentation better. Thank you! We welcome and appreciate contributions of all types — opening issues, fixing typos, adding examples, one-liner code fixes, tests, or complete features.
+
+Any contribution and interaction on any spinframework project MUST follow our [code of conduct](https://github.com/spinframework/governance). Thank you for being part of an inclusive and open community!
+
+Below are a few pointers designed to help you contribute.
+
+## Technical Documentation Types
+
+The following points will help guide your contribution from a resource-type perspective; essentially we would really appreciate you creating and contributing any of the following 3 resource types.
+
+### 1. How-To Guides
+
+How-to guides are oriented towards showing a user how to solve a problem, which leads them to be able to achieve their own goal. The how-to guide will follow a series of logical steps. Think of it as providing a recipe for the user's creativity. For example, you can show a user how to [develop a Spin application](/writing-apps.md) without telling them what the application must do; that is up to the user's imagination.
+
+### 2. Reference
+
+Reference resources are merely a dry description; describing the feature in its simplest form. An example of a reference resource is the [Spin application manifest reference](./manifest-reference). You will notice that the Manifest Reference page simply lists all of the manifest entries and available options.
+
+### 3. Explanation
+
+An explanation resource is written using a deep-dive approach i.e. providing a deep explanation with the view to impart a deep understanding of a particular concept, feature or product. You may find your contribution is so in-depth that it becomes a long form article like a blog post. If that is the case, consider an addition to the Spin Blog.
+
+## Technical Documentation Procedure
+
+### 1. Fork the Repository
+
+The first step is to fork the [spin-docs repository](https://github.com/spinframework/spin-docs), from spinframework's GitHub, to your own GitHub account.
+
+Ensure that you are forking the spin-docs repository **to your own** GitHub account; where you have full editing privileges.
+
+### 2. Clone the Fork
+
+Copy the URL from the UI in readiness for running the `git clone` command.
+
+Go ahead and clone the new fork that you just created (the one which resides in your own GitHub account):
+
+
+
+```bash
+$ cd ~
+$ git clone git@github.com:yourusername/spin-docs.git
+$ cd developer
+```
+
+### 3. Create New Branch
+
+Create a new branch that will house all of your changes for this specific contribution:
+
+
+
+```bash
+$ git checkout -b my_new_branch
+```
+
+### 4. Add Upstream
+
+Create a new remote for the upstream (a pointer to the original repository to which you are contributing):
+
+
+
+```bash
+$ git remote add upstream https://github.com/spinframework/spin-docs
+```
+
+### 5. Code Blocks, Annotations and Table of Contents (ToC)
+
+It is highly recommended that you use either the `` or the `` annotation before each of your code blocks, and that each code block defines the appropriate [syntax highlighting](https://rdmd.readme.io/docs/code-blocks#language-support). The annotation can be skipped for code blocks with example code snippets i.e. non-terminal or generic output examples.
+
+**Selective copy**
+
+The selective copy annotation (``) is intended for use when communicating code and/or CLI commands for the reader to copy and paste. The selective copy annotation allows the reader to see the entire code block (both commands and results) but only copies the lines that start with `$` into the reader's clipboard (minus the `$`) when the user clicks the copy button. For example, copying the following code block will only copy `echo "hello"` into your clipboard, for pasting.
+
+
+
+```bash
+$ echo "hello"
+hello
+```
+
+> Note: If the command, that starts with `$`, is deliberately spread over two lines (by escaping the newline character), then the copy mechanism will still copy the second line which is technically still part of that single command.
+
+**No copy**
+
+The no copy annotation (``) precedes a code block where no copy and pasting of code is intended. If using the no copy attribute please still be sure to add the appropriate syntax highlighting to your code block (for display purposes). For example:
+
+
+
+Please find copyable snippet below, for your convenience:
+
+````
+
+
+```text
+Some generic code not intended for copying/pasting
+```
+````
+
+The above markdown will render the following code block on the web page:
+
+
+
+```text
+Some generic code not intended for copying/pasting
+```
+
+**Non-selective copy** - just a straight copy without any additional features.
+
+If you want the code in a code block to be copyable with no "smarts" to remove the `$` then you can just simply leave out the annotation altogether. A code block in markdown will be copyable without smarts just as is.
+
+**Multi-tab code blocks**
+
+Examples of multi-tab blocks can be seen in the [Spin installer documentation](./install#installing-spin). The above examples demonstrate how tabs can either represent platforms i.e. `Windows`, `Linux` and `macOS` or represent specific programming languages i.e. `Rust`, `JavaScript` and `Golang` etc. Here is a brief example of how to implement multi-tab code blocks when writing technical documentation for this site, using markdown.
+
+The first step to implementing multi-tab code blocks is placing the `enable_shortcodes = true` configuration at the start of the `.md` file. Specifically, in the `.md` file's frontmatter.
+
+The markup to create tabs in markdown is as follows
+
+```
+{{ tabs "os" }}
+
+{{ startTab "Windows"}}
+
+To list files on windows use `dir`
+
+
+
+\`\`\`bash
+$ dir hello_spin
+\`\`\`
+and script in windows have the extension `.bat`
+
+
+
+\`\`\`bash
+hello.bat
+test.bat
+\`\`\`
+
+{{ blockEnd }}
+
+{{ startTab "Linux"}}
+
+To list files on linux use `ls`
+
+
+
+\`\`\`bash
+$ ls
+\`\`\`
+
+and script in linux have the extension `.sh`
+
+
+
+\`\`\`bash
+hello.sh
+test.sh
+\`\`\`
+
+{{ blockEnd }}
+{{ blockEnd }}
+```
+
+**Note**: Existing documentation will already be using class names for code block `tabs` and `startTab` i.e. `{{ tabs "os" }}` and `{{ startTab "Windows"}}` respectively. Please consult the following `tabs` and `startTab` class names that are already in use (before creating your own). If you need to create a new class name (because one does not already exist) please add it to the list below as part of the pull request that contains your code block contribution.
+
+**tabs**:
+- `gh-interact`
+- `os`
+- `platforms`
+- `sdk-type`
+- `spin-version`
+- `cloud-plugin-version`
+
+**startTab**
+- `Azure AKS`
+- `C#`
+- `Docker Desktop`
+- `Generic Kubernetes`
+- `GitHub CLI`
+- `GitHub UI`
+- `K3d`
+- `Linux`
+- `macOS`
+- `Python`
+- `Rust`
+- `TinyGo`
+- `TypeScript`
+- `v0.9.0`
+- `v0.10.0`
+- `v1.0.0`
+- `v1.1.0`
+- `v1.2.0`
+- `v0.1.0`
+- `v0.1.1`
+- `Windows`
+
+The next section covers the highly recommended use of ToCs.
+
+**Implementing a Table of Contents (ToC)**
+
+If you create content with many headings it is highly recommended to place a ToC in your markdown file. There are excellent extensions (such as this Visual Studio Code Extension called [markdown-all-in-one](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) which will automatically generate your ToC).
+
+### 6.1 Checking Your Content - Using NPM
+
+Once you are satisfied with your contribution, you can programmatically check your content.
+
+If you have not done so already, please go ahead and perform the `npm ci` (npm clean install) command; to enable Node dependencies such as `markdownlint-cli2`. Simply run the following command, from the root of the spin-docs repository:
+
+
+
+```bash
+$ npm ci
+```
+
+On top of the Node dependencies the `timeout` executable must be installed on your system and added to the `PATH` environment variable. The `timeout` executable is included in the [gnu coreutils package](https://www.gnu.org/software/coreutils/) which should be present in most Linux distributions.
+
+On macOS you can install the `timeout` binary using the Homebrew package manager as shown below:
+
+
+
+```bash
+$ brew install coreutils
+```
+
+Having all dependencies installed, you can now check for broken links (which takes several minutes) and also lint your markdown files. Simply run the following command, from the root of the spin-docs repository:
+
+
+
+```bash
+$ npm run test
+```
+
+**Hint:** Optionally you can run only the linter with the following command:
+
+
+
+```bash
+# Example of how to lint all Markdown files in a local folder (in this case the spin folder)
+npx markdownlint-cli2 content/*.md \"#node_modules\"
+# Example of how to lint a local Markdown file
+npx markdownlint-cli2 content/install.md \"#node_modules\"
+```
+
+**Note:** Whilst the `npm run test` command (which lints and also programmatically checks all URLs) does take extra time to complete it **must** be utilized before you [push changes](#10-push-changes); preventing the potential pushing of broken URLs to the documentation site.
+
+### 6.2 Indexing Your Content
+
+The Spin documentation site implements in-house search. A new index is automatically generated for you when your contribution is merged into the documentation repository. This is done via a GitHub action. The following section explains how to alter content to increase search visibility for your content.
+
+### 6.3 Increasing Search Visibility For Your Content
+
+The built-in search functionality is based on the indexing of individual words in each markdown file, which works well most of the time. However, there are a couple of scenarios where you _may_ want to deliberately increase search visibility for your content.
+
+**Word Mismatch**
+
+Words in a documentation markdown file may not be words that are searched for by a user. For example, you may write about "different HTTP listening options" whereas a user may only ever try to find that specific content using a search phrase like "alternate port". If you are aware of typical user search phrases it is always recommended to craft your content to include any predictable user search phrases. However, in the rare case of a mismatch between words in your content and the words a user searches for, you can utilize use the `keywords` string in the `[extra]` section of your document's frontmatter to increase visibility. For example, the following code block shows frontmatter that helps a user find your documentation page (when the user searches for `port`):
+
+```markdown
+[extra]
+keywords = "port ports"
+```
+
+Adding a word to the `keywords` string of a page overrides the built-in search functionality by at least one order of magnitude. Adding a word to the `keywords` string may displace other content, so please use it only if necessary.
+
+**Homing in on specific content**
+
+The `keywords` string takes users to the start of a page. In some cases, this is not ideal. You may want to home in on specific content to resolve a search query.
+
+If a search term relates to a specific part of a page, you may use the following syntax anywhere in the body of your markdown file, and the user's search action will direct them straight to the previous heading (nearest heading above the `@searchTerm` syntax).
+
+```markdown
+
+```
+
+
+
+When using the above `@searchTerm` feature, please note the following:
+- the words must be separated by a space i.e.
+- these keywords will be boosted in the search results by at least one order of magnitude; so please use them with caution, so as not to displace other valid pages containing similar content.
+
+Example: If you search for the word "homing", the results will point you to the previous heading in this specific section of the documentation.
+
+
+
+### 6.4 The Edit On GitHub Button
+
+Each markdown file in the documentation requires a link to its GitHub page for the site to correctly render the "Edit on GitHub" button for that page.
+
+
+
+If you create a new markdown file and/or you notice a file without the explicit GitHub URL, please add a URL entry to the [extra] section. For example, the following `url` is required for this page that you are reading (you can check the raw markdown contents of this page to see this example):
+
+```
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/contributing-docs.md"
+```
+
+### 6.5 How To Properly Edit CSS Styles
+
+> The following section (the running of the `npm run styles` command) is not necessary unless you are editing styles i.e. updating `.scss` files, in order to generate new `.css` files, as part of your contribution.
+
+Directly editing `.css` files is not recommended, because `.css` files are overwritten. Instead, if you would like to make and test a new/different style please go ahead and update the appropriate `.scss` file. The following command will automatically update the `.css` file that is relevant to the `.scss` file that you are editing:
+
+
+
+```bash
+$ npm run styles
+```
+
+The above command is designed to be run in the background; enabling you to view your design changes (that are reflected in the `.css`) while you are editing the `.scss` in real-time. If you are not running this command in the background (i.e. just deliberately regenerating the `.css` files once), then the above command can be stopped by pressing `Ctrl` + `C`.
+
+### 6.6 Checking Your Content - Using Bartholomew's CLI
+
+The Bartholomew Command Line Interface (CLI) Tool is called `bart`. The `bart` CLI is a tool that simplifies working with Bartholomew projects, like this documentation site. The `bart` CLI is handy to ensure quality assurance of new and existing content. Installing the CLI is a cinch, so please go ahead and use it when contributing.
+
+To build the Bartholomew CLI from source perform the following commands:
+
+
+
+```bash
+$ cd ~
+$ git clone https://github.com/fermyon/bartholomew.git
+$ cd ~/bartholomew
+$ make bart
+```
+
+Once built, you will find the `bart` CLI executable in the `~/bartholomew/target/release` directory. However, for convenience it would be a great idea to go ahead and add the `bart` executable to your system path, for example:
+
+
+
+```bash
+$ sudo mv ~/bartholomew/target/release/bart /usr/local/bin/
+```
+
+Once installed, you can use the CLI's `--help` flag to learn more. For example:
+
+
+
+```bash
+$ bart --help
+bart 0.6.0
+The Bartholomew CLI
+
+USAGE:
+ bart
+
+FLAGS:
+ -h, --help Prints help information
+ -V, --version Prints version information
+
+SUBCOMMANDS:
+ calendar Print the content calendar for a Bartholomew website
+ check Check content and identify errors or warnings
+ help Prints this message or the help of the given subcommand(s)
+ new Create a new page or website from a template
+```
+
+Let's take a quick look at how you can use the `bart` CLI to check any content that you are wanting to contribute.
+
+### 6.7 Checking Your Content - Preview a Documentation Page on Localhost
+
+You can host your changes to the documentation on your own machine (localhost) by using the following `spin` commands:
+
+
+
+```bash
+$ npm ci
+$ cd spin-up-hub
+$ npm ci
+$ cd ..
+$ spin build
+$ spin up -e "PREVIEW_MODE=1"
+```
+
+> Please note: using the `PREVIEW_MODE=1` as part of a `spin` command is safe on localhost and allows you to view the content (even if the `date` setting in the content's `.md` is set to a future date). It is often the case that you will be checking content before the publishing date via your system. The documentation's manifest file `spin.toml` has the `PREVIEW_MODE` set to `0` i.e. `environment = { PREVIEW_MODE = "0" }`. This `spin.toml` file is correct for a production environment and should always be `0` (so that the CMS adheres to the publishing `date` setting for content on the public site). Simply put, you can use `PREVIEW_MODE=1` safely in your command line on your localhost but you should never update the `spin.toml` file (in this regard).
+
+### 6.8 Scheduling Menu Items for Timed Release
+
+As mentioned above, all pages (`.md` files) in the documentation have a UTC date i.e. `date = "2023-07-25T17:26:00Z"`. The `date` is a page scheduling mechanism whereby each page is only displayed if the `date` has elapsed. Menu items (found in the `/templates/*.hbs` files) that relate to a scheduled page can also be scheduled (so the specific menu item and its associated page appear at the same time). Simply envelope the menu item with the following `if` syntax to synchronize the appearance of the menu item with the related page:
+
+
+
+```
+{{#if (timed_publish "2023-07-25T17:26:00Z" env.PREVIEW_MODE)}}
+ // Scheduled menu item for timed release
+{{/if}}
+```
+
+> In order to keep the code tidy and readable it is advised to remove the `if` logic (from the `.hbs` file) that wraps the content, once the `timed_publish` has elapsed.
+
+### 7. Checking Web Pages
+
+The `bart check` command can be used to check the content. Simply pass in the content as a parameter. The documentation [uses shortcodes](https://developer.fermyon.com/bartholomew/shortcodes), so always pass `--shortcodes ./shortcodes` as shown below:
+
+
+
+```bash
+$ bart check --shortcodes ./shortcodes content/variables.md
+shortcodes: registering alert
+shortcodes: registering details
+shortcodes: registering tabs
+shortcodes: registering startTab
+shortcodes: registering blockEnd
+✅ content/variables.md
+```
+
+> Note: `using a wildcard `*` will check a whole directory via a single command. For example, running `bart check --shortcodes ./shortcodes content/*` will check all markdown files in the Spin project's documentation section.
+
+### 8. Add Changes
+
+Once your changes have been checked, go ahead and add your changes by moving to a top-level directory, under which your changes exist i.e. `cd ~/spin-docs`.
+
+Add your changes by running the following command, from the root of the spin-docs repository:
+
+
+
+```bash
+$ git add
+```
+
+### 9. Commit Changes
+
+All commits must be signed off *and* GPG-signed with a GitHub verification key. The rest of this section is primarily for contributors not familiar with signing, and describes how to configure signing, and how to sign commits.
+
+First, ensure that your Git installation is configured sufficiently so that you can `--signoff` as part of the `git commit` command. Typically, you need the `user.name` and `user.email` to be configured in your Git session. You can check if these are set by typing `git config --list`.
+
+If you need to set these values please use the following commands:
+
+
+
+```bash
+$ git config user.name "yourusername"
+```
+
+
+```bash
+$ git config user.email "youremail@somemail.com"
+```
+
+You must also set up a GPG verification key on your GitHub account, and add this to your Git settings. For more information about setting up a GPG verification key, see GitHub's documentation about [adding a GPG key to your GitHub account](https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account) and [telling your Git client about your GPG key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
+
+With all this set up, type the following commit command, which both _signs off_ (--signoff) and _cryptographically signs_ the data (-S), and leaves a short commit message (-m):
+
+
+
+```bash
+$ git commit -S --signoff -m "Updating documentation"
+```
+
+> Note: the `--signoff` option only adds a Signed-off-by trailer by the committer at the end of the commit log message. You must also use the `-S` option which will GPG-sign your commits. For more information about GPG-signing commits see [the GitHub documentation page for signing commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits).
+
+### 10. Push Changes
+
+At this stage, it is a good idea to just quickly check what GitHub thinks the origin is. For example, if we type `git remote -v` we can see that the origin is our repo; which we a) forked the original repo into and b) which we then cloned to our local disk so that we could edit:
+
+
+
+```bash
+$ git remote -v
+```
+
+The above command will return output similar to the following:
+
+```bash
+origin git@github.com:yourusername/spin-docs.git (fetch)
+origin git@github.com:yourusername/spin-docs.git (push)
+upstream https://github.com/spinframework/spin-docs (fetch)
+upstream https://github.com/spinframework/spin-docs (push)
+```
+
+Once you are satisfied go ahead and push your changes:
+
+
+
+```bash
+$ git push -u origin my_new_branch
+```
+
+### 11. Create a Pull Request
+
+If you return to your GitHub repository in your browser, you will notice that a PR has automatically been generated for you.
+
+Clicking on the green “Compare and pull request” button will allow you to add a title and description as part of the PR.
+
+
+
+You can also add any information in the textbox provided below the title. For example, screen captures and/or code/console/terminal snippets of your contribution working correctly and/or tests passing etc.
+
+Once you have finished creating your PR, please keep an eye on the PR; answering any questions as part of the collaboration process.
+
+**Thank You**
+
+Thanks for contributing.
diff --git a/content/v4/contributing-spin.md b/content/v4/contributing-spin.md
new file mode 100644
index 00000000..6e956527
--- /dev/null
+++ b/content/v4/contributing-spin.md
@@ -0,0 +1,139 @@
+title = "Contributing to Spin"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/contributing-spin.md"
+
+---
+- [Spin Slack Channel](#spin-slack-channel)
+- [Developer Community Calls](#developer-community-calls)
+- [Code of Conduct](#code-of-conduct)
+- [Making Code Contributions to Spin](#making-code-contributions-to-spin)
+- [Before You Commit](#before-you-commit)
+- [Committing and Pushing Your Changes](#committing-and-pushing-your-changes)
+
+We are delighted that you are interested in making Spin better! Thank you!
+
+This document will guide you through making your first contribution to the project.
+We welcome and appreciate contributions of all types — opening issues, fixing
+typos, adding examples, one-liner code fixes, tests, or complete features.
+
+## Spin Slack Channel
+
+Join us in the [Spin CNCF Slack Channel](https://cloud-native.slack.com/archives/C089NJ9G1V0)! This is a great place to ask questions, meet people in the community, and share what you are working on. If you're not already signed up for the CNCF Slack, request an invite [here](https://communityinviter.com/apps/cloud-native/cncf).
+
+## Developer Community Calls
+
+
+
+
+
+
+>> _Recent contributors to Spin, past 30 days. Widget courtesy of OSSinsight.io._
+
+Each Monday at 2:30pm UTC and 9:00pm UTC (alternating), we meet to discuss Spin issues, roadmap, and ideas in our Spin Project Meetings. The Spin Project follows an [open planning process](https://www.fermyon.com/blog/moving-to-a-fully-open-planning-process-for-the-spin-project) - anyone who is interested is welcome to join the discussion. [Subscribe to this Google Calendar](https://calendar.google.com/calendar/u/1?cid=c3Bpbi5tYWludGFpbmVyc0BnbWFpbC5jb20) for meeting dates.
+
+The [Spin Project Meeting Agenda is a public document](https://docs.google.com/document/d/1EG392gb8Eg-1ZEPDy18pgFZvMMrdAEybpCSufFXoe00/edit?usp=sharing). The document contains a rolling agenda with the date and time of each meeting, the Zoom link, and topics of discussion for the day. You will also find the meeting minutes for each meeting and the link to the recording. If you have something you would like to demo or discuss at the project meeting, we encourage you to add it to the agenda.
+
+## Code of Conduct
+
+First, any contribution and interaction on the Spin project MUST follow our
+[code of conduct](https://github.com/spinframework/spin/blob/main/CODE_OF_CONDUCT.md). Thank you for being
+part of an inclusive and open community!
+
+If you plan on contributing anything complex, please go through the issue and PR
+queues first to make sure someone else has not started working on it. If it
+doesn't exist already, please open an issue so you have a chance to get feedback
+from the community and the maintainers before you start working on your feature.
+
+## Making Code Contributions to Spin
+
+The following guide is intended to make sure your contribution can get merged as
+soon as possible. First, make sure you have Rust installed.
+
+
+
+After [installing Rust](https://www.rust-lang.org/tools/install) please ensure the `wasm32-wasip2` and
+ `wasm32-unknown-unknown` targets are configured. For example:
+
+```bash
+rustup target add wasm32-wasip2 && rustup target add wasm32-unknown-unknown
+```
+
+In addition, make sure you have the following prerequisites configured:
+
+- [`rustfmt`](https://github.com/rust-lang/rustfmt)
+- [`clippy`](https://github.com/rust-lang/rust-clippy)
+- `make`
+- [`rust-analyzer`](https://rust-analyzer.github.io/) extension (for Visual Studio Code users)
+- [GPG signature verification for your GitHub commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) and remember to use a sign-off message (`git commit -S -s`) on each of your commits
+
+Once you have set up the prerequisites and identified the contribution you want to make to Spin, make sure you can correctly build the project:
+
+
+
+```bash
+# clone the repository
+$ git clone https://github.com/spinframework/spin && cd spin
+# add a new remote pointing to your fork of the project
+$ git remote add fork https://github.com//spin
+# create a new branch for your work
+$ git checkout -b
+
+# build the Spin CLI
+$ cargo build
+
+# make sure compilation is successful
+$ ./target/debug/spin --help
+
+# run the tests and make sure they pass
+$ make test
+```
+
+Now you should be ready to start making your contribution. To familiarize
+yourself with the Spin project, please read the
+[document about extending Spin](./extending-and-embedding.md). Since most of Spin is implemented in
+Rust, we try to follow the common Rust coding conventions (keep an eye on the
+recommendations from Clippy!). If applicable, add unit or integration tests to
+ensure your contribution is correct.
+
+## Before You Commit
+
+* Format the code (`cargo fmt`)
+* Run Clippy (`cargo clippy`)
+* Run the `lint` task (`make lint`)
+* Build the project and run the tests (`make test`)
+
+Spin enforces lints and tests as part of continuous integration - running them locally will save you a round-trip to your pull request!
+
+If everything works locally, you're ready to commit your changes.
+
+## Committing and Pushing Your Changes
+
+We require commits to be signed both with an email address and with a GPG signature.
+
+> Because of the way GitHub runs enforcement, the GPG signature isn't checked until _after_ all tests have run. Be sure to GPG sign up front, as it can be a bit frustrating to wait for all the tests and _then_ get blocked on the signature!
+
+
+
+```bash
+$ git commit -S -s -m ""
+```
+
+> Some contributors like to follow the [Conventional Commits](https://github.com/conventional-commits) convention for commit messages.
+
+We try to only keep useful changes as separate commits — if you prefer to commit
+often, please
+[cleanup the commit history](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History)
+before opening a pull request.
+
+Once you are happy with your changes you can push the branch to your fork:
+
+
+
+```bash
+# "fork" is the name of the git remote pointing to your fork
+$ git push fork
+```
+
+Now you are ready to create a pull request. Thank you for your contribution!
diff --git a/content/v4/deploying.md b/content/v4/deploying.md
new file mode 100644
index 00000000..656a64b8
--- /dev/null
+++ b/content/v4/deploying.md
@@ -0,0 +1,37 @@
+title = "Deployment Options"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/deploying.md"
+
+---
+
+This page covers known deployment options for Spin applications. [Please help keep this page up to date if we've missed one.](./contributing-docs.md)
+
+## Spin command line
+
+You can run the Spin CLI on a server just as you do on your development workstation. See the [Running Applications guide](/running-apps) for more details.
+
+> The command line provides no recovery or failover features. This may be fine for non-essential or private projects, but for anything that needs higher reliability, you'll need to add other components to provide that.
+
+## Kubernetes
+
+You can deploy Spin applications to a Kubernetes cluster with [SpinKube](https://www.spinkube.dev/docs/overview/) - an open source project that streamlines the experience of developing, deploying, and operating Wasm workloads on Kubernetes. Like Spin, SpinKube is a CNCF Sandbox project.
+
+## Microsoft Azure Kubernetes Service
+
+[The community `spin azure` plugin](https://github.com/Mossaka/spin-plugin-azure) streamlines deploying SpinKube and Spin applications to an AKS cluster. It provides for creating SpinKube clusters, setting up workload identity, CosmosDB integration, and application deployment.
+
+**To install the plugin:** `spin plugins install azure`
+
+**To create a SpinKube cluster:** `spin azure cluster create`
+
+**To deploy an application:** [see the workflow here](https://github.com/Mossaka/spin-plugin-azure?tab=readme-ov-file#workflow-explanation)
+
+## Akamai Functions
+
+[Akamai Functions](https://developer.fermyon.com/wasm-functions) is an edge environment hosted in the Akamai Cloud. It enables you to run Spin applications with global distribution.
+
+**To install the plugin:** `spin plugins install aka`
+
+**To deploy an application:** `spin aka deploy`
diff --git a/content/v4/distributing-apps.md b/content/v4/distributing-apps.md
new file mode 100644
index 00000000..91761c2a
--- /dev/null
+++ b/content/v4/distributing-apps.md
@@ -0,0 +1,154 @@
+title = "Publishing and Distribution"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/distributing-apps.md"
+
+---
+- [Logging Into a Registry](#logging-into-a-registry)
+ - [Logging In Using a Token](#logging-in-using-a-token)
+ - [Fallback Credentials](#fallback-credentials)
+- [Publishing a Spin Application to a Registry](#publishing-a-spin-application-to-a-registry)
+ - [Publishing and Build Profiles](#publishing-and-build-profiles)
+- [Running Published Applications](#running-published-applications)
+ - [Running Published Applications by Digest](#running-published-applications-by-digest)
+ - [Pulling a Published Application](#pulling-a-published-application)
+- [Signing Spin Applications and Verifying Signatures](#signing-spin-applications-and-verifying-signatures)
+
+If you would like to publish a Spin application, so that other users can run it, you can do so using a _container registry_.
+
+{{ details "What's all this about containers?" "The registry protocol was originally created to publish and distribute Docker containers. Over time, registries have evolved to host other kinds of artifact - see [the OCI registry artifacts project](https://github.com/opencontainers/artifacts) for more information. However, the term remains, in services such as GitHub Container Registry or AWS Elastic Container Registry, and in the generic description _OCI (Open Container Initiative) registries_. When you use a 'container' registry to publish and distribute Spin applications, there are no actual containers involved at all!" }}
+
+Many cloud services offer public registries. Examples include GitHub Container Registry, Docker Hub, or Amazon Elastic Container Registry. These support both public and private distribution. You can also run your own registry using open source software.
+
+## Logging Into a Registry
+
+Before you can publish to a registry, or run applications whose registry artifacts are private, you must log in to the registry. This example shows logging into the GitHub Container Registry, `ghcr.io`:
+
+
+
+```bash
+$ spin registry login ghcr.io
+```
+
+If you don't provide any options to `spin registry login`, it prompts you for a username and password.
+
+### Logging In Using a Token
+
+In a non-interactive environment such as GitHub Actions, you will typically log in using a token configured in the environment settings, rather than a password. To do this, use the `--password-stdin` flag, and `echo` the token value to the login command's standard input. This example shows logging into GHCR from a GitHub action:
+
+
+
+```bash
+$ echo "$\{{ secrets.GITHUB_TOKEN }}" | spin registry login ghcr.io --username $\{{ github.actor }} --password-stdin
+```
+
+Other environments will have different ways of referring to the token and user but the pattern remains the same.
+
+### Fallback Credentials
+
+If you have logged into a registry using `docker login`, but not using `spin registry login`, Spin will fall back to your Docker credentials.
+
+## Publishing a Spin Application to a Registry
+
+To publish an application to a registry, use the `spin registry push` command. You must provide a _reference_ for the published application. This is a string whose format is defined by the registry standard, and generally consists of `//:`. (In specific circumstances you may be able to omit the username and/or the version. If you want more detail on references, see the OCI documentation.)
+
+> Remember you will (usually) need to be logged in to publish to a registry.
+
+Here is an example of pushing an application to GHCR:
+
+
+
+```bash
+$ spin registry push ghcr.io/alyssa-p-hacker/hello-world:v1
+Pushed with digest sha256:06b19
+```
+
+Notice that the username is part of the reference; the registry does not infer it from the login. Also notice that the version is specified explicitly; Spin does not infer it from the `spin.toml` file.
+
+> Whether newly uploaded artifacts are private or public depends on the registry. See your registry documentation. This will also tell you how to change the visibility if the default is not what you want.
+
+### Publishing and Build Profiles
+
+If your application has different [build profiles](./build.md#building-with-profiles), and you want to publish it using a profile other than the default, you must specify that profile when you publish. The pushed image will contain _only_ the selected profile. (You won't be able to specify a profile when you run it from the registry.)
+
+
+
+```bash
+# Push the application in its default profile
+$ spin registry push --build ghcr.io/alyssa-p-hacker/hello-world:v1
+# Push the application in its debug profile
+$ spin registry push --profile debug --build ghcr.io/alyssa-p-hacker/hello-world-debug:v1
+```
+
+## Running Published Applications
+
+To run a published application from a registry, use `spin up -f` and pass the registry reference:
+
+
+
+```bash
+$ spin up -f ghcr.io/alyssa-p-hacker/hello-world:v1
+```
+
+> Remember that if the artifact is private you will need to be logged in, with permission to access it.
+
+> Spin optimizes downloads using a local [registry cache](./cache). When running an application from a remote registry, Spin always tries to check the registry for updates, even if the application has already been pulled. However, content files that are already pulled will not be re-downloaded. This applies even if they were downloaded as part of a different application.
+
+### Running Published Applications by Digest
+
+Registry versions are mutable; that is, the owner of an application can change which build the `:v1` label points to at any time. If you want to run a specific build of the package, you can refer to it by _digest_. This is similar to a Git commit hash: it is immutable, meaning the same digest always gets the exact same data, no matter what the package owner does. To do this, use the `@sha256:...` syntax instead of the `:v...` syntax:
+
+
+
+```bash
+$ spin up -f ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19
+```
+
+### Pulling a Published Application
+
+`spin up` automatically downloads the application from the registry. If you want to manually download the application, without running it, use the `spin registry pull` command:
+
+
+
+```bash
+$ spin registry pull ghcr.io/alyssa-p-hacker/hello-world:v1
+$ spin registry pull ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19
+```
+
+> Downloaded applications are cached. When run, or pulled again, Spin checks to see if they have changed from the cached copy, and downloads only the changes if any.
+
+## Signing Spin Applications and Verifying Signatures
+
+Because Spin uses the container registry standards to distribute applications, it can also take advantage of tooling built around those standards. Here is an example of using [Cosign and Sigstore](https://docs.sigstore.dev/) to sign and verify a Spin application:
+
+
+
+```bash
+# Push your Spin application to any registry that supports the OCI registry artifacts,
+# such as the GitHub Container Registry, Docker Hub, Azure ACR, or AWS ECR.
+$ spin registry push ghcr.io/alyssa-p-hacker/hello-world:v1
+
+# You can now sign your Spin app using Cosign (or any other tool that can sign
+# OCI registry objects).
+$ cosign sign ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19
+Generating ephemeral keys...
+Retrieving signed certificate...
+tlog entry created with index: 12519542
+Pushing signature to: ghcr.io/alyssa-p-hacker/hello-world
+
+# Someone interested in your application can now use Cosign to verify the signature
+# before running the application.
+$ cosign verify ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19
+Verification for ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19 --
+The following checks were performed on each of these signatures:
+ - The cosign claims were validated
+ - Existence of the claims in the transparency log was verified offline
+ - Any certificates were verified against the Fulcio roots.
+
+# The consumer of your app can now run it from the registry.
+$ spin up -f ghcr.io/alyssa-p-hacker/hello-world@sha256:06b19
+```
+
+> You'll need Cosign 2.0 or above to verify Spin artifacts.
diff --git a/content/v4/dynamic-configuration.md b/content/v4/dynamic-configuration.md
new file mode 100644
index 00000000..309f8fcd
--- /dev/null
+++ b/content/v4/dynamic-configuration.md
@@ -0,0 +1,423 @@
+title = "Runtime Configuration"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/dynamic-configuration.md"
+
+---
+
+- [Application Variables Runtime Configuration](#application-variables-runtime-configuration)
+ - [Environment Variable Provider](#environment-variable-provider)
+ - [Vault Application Variable Provider](#vault-application-variable-provider)
+ - [Vault Application Variable Provider Example](#vault-application-variable-provider-example)
+ - [Azure Key Vault Application Variable Provider](#azure-key-vault-application-variable-provider)
+ - [Azure Key Vault Application Variable Provider Example](#azure-key-vault-application-variable-provider-example)
+- [Key Value Store Runtime Configuration](#key-value-store-runtime-configuration)
+ - [File Key Value Store Provider](#file-key-value-store-provider)
+ - [Redis Key Value Store Provider](#redis-key-value-store-provider)
+ - [Azure CosmosDB Key Value Store Provider](#azure-cosmosdb-key-value-store-provider)
+ - [AWS DynamoDB Key Value Store Provider](#aws-dynamodb-key-value-store-provider)
+ - [Multiple and Non-Default Key-Value Stores](#multiple-and-non-default-key-value-stores)
+- [SQLite Storage Runtime Configuration](#sqlite-storage-runtime-configuration)
+ - [LibSQL Storage Provider](#libsql-storage-provider)
+- [LLM Runtime Configuration](#llm-runtime-configuration)
+ - [Remote Compute Provider](#remote-compute-provider)
+ - [`default` API](#default-api)
+ - [`open_ai` API](#open_ai-api)
+- [Outbound HTTP Runtime Configuration](#outbound-http-runtime-configuration)
+
+You can change the configuration for Spin application features such as [application variables](./variables),
+[key value storage](./kv-store-api-guide), [SQL storage](./sqlite-api-guide)
+and [Serverless AI](./serverless-ai-api-guide) at the time you run the application,
+requiring no changes to the application code itself.
+
+This runtime configuration data is stored in the `runtime-config.toml` file and passed to `spin up` via the `--runtime-config-file` flag.
+
+{{ details "Why do I need to specify `--runtime-config-file` rather than Spin detecting it automatically?" "Spin uses safe, local defaults for saving your data. The runtime config file can override those. It should be up to you to opt into that override." }}
+
+Let's look at each configuration category in-depth below.
+
+## Application Variables Runtime Configuration
+
+When an application needs the value of an [application variable](./variables), it obtains it from a provider. By default, the only provider Spin considers is the [environment variables provider](#environment-variable-provider). That is, application variables are derived from the environment variables of the Spin process.
+
+You can also tell Spin to use the [Vault provider](#vault-application-variable-provider) and/or the [Azure Key Vault provider](#azure-key-vault-application-variable-provider), by setting these up in the runtime config file.
+
+You can tell Spin to use multiple application variable providers. Spin prioritises them in the order they appear in the runtime config file, with higher-listed providers
+taking precedence. The environment variable provider always has the highest priority.
+
+The provider examples below show how to use or configure each
+provider. For examples on how to access these variables values within your application, see
+[Using Variables from Applications](./variables#using-variables-from-applications).
+
+### Environment Variable Provider
+
+The environment variable provider gets variable values from the `spin` process's
+environment (_not_ the component `environment`). Variable keys are translated
+to environment variables by upper-casing and prepending with `SPIN_VARIABLE_`:
+
+
+
+```bash
+$ export SPIN_VARIABLE_API_KEY="1234" # Sets the `api_key` value.
+$ spin up
+```
+
+If the current directory contains a file named `.env`, then the provider will look there for environment variables not present in the actual environment. Entries in `.env` must follow the same `SPIN_VARIABLE_...` naming convention as 'real' environment variables, and have the same `key=value` format as setting a variable on the command line. For example:
+
+```
+# The .env file can contain comments
+SPIN_VARIABLE_API_KEY="1234"
+```
+
+> The `.env` file must be in the _current_ directory (not necessarily the directory containing the application manifest).
+
+Entries in the `.env` file are _lower_ priority than actual environment variables.
+
+### Vault Application Variable Provider
+
+The Vault application variable provider gets secret values from [HashiCorp Vault](https://www.vaultproject.io/).
+Currently, only the [KV Secrets Engine - Version 2](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2) is supported.
+You can set up the v2 kv secret engine at any mount point and provide Vault information in
+the [runtime configuration](#runtime-configuration) file:
+
+
+
+```toml
+[[config_provider]]
+type = "vault"
+url = "http://127.0.0.1:8200"
+token = "root"
+mount = "secret"
+```
+
+#### Vault Application Variable Provider Example
+
+1. [Install Vault](https://developer.hashicorp.com/vault/tutorials/get-started/install-binary).
+2. Start Vault:
+
+
+
+```bash
+$ vault server -dev -dev-root-token-id root
+```
+
+3. Set a password:
+
+
+
+```bash
+$ export VAULT_TOKEN=root
+$ export VAULT_ADDR=http://127.0.0.1:8200
+$ vault kv put secret/secret value="test_password"
+$ vault kv get secret/secret
+```
+
+4. Go to the [Vault variable provider example](https://github.com/fermyon/enterprise-architectures-and-patterns/tree/main/application-variable-providers/vault-provider) application.
+5. Build and run the `vault-provider` app:
+
+
+
+```bash
+$ spin build
+$ spin up --runtime-config-file runtime-config.toml
+```
+
+6. Test the app:
+
+
+
+```bash
+$ curl localhost:3000 --data "test_password"
+{"authentication": "accepted"}
+```
+
+
+```bash
+$ curl localhost:3000 --data "wrong_password"
+{"authentication": "denied"}
+```
+
+### Azure Key Vault Application Variable Provider
+
+The Azure Key Vault application variable provider gets secret values from [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault).
+
+Currently, only receiving the latest version of a secret is supported.
+
+For authenticating against Azure Key Vault, you must use the client credentials flow. To do so, create a Service Principal (SP) within your Microsoft Entra ID (previously known as Azure Active Directory) and assign the `Key Vault Secrets User` role to the SP on the scope of your Azure Key Vault instance.
+
+You can set up Azure Key Vault application variable provider in
+the [runtime configuration](#runtime-configuration) file:
+
+
+
+```toml
+[[config_provider]]
+type = "azure_key_vault"
+vault_url = "https://spin.vault.azure.net/"
+client_id = "12345678-1234-1234-1234-123456789012"
+client_secret = "some.generated.password"
+tenant_id = "12345678-1234-1234-1234-123456789012"
+authority_host = "AzurePublicCloud"
+```
+
+#### Azure Key Vault Application Variable Provider Example
+
+1. Deploy Azure Key Vault:
+
+
+
+```bash
+# Variable Definition
+$ KV_NAME=spin123
+$ LOCATION=germanywestcentral
+$ RG_NAME=rg-spin-azure-key-vault
+
+# Create an Azure Resource Group and an Azure Key Vault
+$ az group create -n $RG_NAME -l $LOCATION
+$ az keyvault create -n $KV_NAME \
+ -g $RG_NAME \
+ -l $LOCATION \
+ --enable-rbac-authorization true
+
+# Grab the Azure Resource Identifier of the Azure Key Vault instance
+$ KV_SCOPE=$(az keyvault show -n $KV_NAME -g $RG_NAME -otsv --query "id")
+```
+
+2. Add a Secret to the Azure Key Vault instance:
+
+
+
+```bash
+# Grab the ID of the currently signed in user in Azure CLI
+$ CURRENT_USER_ID=$(az ad signed-in-user show -otsv --query "id")
+
+# Make the currently signed in user a "Key Vault Secrets Officer"
+# on the scope of the new Azure Key Vault instance
+$ az role assignment create --assignee $CURRENT_USER_ID \
+ --role "Key Vault Secrets Officer" \
+ --scope $KV_SCOPE
+
+# Create a test secret called `secret` in the Azure Key Vault instance
+$ az keyvault secret set -n secret --vault-name $KV_NAME --value secret_value --onone
+```
+
+3. Create a Service Principal and Role Assignment for Spin:
+
+
+
+```bash
+$ export SP_NAME=sp-spin
+
+# Create the SP
+$ SP=$(az ad sp create-for-rbac -n $SP_NAME -ojson)
+
+# Populate local shell variables from the SP JSON
+$ CLIENT_ID=$(echo $SP | jq -r '.appId')
+$ CLIENT_SECRET=$(echo $SP | jq -r '.password')
+$ TENANT_ID=$(echo $SP | jq -r '.tenant')
+
+# Assign the "Key Vault Secrets User" role to the SP
+# allowing it to read secrets from the Azure Key Vault instance
+$ az role assignment create --assignee $CLIENT_ID \
+ --role "Key Vault Secrets User" \
+ --scope $KV_SCOPE
+```
+
+4. Go to the [Azure Key Vault variable provider example](https://github.com/fermyon/enterprise-architectures-and-patterns/tree/main/application-variable-providers/azure-key-vault-provider) application.
+5. Replace Tokens in `runtime-config.toml`.
+
+The `azure-key-vault-provider` application contains a `runtime-config.toml` file. Replace all tokens (e.g. `$KV_NAME$`) with the corresponding shell variables you created in the previous steps.
+
+6. Build and run the `azure-key-vault-provider` app:
+
+
+
+```bash
+$ spin build
+$ spin up --runtime-config-file runtime-config.toml
+```
+
+7. Test the app:
+
+
+
+```bash
+$ curl localhost:3000
+Loaded Secret from Azure Key Vault: secret_value
+```
+
+## Key Value Store Runtime Configuration
+
+Spin provides built-in key-value storage. By default, this storage is backed by a file in the application `.spin` directory. However, you can use the Spin runtime configuration file (`runtime-config.toml`) to modify the file location, or to use a different backing store. The available store options are the file provider, an external Redis database, Azure CosmosDB or AWS DynamoDB.
+
+### File Key Value Store Provider
+
+To use a file as a backend for Spin's key-value store, set the type to `spin`, and provide a file path:
+
+```toml
+[key_value_store.default]
+type = "spin"
+path = ".spin/precious-data.db"
+```
+
+Spin creates the path and file if they don't already exist.
+
+> If, during development, you need to examine keys and values, you can open the file using `sqlite3` or another SQLite tools. However, the file format is subject to change, and you should not rely on it.
+
+### Redis Key Value Store Provider
+
+To use a Redis store as a backend for Spin's key-value store, set the type to `redis` and provide the URL of the Redis host:
+
+```toml
+[key_value_store.default]
+type = "redis"
+url = "redis://localhost"
+```
+
+### Azure CosmosDB Key Value Store Provider
+
+To use an Azure CosmosDB database as a backend for Spin's key-value store, set the type to `azure_cosmos` and specify your database account details:
+
+```toml
+[key_value_store.default]
+type = "azure_cosmos"
+key = ""
+account = ""
+database = ""
+container = ""
+```
+
+> Note: The CosmosDB container must be created with the default partition key, `/id`.
+
+### AWS DynamoDB Key Value Store Provider
+
+To use an Amazon Web Services DynamoDB database as a backend for Spin's key-value store, set the type to `aws_dynamo` and specify your database account details:
+
+```toml
+[key_value_store.default]
+type = "aws_dynamo"
+region = "" # e.g. "us-east-1"
+table = "" # e.g. "spin-key-values"
+consistent_read = true # optional, to use strongly consistent reads
+```
+
+You may optionally provide `access_key` and `secret_key` credentials; otherwise, Spin picks these up from your [AWS environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html). For short-lived credentials, you can additionally provide `token` (from the [Get Session Token API](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetSessionToken.html) or [`aws sts get-session-token` CLI command](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sts/get-session-token.html)).
+
+By default, the DynamoDB backend uses eventually consistent reads. The `consistent_read` option turns on [strongly consistent reads](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html). This ensures reads are up-to-date with writes, at an increased cost. See the [DynamoDB documentation](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html) for more information.
+
+> Note: The DynamoDB table must be created with the partition key `PK`. It must have no sort key (so that the partition key is the primary key).
+
+### Multiple and Non-Default Key-Value Stores
+
+Whilst a single default store may be sufficient for certain application use cases, each Spin application can be configured to support multiple stores of any `type`, as shown in the `runtime-config.toml` file below:
+
+```toml
+# This defines a new store named user_data
+[key_value_store.user_data]
+type = "spin"
+path = ".spin/user_data.db"
+
+# This defines a new store named other_data backed by a Redis database
+[key_value_store.other_data]
+type = "redis"
+url = "redis://localhost"
+```
+
+You must individually grant each component access to the stores that it needs to use. To do this, use the `component.key_value_stores` entry in the component manifest within `spin.toml`. See [Spin Key Value Store](kv-store-api-guide.md) for more details.
+
+## SQLite Storage Runtime Configuration
+
+Spin provides built-in SQLite storage. By default, this is backed by a database that Spin creates for you underneath your application directory (in the `.spin` subdirectory). However, you can use the Spin runtime configuration file (`runtime-config.toml`) to add and customize SQLite databases.
+
+The following example `runtime-config.toml` tells Spin to map the `default` database to an SQLite database elsewhere in the file system:
+
+```toml
+[sqlite_database.default]
+type = "spin"
+path = "/planning/todo.db"
+```
+
+If you need more than one database, you can configure multiple databases, each with its own name:
+
+```toml
+# This defines a new store named todo
+[sqlite_database.todo]
+type = "spin"
+path = "/planning/todo.db"
+
+# This defines a new store named finance
+[sqlite_database.finance]
+type = "spin"
+path = "/super/secret/monies.db"
+```
+
+Spin creates any database files that don't exist. However, it is up to you to delete them when you no longer need them.
+
+### LibSQL Storage Provider
+
+Spin can also use [libSQL](https://libsql.org/) databases accessed over HTTPS. libSQL is fully compatible with SQLite but provides additional features including remote, distributed databases.
+
+> Spin does not provide libSQL access to file-based databases, only databases served over HTTPS. Specifically, Spin supports [the `sqld` libSQL server](https://github.com/libsql/sqld).
+
+To use libSQL, set `type = "libsql"` in your `runtime-config.toml` entry. You must then provide a `url` and authentication `token` instead of a file path. For example, this entry tells Spin to map the `default` database to a libSQL service running on `libsql.example.com`:
+
+```toml
+# This tells Spin to use the remote host as its default database
+[sqlite_database.default]
+type = "libsql"
+url = "https://sensational-penguin-ahacker.libsql.example.com"
+token = "a secret"
+```
+
+Spin does _not_ create libSQL databases. Use your hosting service's tools to create them (or `sqld` if you are self-hosting) . You can still set up tables and data in a libSQL database via `spin up --sqlite`.
+
+> You must include the scheme in the `url` field. The scheme must be `http` or `https`. Non-HTTP libSQL protocols are not supported.
+
+The `default` database will still be defined, even if you add other databases.
+
+By default, components will not have access to any of these databases (even the default one). You must grant each component access to the databases that it needs to use. To do this, use the `component.sqlite_databases` entry in the component manifest within `spin.toml`. See [SQLite Database](./sqlite-api-guide.md) for more details.
+
+## LLM Runtime Configuration
+
+Spin provides a Large Language Model interface for interacting with LLMs for inferencing and embedding. The default host implementation is to use local CPU/GPU compute. However, you can use the Spin runtime configuration file (`runtime-config.toml`) to direct Spin to use remote compute using HTTP requests.
+
+### Remote Compute Provider
+
+The following is an example of how an application's `runtime-config.toml` file can be configured to use the remote compute option. Note the `type`, `url` and `auth_token` are set to `remote_http`, URL of the server and the auth token for the server.
+
+```toml
+[llm_compute]
+type = "remote_http"
+url = "http://example.com"
+auth_token = ""
+api_type = "default"
+```
+
+By default, components will not have access to the LLM models unless granted explicit access through the `component.ai_models` entry in the component manifest within `spin.toml`. See [Serverless AI](./serverless-ai-api-guide) for more details.
+
+Remote compute supports two APIs, selected using the `api_type` field.
+
+#### `default` API
+
+If the `api_type` is `default`, Spin uses its own "Cloud GPU" API. This requires you to deploy your own LLM proxy service. You can find a reference implementation of a proxy service in the [`spin-cloud-gpu plugin repository`](https://github.com/fermyon/spin-cloud-gpu/blob/main/fermyon-cloud-gpu/src/index.ts).
+
+#### `open_ai` API
+
+If the `api_type` is `open_ai`, Spin uses the [OpenAI API](https://github.com/openai/openai-openapi). This API is offered by some commercial LLM providers.
+
+## Outbound HTTP Runtime Configuration
+
+You can control applications' use of outbound HTTP via the runtime configuration file via the `[outbound_http]` section.
+
+* By default, Spin pools outbound HTTP connections to the same destination. You can turn this off by setting `connection_pooling = false`.
+* By default, Spin does not limit the number of concurrent outbound HTTP connections the application may make. You can set a limit by setting `max_concurrent_requests = `.
+
+For example, the following runtime config section prevents connection pooling and limits the application to 10 concurrent requests:
+
+```toml
+[outbound_http]
+connection_pooling = false
+max_concurrent_requests = 10
+```
diff --git a/content/v4/extending-and-embedding.md b/content/v4/extending-and-embedding.md
new file mode 100644
index 00000000..14e13744
--- /dev/null
+++ b/content/v4/extending-and-embedding.md
@@ -0,0 +1,179 @@
+title = "Extending and Embedding Spin"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/extending-and-embedding.md"
+
+---
+- [Extending Spin with a Custom Trigger](#extending-spin-with-a-custom-trigger)
+ - [Implement the Trigger World](#implement-the-trigger-world)
+ - [The Trigger Implements the `Trigger` Trait](#the-trigger-implements-the-trigger-trait)
+ - [The Trigger is an Executable](#the-trigger-is-an-executable)
+ - [The Trigger Detects Events...](#the-trigger-detects-events)
+ - [...and Invokes the Guest](#and-invokes-the-guest)
+- [Other Ways to Extend and Use Spin](#other-ways-to-extend-and-use-spin)
+
+## Extending Spin with a Custom Trigger
+
+> The complete example for a custom trigger [can be found on GitHub](https://github.com/spinframework/spin/tree/main/examples/spin-timer).
+
+Spin currently implements triggers and application models for:
+
+- [HTTP applications](./http-trigger.md) that are triggered by incoming HTTP
+requests, and that return an HTTP response
+- [Redis applications](./redis-trigger.md) that are triggered by messages on Redis
+channels
+
+The Spin internals and execution context (the part of Spin executing
+components) are agnostic of the event source and application model.
+In this document, we will explore how to extend Spin with custom event sources
+(triggers) and application models built on top of the WebAssembly component
+model, as well as how to embed Spin in your application.
+
+In this article, we will build a Spin trigger to run the applications based on a
+timer, executing Spin components at a configured time interval.
+
+> Custom triggers are an experimental feature. The trigger APIs are not stabilized, and you may need to tweak your trigger code as you update to new Spin versions.
+
+Application entry points are defined using
+[WebAssembly Interface (WIT)](https://component-model.bytecodealliance.org/design/wit.html). Here's the entry point for HTTP triggers:
+
+
+
+```fsharp
+interface incoming-handler {
+ use types.{incoming-request, response-outparam}
+
+ handle: func(
+ request: incoming-request,
+ response-out: response-outparam
+ )
+}
+```
+
+The entry point we want to execute for our timer trigger takes no input and doesn't return anything. This is purposefully chosen
+to be a simple function signature. For simplicity, we allow guest code to use Spin's [application variables API](./variables.md) but not other Spin APIs.
+
+> Custom trigger guest code can't currently use the Spin SDK and must refer to the Spin WITs directly.
+
+Here is the resulting timer WIT:
+
+
+
+```fsharp
+// examples/spin-timer/spin-timer.wit
+package fermyon:example
+
+world spin-timer {
+ import fermyon:spin/variables@2.0.0
+ export handle-timer-request: func()
+}
+```
+
+`handle-timer-request` is the function that all components executed by the timer trigger must
+implement, and which is used by the timer executor when instantiating and
+invoking the component.
+
+The timer trigger itself is a Spin plugin whose name is `trigger-timer`. The first part must be `trigger` and the second part is the trigger type.
+
+You can see the full timer trigger code at the link above but here are some key features.
+
+### Implement the Trigger World
+
+The timer trigger implements the WIT world described in `spin-timer.wit`. To do that, it uses the `wasmtime` binding generator — this generates code that allows the trigger to invoke the guest's entry point, and allows the guest to invoke the Spin APIs available in the world.
+
+
+
+```rust
+// examples/spin-timer/src/main.rs
+wasmtime::component::bindgen!({
+ path: ".",
+ world: "spin-timer",
+ async: true
+});
+```
+
+### The Trigger Implements the `Trigger` Trait
+
+Using `Trigger` allows the trigger to offload a great deal of boilerplate loader work to the `spin_trigger` crate and the `FactorsTriggerCommand` CLI helper.
+
+```rust
+struct TimerTrigger {
+ speedup: u64,
+ component_timings: HashMap,
+}
+
+#[async_trait]
+impl Trigger for TimerTrigger {
+ // ...
+}
+```
+
+The `Trigger` trait is generic in the set of _factors_ supported by the trigger - this is roughly the set of APIs available to guest code. In most circumstances, your implementation should also be generic, as shown, because your trigger is only concerned with detecting events, and can do that regardless of what APIs are available to the guests that handle those events.
+
+### The Trigger is an Executable
+
+A trigger is a separate program, so that it can be installed as a plugin. So it is a Rust `bin` project and has a `main` function. It can be useful to also provide a library crate, so that projects that embed Spin can load it in process if desired, but the timer sample doesn't currently show that.
+
+```rust
+type Command = FactorsTriggerCommand;
+// | |
+// the trigger type you created above |
+// the factors (APIs) available to guests
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+ let t = Command::parse();
+ t.run().await
+}
+```
+
+`FactorsTriggerCommand` allows you to configure the set of factors. `spin_runtime_factors::FactorsBuilder` will give you a set that matches the Spin CLI, which is almost always the right choice for a trigger plugin. If other hosts link your trigger, they will initialize it with the factors they support instead. (This is why it's desirable for your `Trigger` implementation to be as generic as possible!)
+
+### The Trigger Detects Events...
+
+In this case the trigger "detects" events by running a timer. In most cases, the trigger detects events by listening on a socket, completion port, or other mechanism, or by polling a resource such as a directory or an HTTP endpoint.
+
+```rust
+for (component_id, interval_secs) in &self.component_timings {
+ scope.spawn(async {
+ let duration =
+ tokio::time::Duration::from_millis(*interval_secs * 1000 / speedup);
+ loop {
+ tokio::time::sleep(duration).await;
+
+ self.handle_timer_event(&trigger_app, component_id)
+ .await
+ .unwrap();
+ }
+ });
+}
+```
+
+### ...and Invokes the Guest
+
+The `Trigger` and `FactorsTriggerCommand` infrastructure provides the trigger with a `TriggerApp` representing the configured WebAssembly environment, already initialized with the guest Wasm for each component. When an event occurs, the trigger creates a component _instance_ from the `TriggerApp` and invokes it via the WIT interfaces.
+
+```rust
+async fn handle_timer_event(
+ &self,
+ trigger_app: &TriggerApp,
+ component_id: &str,
+) -> anyhow::Result<()> {
+ // Obtain a component instance from the app
+ let instance_builder = trigger_app.prepare(component_id)?;
+ let (instance, mut store) = instance_builder.instantiate(()).await?;
+ // Wrap the instance in the generated WIT bindings
+ let timer = SpinTimer::new(&mut store, &instance)?;
+ // Invoke the guest entry point via the bindings
+ timer.call_handle_timer_request(&mut store).await
+}
+```
+
+## Other Ways to Extend and Use Spin
+
+Besides building custom triggers, the internals of Spin could also be used independently:
+
+- The Spin execution context can be used entirely without a `spin.toml` application manifest — for embedding scenarios, the configuration for the
+execution can be constructed without a `spin.toml`, for example by running applications only from a registry.
+- You can create a custom Spin runtime with a different set of factors to target specific scenarios.
diff --git a/content/v4/go-components.md b/content/v4/go-components.md
new file mode 100644
index 00000000..1b81c753
--- /dev/null
+++ b/content/v4/go-components.md
@@ -0,0 +1,428 @@
+title = "Building Spin components in Go"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/go-components.md"
+
+---
+
+- [Versions](#versions)
+- [HTTP Components](#http-components)
+- [Sending Outbound HTTP Requests](#sending-outbound-http-requests)
+- [Redis Components](#redis-components)
+- [Asynchronous and Streaming Idioms in Go](#asynchronous-and-streaming-idioms-in-go)
+ - [Spawning Asynchronous Tasks](#spawning-asynchronous-tasks)
+ - [Creating Futures and Streams](#creating-futures-and-streams)
+- [Storing Data in Redis From Go Components](#storing-data-in-redis-from-go-components)
+- [Using Go Packages in Spin Components](#using-go-packages-in-spin-components)
+- [Storing Data in the Spin Key-Value Store](#storing-data-in-the-spin-key-value-store)
+- [Storing Data in SQLite](#storing-data-in-sqlite)
+- [AI Inferencing From Go Components](#ai-inferencing-from-go-components)
+
+> This guide assumes you have Spin installed. If this is your first encounter with Spin, please see the [Quick Start](quickstart), which includes information about installing Spin with the Go templates, installing required tools, and creating Go applications.
+
+> This guide assumes you are familiar with the Go programming language, and that
+> you have
+> [configured the TinyGo toolchain locally](https://tinygo.org/getting-started/install/).
+Using TinyGo to compile components for Spin is currently required, as the
+[Go compiler doesn't currently have support for compiling to WASI](https://github.com/golang/go/issues/31105).
+
+> All examples from this page can be found in [the Spin Go SDK repository on GitHub](https://github.com/spinframework/spin-go-sdk/tree/main/examples).
+
+[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2)
+
+## Versions
+
+TinyGo `0.35.0` is recommended, which requires Go `v1.22+`. Older versions of TinyGo may not support the command-line flags used when building Spin applications.
+
+## HTTP Components
+
+In Spin, HTTP components are triggered by the occurrence of an HTTP request, and
+must return an HTTP response at the end of their execution. Components can be
+built in any language that compiles to WASI, and Go has improved support for
+writing applications, through its SDK.
+
+Building a Spin HTTP component using the Go SDK means using the `init` function
+to assign a handler function to `handler.Exports.Handle`. Below is a complete
+implementation for a component that returns "Hello, Spin" as its response:
+
+
+
+```go
+package main
+
+import (
+ "fmt"
+ "net/http"
+
+ // The WASI contracts for receiving HTTP requests
+ handler "github.com/spinframework/spin-go-sdk/v3/exports/wasi_http_service_0_3_0_rc_2026_03_15/export_wasi_http_0_3_0_rc_2026_03_15_handler"
+ _ "github.com/spinframework/spin-go-sdk/v3/exports/wasi_http_service_0_3_0_rc_2026_03_15/wit_exports"
+ . "github.com/spinframework/spin-go-sdk/v3/imports/wasi_http_0_3_0_rc_2026_03_15_types"
+ . "go.bytecodealliance.org/pkg/wit/types")
+
+func Handle(request *Request) Result[*Response, ErrorCode] {
+ // The byte stream which will become the response body
+ tx, rx := MakeStreamU8()
+
+ // Start a goroutine to write the response body to the stream
+ go func() {
+ defer tx.Drop()
+ tx.WriteAll([]uint8("Hello, Spin"))
+ }()
+
+ // Create the response object
+ response, send := ResponseNew(
+ // Response headers
+ FieldsFromList([]Tuple2[string, []uint8]{
+ Tuple2[string, []uint8]{"content-type", []uint8("text/plain")},
+ }).Ok(),
+ // Response body stream
+ Some(rx),
+ // A future that will provide (an empty set of) trailers once the body completes
+ trailersFuture(),
+ )
+ send.Drop()
+
+ return Ok[*Response, ErrorCode](response)
+}
+
+// Creates a future that will resolve to an (empty) set of HTTP trailers
+func trailersFuture() *FutureReader[Result[Option[*Fields], ErrorCode]] {
+ tx, rx := MakeFutureResultOptionFieldsErrorCode()
+ go tx.Write(Ok[Option[*Fields], ErrorCode](None[*Fields]()))
+ return rx
+}
+
+func init() {
+ // Assign the Handle function as the WASI HTTP `handler`
+ handler.Exports.Handle = Handle
+}
+
+// Formally required but never used
+func main() {}
+```
+
+This component can be built using the `componentize-go` tool:
+
+
+
+```bash
+$ componentize-go -w wasi:http/service@0.3.0-rc-2026-03-15 -w platform build
+```
+
+Once built, we can run our Spin HTTP component using the Spin up command:
+
+
+
+```bash
+$ spin up
+```
+
+The Spin HTTP component can now receive and process incoming requests:
+
+
+
+```bash
+$ curl -i localhost:3000
+HTTP/1.1 200 OK
+content-type: text/plain
+content-length: 12
+
+Hello, Spin
+```
+
+## Sending Outbound HTTP Requests
+
+If allowed, Spin components can send outbound requests to HTTP endpoints. Let's
+see an example of a component that makes a request to
+[an API that returns random animal facts](https://random-data-api.fermyon.app/animals/json) and
+inserts a custom header into the response before returning:
+
+
+
+```go
+// A Spin component written in Go that sends a request to an API
+// with random animal facts.
+
+package main
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+
+ spinhttp "github.com/spinframework/spin-go-sdk/v2/http"
+)
+
+func init() {
+ spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
+ resp, _ := spinhttp.Get("https://random-data-api.fermyon.app/animals/json")
+
+ fmt.Fprintln(w, resp.Body)
+ fmt.Fprintln(w, resp.Header.Get("content-type"))
+
+ // `spin.toml` is not configured to allow outbound HTTP requests to this host,
+ // so this request will fail.
+ if _, err := spinhttp.Get("https://fermyon.com"); err != nil {
+ fmt.Fprintf(os.Stderr, "Cannot send HTTP request: %v", err)
+ }
+ })
+}
+```
+
+The Outbound HTTP Request example above can be built using the `tingygo` toolchain:
+
+
+
+```bash
+$ tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm .
+```
+
+Before we can execute this component, we need to add the
+`random-data-api.fermyon.app` domain to the application manifest `allowed_outbound_hosts`
+list containing the list of domains the component is allowed to make HTTP
+requests to:
+
+
+
+```toml
+# spin.toml
+spin_manifest_version = 2
+
+[application]
+name = "spin-hello-tinygo"
+version = "1.0.0"
+
+[[trigger.http]]
+route = "/hello"
+component = "tinygo-hello"
+
+[component.tinygo-hello]
+source = "main.wasm"
+allowed_outbound_hosts = [ "https://random-data-api.fermyon.app" ]
+```
+
+Running the application using `spin up` will start the HTTP
+listener locally (by default on `localhost:3000`), and our component can
+now receive requests in route `/hello`:
+
+
+
+```bash
+$ curl -i localhost:3000/hello
+HTTP/1.1 200 OK
+content-length: 93
+
+{"timestamp":1684299253331,"fact":"Reindeer grow new antlers every year"}
+```
+
+> Without the `allowed_outbound_hosts` field populated properly in `spin.toml`,
+> the component would not be allowed to send HTTP requests, and sending the
+> request would generate in a "Destination not allowed" error.
+
+> You can set `allowed_outbound_hosts = ["https://*:*"]` if you want to allow
+> the component to make requests to any HTTP host. This is not recommended
+> unless you have a specific need to contact arbitrary servers and perform your own safety checks.
+
+## Redis Components
+
+Besides the HTTP trigger, Spin has built-in support for a Redis trigger, which
+will connect to a Redis instance and will execute components for new messages
+on the configured channels.
+
+> See the [Redis trigger](./redis-trigger.md) for details about the Redis trigger.
+
+Writing a Redis component in Go also takes advantage of the SDK:
+
+
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/spinframework/spin-go-sdk/v2/redis"
+)
+
+func init() {
+ // redis.Handle() must be called in the init() function.
+ redis.Handle(func(payload []byte) error {
+ fmt.Println("Payload::::")
+ fmt.Println(string(payload))
+ return nil
+ })
+}
+```
+
+The manifest for a Redis application must contain the address of the Redis instance. This is set at the application level:
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "spin-redis"
+trigger = { type = "redis", }
+version = "0.1.0"
+
+[application.trigger.redis]
+address = "redis://localhost:6379"
+
+[[trigger.redis]]
+channel = "messages"
+component = "echo-message"
+
+[component.echo-message]
+source = "main.wasm"
+[component.echo-message.build]
+command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ."
+```
+
+The application will connect to `redis://localhost:6379`, and for every new message
+on the `messages` channel, the `echo-message` component will be executed:
+
+
+
+```bash
+# first, start redis-server on the default port 6379
+$ redis-server --port 6379
+# then, start the Spin application
+$ spin build --up
+INFO spin_redis_engine: Connecting to Redis server at redis://localhost:6379
+INFO spin_redis_engine: Subscribed component 0 (echo-message) to channel: messages
+```
+
+For every new message on the `messages` channel:
+
+
+
+```bash
+$ redis-cli
+127.0.0.1:6379> publish messages "Hello, there!"
+```
+
+Spin will instantiate and execute the component:
+
+
+
+```bash
+INFO spin_redis_engine: Received message on channel "messages"
+Payload::::
+Hello, there!
+```
+
+## Asynchronous and Streaming Idioms in Go
+
+### Spawning Asynchronous Tasks
+
+Just as in native code, you can spawn an asynchronous task in Go using the `go` language keyword. (We mention this because some other languages require special library functions for this. But in Go you can use the normal language idiom.)
+
+### Creating Futures and Streams
+
+The Go SDK provides `Make*` functions for creating Wasm Component Model futures and streams. The bindings contain a corresponding function for each concrete future or stream type mentioned in the Spin and WASI APIs.
+
+To create a future, call `MakeFuture` - for example, `MakeFutureFields`. This returns a writer (which you can use later to complete the future) and a reader (representing the future which will eventually resolve to a value).
+
+To create a stream, call `MakeStream` - for example, `MakeStreamU8` is a byte stream. Again, this returns a writer and a reader. The writer is typically handed to a goroutine to asynchronously send values into the stream. The reader is typically passed to an API that takes a stream parameter, for example acting as the body in an HTTP response.
+
+For generic types, the type name in the function is formed by concatenation, so you may see things like `MakeFutureResultOptionFieldsErrorCode` at the bindings level. You shouldn't normally have to deal with these in application code though!
+
+## Storing Data in Redis From Go Components
+
+Using the Spin's Go SDK, you can use the Redis key/value store to publish
+messages to Redis channels. This can be used from both HTTP and Redis triggered
+components.
+
+Let's see how we can use the Go SDK to connect to Redis. This HTTP component demonstrates fetching a value from Redis by key, setting a
+key with a value, and publishing a message to a Redis channel:
+
+
+
+```go
+package main
+
+import (
+ "net/http"
+ "os"
+
+ spin_http "github.com/spinframework/spin-go-sdk/v2/http"
+ "github.com/spinframework/spin-go-sdk/v2/redis"
+)
+
+func init() {
+ // handler for the http trigger
+ spin_http.Handle(func(w http.ResponseWriter, r *http.Request) {
+
+ // addr is the environment variable set in `spin.toml` that points to the
+ // address of the Redis server.
+ addr := os.Getenv("REDIS_ADDRESS")
+
+ // channel is the environment variable set in `spin.toml` that specifies
+ // the Redis channel that the component will publish to.
+ channel := os.Getenv("REDIS_CHANNEL")
+
+ // payload is the data publish to the redis channel.
+ payload := []byte(`Hello redis from tinygo!`)
+
+ // create a new redis client.
+ rdb := redis.NewClient(addr)
+
+ if err := rdb.Publish(channel, payload); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // set redis `mykey` = `myvalue`
+ if err := rdb.Set("mykey", []byte("myvalue")); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // get redis payload for `mykey`
+ if payload, err := rdb.Get("mykey"); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ } else {
+ w.Write([]byte("mykey value was: "))
+ w.Write(payload)
+ }
+ })
+}
+```
+
+As with all networking APIs, you must grant access to Redis hosts via the `allowed_outbound_hosts` field in the application manifest:
+
+
+
+```toml
+[component.storage-demo]
+environment = { REDIS_ADDRESS = "redis://127.0.0.1:6379", REDIS_CHANNEL = "messages" }
+allowed_outbound_hosts = ["redis://127.0.0.1:6379"]
+```
+
+This HTTP component can be paired with a Redis component, triggered on new messages on the `messages` Redis channel, to build an asynchronous messaging application, where the HTTP front-end queues work for a Redis-triggered back-end to execute as capacity is available.
+
+> You can find a complete example for using outbound Redis from an HTTP component
+> in the [Spin repository on GitHub](https://github.com/spinframework/spin-go-sdk/tree/main/examples/redis-outbound).
+
+## Using Go Packages in Spin Components
+
+Any [package from the Go standard library](https://tinygo.org/docs/reference/lang-support/stdlib/) that can be imported in TinyGo and that compiles to
+WASI can be used when implementing a Spin component.
+
+> Make sure to read [the page describing the HTTP trigger](./http-trigger.md) for more
+> details about building HTTP applications.
+
+## Storing Data in the Spin Key-Value Store
+
+Spin has a key-value store built in. For information about using it from TinyGo, see [the key-value API guide](kv-store-api-guide).
+
+## Storing Data in SQLite
+
+For more information about using SQLite from TinyGo, see [SQLite storage](sqlite-api-guide).
+
+## AI Inferencing From Go Components
+
+For more information about using Serverless AI from TinyGo, see the [Serverless AI](serverless-ai-api-guide) API guide.
diff --git a/content/v4/http-outbound.md b/content/v4/http-outbound.md
new file mode 100644
index 00000000..284f5c4f
--- /dev/null
+++ b/content/v4/http-outbound.md
@@ -0,0 +1,320 @@
+title = "Making HTTP Requests"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/http-outbound.md"
+
+---
+- [Using HTTP From Applications](#using-http-from-applications)
+- [Restrictions](#restrictions)
+- [Granting HTTP Permissions to Components](#granting-http-permissions-to-components)
+ - [Configuration-Based Permissions](#configuration-based-permissions)
+- [Making HTTP Requests Within an Application](#making-http-requests-within-an-application)
+ - [Local Service Chaining](#local-service-chaining)
+ - [Intra-Application HTTP Requests by Route](#intra-application-http-requests-by-route)
+
+Spin provides an interface for you to make outgoing HTTP requests.
+
+{{ details "Why do I need a Spin interface? Why can't I just use my language's HTTP library?" "The current version of the WebAssembly System Interface (WASI) doesn't provide a sockets interface, so HTTP libraries can't be built to Wasm. The Spin interface means Wasm modules can bypass this limitation by asking Spin to perform the HTTP request on their behalf." }}
+
+## Using HTTP From Applications
+
+The outbound HTTP interface depends on your language.
+
+> Under the surface, Spin uses the `wasi-http` interface. If your tools support the Wasm Component Model, you can work with that directly; but for most languages the Spin SDK is more idiomatic.
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/http/index.html)
+
+To send requests, use the [`spin_sdk::http::send`](https://docs.rs/spin-sdk/latest/spin_sdk/http/fn.send.html) function. This takes a request argument and returns a response (or error).
+
+The request you pass to `send` may be:
+
+* [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) - typically constructed via `http::Request::builder()`
+* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Request.html) - typically constructed via `spin_sdk::http::Request::get()`, `spin_sdk::http::Request::post()`, or `spin_sdk::http::Request::builder()`
+* Any type which implements the `spin_sdk::http::IntoRequest` trait
+
+Generally, you should use `OutgoingRequest` when you need to stream the outbound request body; otherwise, the `Request` types are usually simpler.
+
+The response is a `spin_sdk::http::Response`.
+
+Here is an example of doing outbound HTTP when you want to wait for the entire response and load it into memory as a single blob:
+
+```rust
+use spin_sdk::http::{EmptyBody, FullBody, IntoResponse, Request, Response};
+use spin_sdk::http::body::IncomingBodyExt;
+use spin_sdk::http_service;
+use http::Method;
+
+#[http_service]
+async fn handle_p3_body_test(req: Request) -> anyhow::Result {
+ // Create the outbound request object
+ let request = Request::builder()
+ .method(Method::GET)
+ .uri("https://spinframework.dev/")
+ .body(EmptyBody::new())?;
+
+ // Send the request and await the response
+ let response = spin_sdk::http_wasip3::send(request).await?;
+
+ // Get the outbound response body as a bytes::Bytes
+ let body = response.into_body().bytes().await?;
+
+ // Process the response bytes
+ let response_len = body.len();
+
+ // Return a response to the inbound request
+ Ok((http::StatusCode::OK, response_len.to_string()))
+}
+```
+
+Here is an example of doing outbound HTTP when you want to process the response as a stream, handling each chunk as it arrives from the server and then discarding it:
+
+```rust
+use futures::StreamExt;
+use spin_sdk::http::{EmptyBody, FullBody, IntoResponse, Request, Response};
+use spin_sdk::http::body::IncomingBodyExt;
+use spin_sdk::http_service;
+use http::Method;
+
+#[http_service]
+async fn handle_request(_req: Request) -> anyhow::Result {
+ // Create the outbound request object
+ let request = Request::builder()
+ .method(Method::GET)
+ .uri("https://spinframework.dev/")
+ .body(EmptyBody::new())?;
+
+ // Send the request and await the response
+ let response = spin_sdk::http::send(request).await?;
+
+ // Get the outbound response body as a stream
+ let mut body = response.into_body().stream();
+
+ // Use the response stream
+ let mut response_len = 0;
+ while let Some(chunk) = body.next().await {
+ response_len += chunk?.len();
+ }
+
+ // Return a response to the inbound request
+ Ok((http::StatusCode::OK, response_len.to_string()))
+}
+```
+
+Here is an example of doing outbound HTTP when you want to stream outbound request data to the remote host:
+
+```rust
+use bytes::Bytes;
+use futures::{SinkExt, StreamExt};
+use http_body_util::StreamBody;
+use spin_sdk::http::body::IncomingBodyExt;
+use spin_sdk::http::{IntoResponse, Request};
+use spin_sdk::http_service;
+use http::Method;
+
+#[http_service]
+async fn handle_p3_body_test(req: Request) -> anyhow::Result {
+ // Set up a channel - one end of this is the streaming body, the other
+ // end is where the client writes data into the streaming body.
+ let (mut tx, rx) = futures::channel::mpsc::channel::(1024);
+
+ // Spawn a task to send data into the stream
+ spin_sdk::spawn(async move {
+ for index in 0..20 {
+ let line = format!("test line {index}\n");
+ tx.send(line.into()).await.unwrap();
+ }
+ });
+
+ // Create the outbound request object
+ let request = Request::builder()
+ .method(Method::POST)
+ .uri("https://spinframework.dev/")
+ .body(StreamBody::new(rx))?;
+
+ // Adapt the Bytes-based stream to something that `http` understands
+ let rx = rx.map(|value| anyhow::Ok(http_body::Frame::data(value)));
+
+ // Send the request with the streaming body, and await the response.
+ // The outbound body could still be streaming during the await, and
+ // even while the response body is being processed (although that won't
+ // happen in this simple case!).
+ let response = spin_sdk::http::send(request).await?;
+
+ // Process the response, in this case buffering in memory, but
+ // this could be streaming too as in the previous example
+ let body = response.into_body().bytes().await?;
+ let text = String::from_utf8_lossy(&body[..]);
+ Ok((http::StatusCode::OK, text.to_string()))
+}
+```
+
+For more examples, [see the `spin-rust-sdk` repository](https://github.com/spinframework/spin-rust-sdk/blob/main/examples).
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript"}}
+
+HTTP operations are available via the standard JavaScript `fetch` function. The Spin runtime maps this to the underlying Wasm interface. For example:
+
+```javascript
+const response = await fetch("https://example.com/users");
+```
+
+**Notes**
+
+You can find a complete example of using outbound HTTP in the JavaScript SDK repository on [GitHub](https://github.com/spinframework/spin-js-sdk/tree/main/examples/common-patterns/outbound-http)
+
+**Note**: `fetch` currently only works when building for the HTTP trigger.
+
+{{ blockEnd }}
+
+{{ startTab "Python"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v4/http/index.html)
+
+HTTP functions and classes are available in the `http` module. The function name is [`send`](https://spinframework.github.io/spin-python-sdk/v4/http/index.html#spin_sdk.http.send). The [request type](https://spinframework.github.io/spin-python-sdk/v4/http/index.html#spin_sdk.http.Request) is `Request`, and the [response type](https://spinframework.github.io/spin-python-sdk/v4/http/index.html#spin_sdk.http.Response) is `Response`. For example:
+
+```python
+from spin_sdk import http
+from spin_sdk.http import Request, Response, send
+
+class HttpHandler(http.Handler):
+ async def handle_request(self, request: Request) -> Response:
+ response = await send(Request("GET", "https://random-data-api.fermyon.app/animals/json", {}, None))
+ return response
+```
+
+**Notes**
+
+* For compatibility with idiomatic Python, types do not necessarily match the underlying Wasm interface. For example, `method` is a string.
+* Request and response bodies are `bytes`. (You can pass literal strings using the `b` prefix.) Pass `None` for no body.
+* Request and response headers are dictionaries.
+* Errors are signalled through exceptions.
+
+You can find a complete example for using outbound HTTP in the [Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples/outgoing-request).
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/http)
+
+HTTP functions are available in the `github.com/spinframework/spin-go-sdk/v2/http` package. [See Go Packages for reference documentation.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2/http) The general function is named `Send`, but the Go SDK also surfaces individual functions, with request-specific parameters, for the `Get` and `Post` operations. For example:
+
+```go
+import (
+ spinhttp "github.com/spinframework/spin-go-sdk/v2/http"
+)
+
+res1, err1 := spinhttp.Get("https://random-data-api.fermyon.app/animals/json")
+res2, err2 := spinhttp.Post("https://example.com/users", "application/json", json)
+
+request, err := http.NewRequest("PUT", "https://example.com/users/1", bytes.NewBufferString(user1))
+request.Header.Add("content-type", "application/json")
+res3, err3 := spinhttp.Send(req)
+
+```
+
+**Notes**
+
+* In the `Post` function, the body is an `io.Reader`. The Spin SDK reads this into the underlying Wasm byte array.
+* The `NewRequest` function is part of the standard library. The `Send` method adapts the standard request type to the underlying Wasm interface.
+* Errors are returned through the usual Go multiple return values mechanism.
+
+You can find a complete example for using outbound HTTP in the [Spin repository on GitHub](https://github.com/spinframework/spin-go-sdk/tree/main/examples/http-outbound).
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+## Restrictions
+
+Spin applications cannot set the `Host` header on outbound requests. (This is a limitation of the Wasmtime runtime which underpins Spin.)
+
+## Granting HTTP Permissions to Components
+
+By default, Spin components are not allowed to make outgoing HTTP requests. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to make HTTP requests to a particular host, use the `allowed_outbound_hosts` field in the component manifest:
+
+```toml
+[component.example]
+allowed_outbound_hosts = [ "https://random-data-api.fermyon.app", "http://api.example.com:8080" ]
+```
+
+The Wasm module can make HTTP requests _only_ to the specified hosts. If a port is specified, the module can make requests only to that port; otherwise, the module can make requests only on the default port for the scheme. Requests to other hosts (or ports) will fail with an error.
+
+You can use a wildcard to allow requests to any subdomain of a domain:
+
+```toml
+[component.example]
+allowed_outbound_hosts = [ "https://*.example.com" ]
+```
+
+You can also pass an IPv4 CIDR address:
+
+```toml
+[component.example]
+allowed_outbound_hosts = [ "https://192.168.1.0/24" ]
+```
+
+For development-time convenience, you can also pass the string `"https://*:*"` in the `allowed_outbound_hosts` collection. This allows the Wasm module to make HTTP requests to _any_ host and on any port. However, once you've determined which hosts your code needs, you should remove this string and list the hosts instead. Other Spin implementations may restrict host access and disallow components that ask to connect to anything and everything!
+
+### Configuration-Based Permissions
+
+You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `allowed_outbound_hosts` field.
+
+## Making HTTP Requests Within an Application
+
+If your Spin application functions as a set of microservices, you'll often want to make requests directly from one component to another within the same application. It's best not to use a full URL for this, because that's not portable across different deployment environments - the URL in the cloud is different from the one in your development environment. Instead, Spin provides two ways to make inter-component requests:
+
+* By component ID ("local service chaining")
+* By route
+
+Both of these work only from HTTP components. That is, if you want to make an intra-application request from, say, a Redis trigger, you must use a full URL.
+
+### Local Service Chaining
+
+To make an HTTP request to another component in your application, use the special `.spin.internal` host name. For example, an outbound HTTP request to `authz.spin.internal` will be handled by the `authz` component.
+
+In this way of doing self-requests, the request is passed in memory without ever leaving the Spin host process. This is extremely fast, as the two components are wired almost directly together, but may reduce deployment flexibility depending on the nature of the microservices. Also, public components that are the target of service chaining requests may see URLs in both routed and chained forms: therefore, if they parse the URL (for example, extracting a resource identifier from the path), they must ensure both forms are correctly handled.
+
+> Service chaining is the only way to call [private endpoints](./http-trigger#private-endpoints).
+
+You must still grant permission by including the relevant `spin.internal` hosts in `allowed_outbound_hosts`:
+
+```toml
+allowed_outbound_hosts = ["http://authz.spin.internal", "https://reporting.spin.internal"]
+```
+
+You may use either the `http` or `https` scheme, and the scheme in the service chaining request is ignored: only the special host name matters.
+
+To allow local chaining to _any_ component in your application, you can use a subdomain wildcard:
+
+```toml
+allowed_outbound_hosts = ["http://*.spin.internal"]
+```
+
+However, the wildcard implies that the component requires _all other_ components to be local to it. You will therefore not be able to use [selective deployment](./running-apps#splitting-an-application-across-environments) for an application that uses wildcard service chaining.
+
+> Local service chaining may not be supported on distributed Spin hosts.
+
+### Intra-Application HTTP Requests by Route
+
+To make an HTTP request to another route with your application, you can pass just the route as the URL. For example, if you make an outbound HTTP request to `/api/customers/`, Spin prepends the route with whatever host the application is running on. It also replaces the URL scheme (`http` or `https`) with the scheme of the current HTTP request. For example, if the application is running in the cloud, Spin changes `/api` to `https://.../api`.
+
+> You can also use the special host `self.alt` to perform self-requests by route. This is important for the JavaScript `fetch` wrapper, which handles relative requests in a way that doesn't work with `allowed_outbound_hosts`. For example, you would write `fetch('http://self.alt/api')`.
+>
+In this way of doing self-requests, the request undergoes normal HTTP processing once Spin has prepended the host. For example, in a cloud deployment, the request passes through the network, and potentially back in through a load balancer or other gateway. The benefit of this is that it allows load to be distributed across the environment, but it may count against your use of bandwidth.
+
+You must still grant permission by including `self` or `self.alt` in `allowed_outbound_hosts`:
+
+```toml
+allowed_outbound_hosts = ["http://self", "https://self.alt"]
+```
+
+> It doesn't matter which you use - either 'allow' form enables both relative and `self.alt` URLs.
diff --git a/content/v4/http-trigger.md b/content/v4/http-trigger.md
new file mode 100644
index 00000000..83137f33
--- /dev/null
+++ b/content/v4/http-trigger.md
@@ -0,0 +1,538 @@
+title = "The Spin HTTP Trigger"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/http-trigger.md"
+
+---
+- [Specifying an HTTP Trigger](#specifying-an-http-trigger)
+- [HTTP Trigger Routes](#http-trigger-routes)
+ - [Resolving Overlapping Routes](#resolving-overlapping-routes)
+ - [Private Endpoints](#private-endpoints)
+ - [Reserved Routes](#reserved-routes)
+- [Authoring HTTP Components](#authoring-http-components)
+ - [The Request Handler](#the-request-handler)
+ - [Getting Request and Response Information](#getting-request-and-response-information)
+ - [Additional Request Information](#additional-request-information)
+ - [Inside HTTP Components](#inside-http-components)
+- [Static Responses with the HTTP Trigger](#static-responses-with-the-http-trigger)
+- [HTTP With Wagi (WebAssembly Gateway Interface)](#http-with-wagi-webassembly-gateway-interface)
+ - [Wagi Component Requirements](#wagi-component-requirements)
+ - [Request Handling in Wagi](#request-handling-in-wagi)
+ - [Wagi HTTP Environment Variables](#wagi-http-environment-variables)
+- [Exposing HTTP Triggers Using HTTPS](#exposing-http-triggers-using-https)
+ - [Trigger Options](#trigger-options)
+ - [Environment Variables](#environment-variables)
+- [Controlling Instance Reuse](#controlling-instance-reuse)
+ - [Developer Guidelines for Instance Reuse](#developer-guidelines-for-instance-reuse)
+ - [Preventing Instance Reuse](#preventing-instance-reuse)
+
+HTTP applications are an important workload in event-driven environments,
+and Spin has built-in support for creating and running HTTP
+components. This page covers Spin options that are specific to HTTP.
+
+The HTTP trigger type in Spin is a web server. When an application has HTTP triggers, Spin listens for incoming requests and,
+based on the [application manifest](./writing-apps.md), it routes them to a
+component, which provides an HTTP response.
+
+## Specifying an HTTP Trigger
+
+An HTTP trigger maps an HTTP route to a component. For example:
+
+```toml
+[[trigger.http]]
+route = "/..." # the route that the trigger matches
+component = "my-application" # the name of the component to handle this route
+```
+
+Such a trigger says that HTTP requests matching the specified _route_ should be handled by the specified _component_. The `component` field works the same way across all triggers - see [Triggers](triggers) for the details.
+
+## HTTP Trigger Routes
+
+An HTTP route may be _exact_ or _wildcard_.
+
+An _exact_ route matches only the given route. This is the default behavior. For example, `/cart` matches only `/cart`, and not `/cart/checkout`:
+
+
+
+```toml
+# Run the `shopping-cart` component when the application receives a request to `/cart`...
+[[trigger.http]]
+route = "/cart"
+component = "shopping-cart"
+
+# ...and the `checkout` component for `/cart/checkout`
+[[trigger.http]]
+route = "/cart/checkout"
+component = "checkout"
+```
+
+You can use wildcards to match 'patterns' of routes. Spin supports two kinds of wildcards: single-segment wildcards and trailing wildcards.
+
+A single-segment wildcard uses the syntax `:name`, where `name` is a name that identifies the wildcard. Such a wildcard will match only a single segment of a path, and allows further matching on segments beyond it. For example, `/users/:userid/edit` matches `/users/1/edit` and `/users/alice/edit`, but does not match `/users`, `/users/1`, or `/users/1/edit/cart`.
+
+A trailing wildcard uses the syntax `/...` and matches the given route and any route under it. For example, `/users/...` matches `/users`, `/users/1`, `/users/1/edit`, and so on. Any of these routes will run the mapped component.
+
+> In particular, the route `/...` matches all routes.
+
+> Browser clients often `GET /favicon.ico` after a page request. If you use the `/...` route, consider handling this case in your code!
+
+
+
+```toml
+[[trigger.http]]
+# Run the `user-manager` component when the application receives a request to `/users`
+# or any path beginning with `/users/`
+route = "/users/..."
+component = "user-manager"
+```
+
+### Resolving Overlapping Routes
+
+If multiple triggers could potentially handle the same request based on their
+defined routes, the trigger whose route has the longest matching prefix
+takes precedence. This also means that exact matches take precedence over wildcard matches.
+
+In the following example, requests starting with the `/users/` prefix (e.g. `/users/1`)
+will be handled by `user-manager`, even though it is also matched by the `shop` route, because the `/users` prefix is longer than `/`.
+But requests to `/users/admin` will be handled by the `admin` component, not `user-manager`, because that is a more exact match still:
+
+
+
+```toml
+# spin.toml
+
+[[trigger.http]]
+route = "/users/..."
+component = "user-manager"
+
+[[trigger.http]]
+route = "/users/admin"
+component = "admin"
+
+[[trigger.http]]
+route = "/..."
+component = "shop"
+```
+
+### Private Endpoints
+
+Private endpoints are where an internal microservice is not exposed to the network (does not have an HTTP route) and so is accessible only from within the application.
+
+
+
+```toml
+[[trigger.http]]
+route = { private = true }
+component = "internal"
+```
+
+To access a private endpoint, use [local service chaining](./http-outbound#local-service-chaining) (where the request is passed in memory without ever leaving the Spin host process). Such calls still require the internal endpoint to be included in `allowed_outbound_hosts`.
+
+### Reserved Routes
+
+Every HTTP application automatically has a special route always configured at `/.well-known/spin/...`. This route takes priority over any routes in the application: that is, the Spin runtime handles requests to this route, and the application never sees such requests.
+
+You can use paths within this route for health and status checking. The following are currently defined:
+
+* `/.well-known/spin/health`: returns 200 OK if Spin is healthy and accepting requests
+* `/.well-known/spin/info`: returns information about the application and deployment
+
+Other paths within the reserved space currently return 404 Not Found.
+
+## Authoring HTTP Components
+
+> Spin has two ways of running HTTP components, depending on language support for the evolving WebAssembly component standards. This section describes the default way, which is currently used by Rust, JavaScript/TypeScript, Python, and TinyGo components. For other languages, see [HTTP Components with Wagi](#http-with-wagi-webassembly-gateway-interface) below.
+
+By default, Spin runs components using the [WebAssembly component model](https://component-model.bytecodealliance.org/). In this model, the Wasm module exports a well-known interface that Spin calls to handle the HTTP request.
+
+### The Request Handler
+
+The exact signature of the HTTP handler, and how a function is identified to be exported as the handler, will depend on your language.
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/http/index.html)
+
+In Rust, the handler is identified by the [`#[spin_sdk::http_service]`](https://docs.rs/spin-sdk/latest/spin_sdk/attr.http_service.html) attribute. The handler function is an async function which receives the request as an argument, and returns the response as the return value of the function. For example:
+
+```rust
+#[http_service]
+async fn handle(request: http::Request) -> anyhow::Result { ... }
+```
+
+You have some flexibility in choosing the types of the request and response. The request may be:
+
+* [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html)
+* [`spin_sdk::http::Request`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Request.html)
+* Any type which implements the [`spin_sdk::http::FromRequest`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.FromRequest.html) trait
+
+The response may be:
+
+* [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html) - typically constructed via `Response::builder()`
+* [`spin_sdk::http::Response`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.Response.html) - typically constructed via a [`ResponseBuilder`](https://docs.rs/spin-sdk/latest/spin_sdk/http/struct.ResponseBuilder.html)
+* Any type which implements the [`spin_sdk::http::IntoResponse`](https://docs.rs/spin-sdk/latest/spin_sdk/http/trait.IntoResponse.html) trait
+* A `Result` where the success type is one of the above and the error type is `anyhow::Error` or another error type for which you have implemented `spin_sdk::http::IntoResponse` (such as `anyhow::Result`)
+
+> The HTTP template generates a return type of `anyhow::Result`, so you don't need to tweak it if you change the concrete type of the response.
+
+For example:
+
+```rust
+use http::{Request, Response};
+use spin_sdk::http::IntoResponse;
+use spin_sdk::http_service;
+
+#[http_service]
+async fn handle_hello_rust(_req: Request<()>) -> anyhow::Result {
+ Ok(Response::builder()
+ .status(200)
+ .header("content-type", "text/plain")
+ .body("Hello, Spin".to_string())?)
+}
+```
+
+For a full Rust SDK reference, see the [Rust Spin SDK documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html).
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/)
+
+Building a Spin HTTP component with the JavaScript/TypeScript SDK now involves adding an event listener for the `fetch` event. This event listener handles incoming HTTP requests and allows you to construct and return HTTP responses.
+
+Below is a complete implementation for such a component in TypeScript:
+
+```ts
+import { AutoRouter } from 'itty-router';
+
+let router = AutoRouter();
+
+router
+ .get("/", () => new Response("hello universe"))
+ .get('/hello/:name', ({ name }) => `Hello, ${name}!`)
+
+//@ts-ignore
+addEventListener('fetch', async (event: FetchEvent) => {
+ event.respondWith(router.fetch(event.request));
+});
+```
+
+{{ blockEnd }}
+
+{{ startTab "Python"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v4/)
+
+In Python, the application must define a top-level class named `HttpHandler` which inherits from [http.Handler](https://spinframework.github.io/spin-python-sdk/v4/http/index.html#spin_sdk.http.Handler), overriding the `handle_request` method.
+
+```python
+from spin_sdk import http
+from spin_sdk.http import Request, Response
+
+class HttpHandler(http.Handler):
+ async def handle_request(self, request: Request) -> Response:
+ return Response(
+ 200,
+ {"content-type": "text/plain"},
+ bytes("Hello from Python!", "utf-8")
+ )
+```
+
+You can find a complete example for handling a HTTP request in the [Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples/hello).
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/http)
+
+In Go, you register the handler as a callback in your program's `init` function. Set `handler.Exports.Handle` to your handler function. Your handler takes a `*Request` pointer, and returns a `Result[Response, ErrorCode]` with the response.
+
+```go
+package main
+
+import (
+ "fmt"
+ "net/http"
+
+ handler "github.com/spinframework/spin-go-sdk/v3/exports/wasi_http_service_0_3_0_rc_2026_03_15/export_wasi_http_0_3_0_rc_2026_03_15_handler"
+ _ "github.com/spinframework/spin-go-sdk/v3/exports/wasi_http_service_0_3_0_rc_2026_03_15/wit_exports"
+ . "github.com/spinframework/spin-go-sdk/v3/imports/wasi_http_0_3_0_rc_2026_03_15_types"
+ . "go.bytecodealliance.org/pkg/wit/types")
+
+func Handle(request *Request) Result[*Response, ErrorCode] {
+ tx, rx := MakeStreamU8()
+
+ go func() {
+ defer tx.Drop()
+ tx.WriteAll([]uint8("hello, world!"))
+ }()
+
+ response, send := ResponseNew(
+ FieldsFromList([]Tuple2[string, []uint8]{
+ Tuple2[string, []uint8]{"content-type", []uint8("text/plain")},
+ }).Ok(),
+ Some(rx),
+ trailersFuture(),
+ )
+ send.Drop()
+
+ return Ok[*Response, ErrorCode](response)
+}
+
+func trailersFuture() *FutureReader[Result[Option[*Fields], ErrorCode]] {
+ tx, rx := MakeFutureResultOptionFieldsErrorCode()
+ go tx.Write(Ok[Option[*Fields], ErrorCode](None[*Fields]()))
+ return rx
+}
+
+func init() {
+ handler.Exports.Handle = Handle
+}
+
+func main() {}
+```
+
+See [Go Components](./go-components) for an explanation of this code.
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+### Getting Request and Response Information
+
+Exactly how the Spin SDK surfaces the request and response types varies from language to language; this section calls out general features.
+
+* In the request record, the URL contains the path and query, but not the scheme and host. For example, in a request to `https://example.com/shop/users/1/cart/items/3/edit?theme=pink`, the URL contains `/shop/users/1/cart/items/3/edit?theme=pink`. If you need the full URL, you can get it from the `spin-full-url` header - see the table below.
+
+### Additional Request Information
+
+As well as any headers passed by the client, Spin sets several headers on the request passed to your component, which you can use to access additional information about the HTTP request.
+
+> In the following table, the examples suppose that:
+> * Spin is listening on `example.com:8080`
+> * The trigger `route` is `/users/:userid/cart/...`
+> * The request is to `https://example.com:8080/users/1/cart/items/3/edit?theme=pink`
+
+| Header Name | Value | Example |
+|------------------------------|----------------------|---------|
+| `spin-full-url` | The full URL of the request. This includes full host and scheme information. | `https://example.com:8080/users/1/cart/items/3/edit?theme=pink` |
+| `spin-path-info` | The request path relative to the component route | `/items/3/edit` |
+| `spin-path-match-n` | Where `n` is the pattern for our single-segment wildcard value (e.g. `spin-path-match-userid` will access the value in the URL that represents `:userid`) | `1` |
+| `spin-matched-route` | The part of the trigger route that was matched by the route (including the wildcard indicator if present) | `/users/:userid/cart/...` |
+| `spin-raw-component-route` | The component route pattern matched, including the wildcard indicator if present | `/users/:userid/cart/...` |
+| `spin-component-route` | The component route pattern matched, _excluding_ any wildcard indicator | `/users/:userid/cart` |
+| `spin-client-addr` | The IP address and port of the client. Some Spin runtimes do not set this header. | `127.0.0.1:53152` |
+
+### Inside HTTP Components
+
+For the most part, you'll build HTTP component modules using a language SDK (see the Language Guides section), such as a JavaScript module or a Rust crate. If you're interested in what happens inside the SDK, or want to implement HTTP components in another language, read on!
+
+The HTTP component interface is defined using a WebAssembly Interface (WIT) file. ([Learn more about the WIT language here.](https://component-model.bytecodealliance.org/design/wit.html)). You can find the latest WITs for Spin HTTP components at [https://github.com/spinframework/spin/tree/main/wit](https://github.com/spinframework/spin/tree/main/wit).
+
+The HTTP types and interfaces are defined in [https://github.com/spinframework/spin/tree/main/wit/deps/http@0.3.0-rc-2026-03-15](https://github.com/spinframework/spin/tree/main/wit/deps/http@0.3.0-rc-2026-03-15), which tracks [the `wasi-http` specification](https://github.com/WebAssembly/wasi-http).
+
+In particular, the entry point for Spin HTTP components is defined in [the `handler` interface](https://github.com/spinframework/spin/blob/main/wit/deps/http@0.3.0-rc-2026-03-15/worlds.wit):
+
+
+
+```fsharp
+// handler.wit
+
+interface handler {
+ use types.{request, response, error-code};
+
+ /// This function may be called with either an incoming request read from the
+ /// network or a request synthesized or forwarded by another component.
+ handle: async func(
+ request: request,
+ ) -> result;
+}
+```
+
+This is the interface that all HTTP components must implement, and which is used by the Spin HTTP executor when instantiating and invoking the component.
+
+However, this is not necessarily the interface you, the component author, work with. In many cases, you will use a more idiomatic wrapper provided by the Spin SDK, which implements the "true" interface internally.
+
+But if you wish, and if your language supports it, you can implement the `incoming-handler` interface directly, using tools such as the
+[Bytecode Alliance `wit-bindgen` project](https://github.com/bytecodealliance/wit-bindgen). Spin will happily load and run such a component. This is exactly how Spin SDKs, such as the [Rust](rust-components) SDK, are built; as component authoring tools roll out for Go, JavaScript, Python, and other languages, you'll be able to use those tools to build `wasi-http` handlers and therefore Spin HTTP components.
+
+## Static Responses with the HTTP Trigger
+
+You can write short, static responses within the HTTP trigger by setting `static_response` (instead of `component`):
+
+```toml
+# Example use case: fallback 404 handling
+[[trigger.http]]
+route = "/..."
+static_response = { status_code = 404, body = "not found" }
+
+# Example use case: redirect
+[[trigger.http]]
+route = "/bob"
+static_response = { status_code = 302, headers = { location = "/users/bob" } }
+```
+
+Static responses may have only text or empty bodies.
+
+## HTTP With Wagi (WebAssembly Gateway Interface)
+
+A number of languages support WASI Preview 1 but not the component model. To enable developers to use these languages, Spin supports an
+HTTP executor based on [Wagi](https://github.com/deislabs/wagi), or the
+WebAssembly Gateway Interface, a project that implements the
+[Common Gateway Interface](https://datatracker.ietf.org/doc/html/rfc3875)
+specification for WebAssembly.
+
+Wagi allows a module built in any programming language that compiles to [WASI](https://wasi.dev/)
+to handle an HTTP request by passing the HTTP request information to the module's
+standard input, environment variables, and arguments, and expecting the HTTP
+responses through the module's standard output.
+This means that if a language has support for the WebAssembly System Interface,
+it can be used to build Spin HTTP components.
+The Wagi model is only used to parse the HTTP request and response. Everything
+else — defining the application, running it, or [distributing](./distributing-apps.md)
+is done the same way as a component that uses the Spin executor.
+
+### Wagi Component Requirements
+
+Spin uses the component model by default, and cannot detect from the Wasm module alone whether it was built with component model support. For Wagi components, therefore, you must tell Spin in the component manifest to run them using Wagi instead of 'default' Spin. To do this, use the `executor` field in the `trigger` table:
+
+```toml
+[[trigger.http]]
+route = "/"
+component = "wagi-test"
+executor = { type = "wagi" }
+```
+
+> If, for whatever reason, you want to highlight that a component uses the default Spin execution model, you can write `executor = { type = "spin" }`. But this is the default and is rarely written out.
+
+Wagi supports non-default entry points, and allows you to pass an arguments string that a program can receive as if it had been passed on the command line. If you need these you can specify them in the `executor` table. For details, see the [Manifest Reference](manifest-reference#the-componenttrigger-table-for-http-applications).
+
+### Request Handling in Wagi
+
+Building a Wagi component in a particular programming language that can compile
+to `wasm32-wasip2` does not require any special libraries — instead,
+[building Wagi components](https://github.com/deislabs/wagi/tree/main/docs) can
+be done by reading the HTTP request from the standard input and environment
+variables, and sending the HTTP response to the module's standard output.
+
+In pseudo-code, this is the minimum required in a Wagi component:
+
+- either the `content-media` or `location` headers must be set — this is done by
+printing its value to standard output
+- an empty line between the headers and the body
+- the response body printed to standard output:
+
+
+
+```text
+print("content-type: text/html; charset=UTF-8\n\n");
+print("hello world\n");
+```
+
+Here is a working example, written in [Grain](https://grain-lang.org/),
+a programming language that natively targets WebAssembly and WASI but
+does not yet support the component model:
+
+```js
+import Process from "sys/process";
+import Array from "array";
+
+print("content-type: text/plain\n");
+
+// This will print all the Wagi env variables
+print("==== Environment: ====");
+Array.forEach(print, Process.env());
+
+// This will print the route path followed by each query
+// param. So /foo?bar=baz will be ["/foo", "bar=baz"].
+print("==== Args: ====");
+Array.forEach(print, Process.argv());
+```
+
+> You can find examples on how to build Wagi applications in
+> [the DeisLabs GitHub organization](https://github.com/deislabs?q=wagi&type=public&language=&sort=).
+
+### Wagi HTTP Environment Variables
+
+Wagi passes request metadata to the program through well-known environment variables. The key path-related request variables are:
+
+- `X_FULL_URL` - the full URL of the request —
+ `http://localhost:3000/hello/abc/def?foo=bar`
+- `PATH_INFO` - the path info, relative to the
+ component route — in our example, where the the
+ component route is `/hello`, this is `/abc/def`.
+- `X_MATCHED_ROUTE` - the route pattern matched (including the
+ wildcard pattern, if applicable) — in our case `"/hello/..."`.
+- `X_RAW_COMPONENT_ROUTE` - the route pattern matched (including the wildcard
+ pattern, if applicable) — in our case `/hello/...`.
+- `X_COMPONENT_ROUTE` - the route path matched (stripped of the wildcard
+ pattern) — in our case `/hello`
+
+For details, and for a full list of all Wagi environment variables, see
+[the Wagi documentation](https://github.com/deislabs/wagi/blob/main/docs/environment_variables.md).
+
+## Exposing HTTP Triggers Using HTTPS
+
+When exposing HTTP triggers using HTTPS you must provide `spin up` with a TLS certificate and a private key. This can be achieved by either using trigger options (`--tls-cert` and `--tls-key`) when running the `spin up` command, or by setting environment variables (`SPIN_TLS_CERT` and `SPIN_TLS_KEY`) before running the `spin up` command.
+
+### Trigger Options
+
+The `spin up` command accepts some HTTP-trigger-specific options:
+
+The `--listen` option sets the local IP and port that `spin up` should listen to for requests. By default, it listens to `localhost:3000`.
+
+The `--tls-cert` and `--tls-key` options provide a way for you to configure a TLS certificate. If they are not set, plaintext HTTP will be used. The `--tls-cert` option specifies the path to the TLS certificate to use for HTTPS. The certificate should be in PEM format. The `--tls-key` option specifies the path to the private key to use for HTTPS. The key should be in PKCS#8 format.
+
+### Environment Variables
+
+The `spin up` command can also automatically use the `SPIN_TLS_CERT` and `SPIN_TLS_KEY` environment variables instead of the respective flags (`--tls-cert` and `--tls-key`):
+
+
+
+```bash
+SPIN_TLS_CERT=
+SPIN_TLS_KEY=
+```
+
+Once set, `spin up` will automatically use these explicitly set environment variables. For example, if using a Linux-based system, you can go ahead and use the `export` command to set the variables in your session (before you run the `spin up` command):
+
+
+
+```bash
+export SPIN_TLS_CERT=
+export SPIN_TLS_KEY=
+```
+
+## Controlling Instance Reuse
+
+Instance reuse is when Spin handles more than one HTTP request within a single component instance. Instance reuse improves performance and density, because Spin does not need to create a new instance for every request. Spin can reuse instances both consecutively and concurrently - that is, it can assign a new request to either a freshly created instance, an instance that has finished handling a request, or an instance that is _in the middle of_ handling another request.
+
+The exact details depend on your component.
+
+A WASI P2 HTTP component is _not_ subject to instance reuse. This includes all triggers other than HTTP.
+
+A WASI P3 HTTP component is _always_ subject to instance reuse, unless it calls specific APIs to prevent concurrent use. You can control instance reuse behaviour using the following `spin up` flags:
+
+* `--max-instance-reuse-count` sets the maximum number of times a single instance can be reused
+* `--max-instance-concurrent-reuse-count` sets the maximum number of requests that can be running in a single instance at the same time
+* `--idle-instance-timeout` controls how long Spin will allow a reusable instance to sit idle before evicting it
+
+All of these flags accept ranges. When you provide a range, Spin assigns each new instance a random value from that range. This is to help you test that your component works correctly both when fresh (e.g. you do not rely on long-running state) and when reused (e.g. you are not unintentionally leaking data from one request to another).
+
+### Developer Guidelines for Instance Reuse
+
+When writing WASI P3 HTTP components, you can take advantage of reuse in your code, by placing static data in static or global variables, which will become part of the instance state. For example, if your component contains a routing table, you could cache the parsed table in a static variable - you would then parse the table only if the cache had not been initialised (i.e. in a fresh instance), avoiding the overhead of parsing on every request.
+
+> Don't rely on techniques like this for expensive operations. Spin doesn't guarantee the degree of instance reuse, and reuse may vary across different Spin hosts.
+
+Conversely, take care that request data is not stored in static or global variables. If you're used to the WASI P2 model, you may have an implicit expectation that each request finds your component in an entirely fresh state. In WASI P3, that's no longer the case. You should store data that's private to a request in local variables; if you must store it in a static variable, make sure to isolate it (for example storing it in a map under a request-specific key).
+
+### Preventing Instance Reuse
+
+Although you can control instance reuse on the `spin up` command line, this isn't necessarily available in other hosts. If the structure of your code means that it's not safe to reuse instances, then you can use Wasm Component Model backpressure functions in your code to tell the host not to schedule further requests to the current instance. How these are surfaced will depend on your language - for example, in Rust you would use `spin_sdk::wit_bindgen::backpressure_inc` to suspend re-use and a balancing `spin_sdk::wit_bindgen::backpressure_dec` to re-enable it. See the [Component Model documentation](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Concurrency.md#backpressure) for detailed information.
diff --git a/content/v4/index.md b/content/v4/index.md
new file mode 100644
index 00000000..2285895e
--- /dev/null
+++ b/content/v4/index.md
@@ -0,0 +1,24 @@
+title = "Introducing Spin"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/index.md"
+
+---
+
+Spin is a framework for building and running event-driven microservice applications with WebAssembly (Wasm) components.
+
+Spin uses Wasm because it is **sandboxed, portable, and fast**. Millisecond cold start times mean no need to keep applications "warm".
+
+Many languages have Wasm implementations, so **developers don't have to learn new languages or libraries**.
+
+Spin is **open source**, under the aegis of the [CNCF](https://www.cncf.io/), and **built on standards**, meaning you can take your Spin applications anywhere. There are Spin implementations for local development, for self-hosted servers, for Kubernetes, and for cloud-hosted services.
+
+**Want to see the kinds of things people are building with Spin?** Check out what's [Built With Spin](./see-what-people-have-built-with-spin)!
+
+Or dive into the documentation and get started:
+
+- [Take Spin for a spin](quickstart)
+- [Install Spin](install)
+- [Learn how to write Spin applications](writing-apps)
\ No newline at end of file
diff --git a/content/v4/install.md b/content/v4/install.md
new file mode 100644
index 00000000..7adfe67b
--- /dev/null
+++ b/content/v4/install.md
@@ -0,0 +1,313 @@
+title = "Install Spin"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/install.md"
+keywords = "install"
+
+---
+- [Installing Spin](#installing-spin)
+- [Verifying the Release Signature](#verifying-the-release-signature)
+- [Building Spin From Source](#building-spin-from-source)
+- [Using Cargo to Install Spin](#using-cargo-to-install-spin)
+- [Installing Templates and Plugins](#installing-templates-and-plugins)
+ - [Templates](#templates)
+ - [Plugins](#plugins)
+- [Next Steps](#next-steps)
+
+## Installing Spin
+
+Spin runs on Linux (amd64 and arm64), macOS (Intel and Apple Silicon), and Windows (amd64):
+
+{{ tabs "os" }}
+
+{{ startTab "Linux"}}
+
+**Homebrew**
+
+You can manage your Spin installation via [Homebrew](https://brew.sh/). Homebrew automatically installs Spin templates and Spin plugins, and on uninstall, will prompt you to delete the directory where the templates and plugins were downloaded:
+
+Install the Fermyon tap, which Homebrew tracks, updates, and installs Spin from:
+
+
+
+```bash
+$ brew tap spinframework/tap
+```
+
+Install Spin:
+
+
+
+```bash
+$ brew install spinframework/tap/spin
+```
+
+> Note: `brew install spin` will **not** install Spin framework. Spin is accessed from the `spinframework` tap, as shown above.
+
+**Installer script**
+
+Another option (other than brew) is to use our installer script. The installer script installs Spin along with a starter set of language templates and plugins:
+
+
+
+```bash
+$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash
+```
+
+Once you have run the installer script, it is highly recommended to add Spin to a folder, which is on your path, e.g.:
+
+
+
+```bash
+$ sudo mv spin /usr/local/bin/
+```
+
+> If you have already installed Spin by building from source, and then install it via the installer, we recommend you remove the older source install by running `cargo uninstall spin-cli` Otherwise the Cargo path may take precedence over the "install from binary" path, and commands may get the "wrong" version of Spin. Use `spin --version` to confirm the version on the PATH is the one you intend, or `which spin` to confirm the path it is found on.
+
+To install a specific version (`v1.2.3` is just an example), you can pass arguments to the install script this way:
+
+
+
+```bash
+$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash -s -- -v v1.2.3
+```
+
+To install the canary version of spin, you should pass the argument `-v canary`. The canary version is always the latest commit to the main branch of Spin:
+
+
+
+```bash
+$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash -s -- -v canary
+```
+
+{{ blockEnd }}
+
+{{ startTab "macOS"}}
+
+**Homebrew**
+
+You can manage your Spin installation via [Homebrew](https://brew.sh/). Homebrew automatically installs Spin templates and Spin plugins, and on uninstall, will prompt you to delete the directory where the templates and plugins were downloaded:
+
+Install the Fermyon tap, which Homebrew tracks, updates, and installs Spin from:
+
+
+
+```bash
+$ brew tap spinframework/tap
+```
+
+Install Spin:
+
+
+
+```bash
+$ brew install spinframework/tap/spin
+```
+
+> Note: `brew install spin` will **not** install Spin framework. Spin is accessed from the `spinframework` tap, as shown above.
+
+**Installer script**
+
+Another option (other than brew) is to use our installer script. The installer script installs Spin along with a starter set of language templates and plugins:
+
+The installer script also installs Spin along with a starter set of language templates and plugins:
+
+
+
+```bash
+$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash
+```
+
+Once you have run the installer script, it is highly recommended to add Spin to a folder, which is on your path, e.g.:
+
+
+
+```bash
+$ sudo mv spin /usr/local/bin/
+```
+
+> If you have already installed Spin by building from source, and then install it via the installer, we recommend you remove the older source install by running `cargo uninstall spin-cli` Otherwise the Cargo path may take precedence over the "install from binary" path, and commands may get the "wrong" version of Spin. Use `spin --version` to confirm the version on the PATH is the one you intend, or `which spin` to confirm the path it is found on.
+
+To install a specific version (`v1.2.3` is just an example), you can pass arguments to the install script this way:
+
+
+
+```bash
+$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash -s -- -v v1.2.3
+```
+
+To install the canary version of spin, you should pass the argument `-v canary`. The canary version is always the latest commit to the main branch of Spin:
+
+
+
+```bash
+$ curl -fsSL https://spinframework.dev/downloads/install.sh | bash -s -- -v canary
+```
+
+{{ blockEnd }}
+
+{{ startTab "Windows"}}
+
+If using Windows (PowerShell / cmd.exe), you can download the Windows binary release of Spin.
+
+Simply unzip the binary release and place the `spin.exe` in your system path.
+
+This does not install any Spin templates or plugins. For a starter list, see the [Installing Templates and Plugins section](#installing-templates-and-plugins).
+
+If you want to use WSL2 (Windows Subsystem for Linux 2), please follow the instructions for using Linux.
+
+{{ blockEnd }}
+{{ blockEnd }}
+
+## Verifying the Release Signature
+
+The Spin project [signs releases](https://github.com/spinframework/spin/blob/main/docs/content/sips/012-signing-spin-releases.md) using [Sigstore](https://docs.sigstore.dev/), a project that helps with signing software and _stores signatures in a tamper-resistant public log_. Consumers of Spin releases can validate the integrity of the package they downloaded by performing a validation of the artifact against the signature present in the public log. Specifically, users get two main guarantees by verifying the signature: 1) that the author of the artifact is indeed the one expected (i.e. the build infrastructure associated with the Spin project, at a given revision that can be inspected), and 2) that the content generated by the build infrastructure has not been tampered with.
+
+To verify the release signature, first [configure Cosign v2.0.0+](https://docs.sigstore.dev/cosign/system_config/installation/). This is the CLI tool that we will use validate the signature.
+The same directory where the installation script was run should also contain a signature of the Spin binary and the certificate used to perform the signature. The following command will perform the signature verification using the `cosign` CLI:
+
+
+
+```bash
+$ cosign verify-blob \
+ --signature spin.sig \
+ --certificate crt.pem \
+ --certificate-identity https://github.com/spinframework/spin/.github/workflows/release.yml@refs/tags/ \
+ --certificate-oidc-issuer https://token.actions.githubusercontent.com \
+ # --certificate-github-workflow-sha \
+ ./spin
+Verified OK
+```
+
+You can now move the Spin binary to the path knowing that it was indeed built by the infrastructure associated with the Spin project, and that it has not been tampered with since the build.
+
+## Building Spin From Source
+
+[Follow the contribution document](./contributing-spin.md) for a detailed guide on building Spin from source:
+
+
+
+```bash
+$ git clone https://github.com/spinframework/spin
+$ cd spin && make build
+$ ./target/release/spin --help
+```
+
+> Please note: On a fresh Linux installation, you will also need the standard build toolchain (`gcc`, `make`, etc.), the SSL library headers, and on some distributions you may need `pkg-config`. For example, on Debian-like distributions, including Ubuntu, you can install the standard build toolchain with this command:
+
+
+
+```bash
+$ sudo apt-get install build-essential libssl-dev pkg-config
+```
+
+This does not install any Spin templates or plugins. For a starter list, see the [Installing Templates and Plugins section](#installing-templates-and-plugins).
+
+## Using Cargo to Install Spin
+
+If you have [`cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html), you can clone the repo and install it to your path.
+
+> Note: The `main` branch may have unstable or breaking changes. It is advised to check out the latest Spin release tag, which can be found by navigating to https://github.com/spinframework/spin/releases/latest.
+
+
+
+```bash
+$ git clone https://github.com/spinframework/spin
+$ cd spin
+$ # Check out the latest tagged release
+$ # git checkout
+$ rustup target add wasm32-wasip2
+$ rustup target add wasm32-unknown-unknown
+$ cargo install --locked --path .
+$ spin --help
+```
+
+> Please note: Installing Spin from source requires Rust 1.79 or newer. You can update Rust using the following command:
+
+
+
+```bash
+$ rustup update
+```
+
+Installing Spin from source does not install any Spin templates or plugins. For a starter list, see the [Installing Templates and Plugins section](#installing-templates-and-plugins).
+
+## Installing Templates and Plugins
+
+Spin has a variety of templates and plugins to make it easier to create Spin applications in your favorite programming language. [The install script](install#installing-spin) automatically installs a starter set of templates from the Spin Framework project.
+
+If you used a different installation method, you can install templates and plugins manually, as follows.
+
+### Templates
+
+Rust, Go and miscellaneous other languages:
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin --upgrade
+```
+
+Python:
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin-python-sdk --upgrade
+```
+
+JavaScript and TypeScript:
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin-js-sdk --upgrade
+```
+
+To list installed templates, run:
+
+
+
+```bash
+$ spin templates list
+```
+
+For more information please read the [managing templates](./managing-templates) section of the documentation.
+
+### Plugins
+
+First update the local cache by running the `spin plugins update` command:
+
+
+
+```bash
+$ spin plugins update
+```
+
+To list available plugins, run:
+
+
+
+```bash
+$ spin plugins search
+```
+
+Then install plugins by name:
+
+
+
+```bash
+$ spin plugins install foo --yes
+```
+
+For more information, please visit the [managing plugins](./managing-plugins) section of the documentation.
+
+## Next Steps
+
+{{suh_cards}}
+{{card_element "sample" "Checklist Sample App" "A checklist app that persists data in a key value store" "/hub/preview/sample_checklist" "Typescript,Http,Kv" true }}
+{{card_element "template" "Zola SSG Template" "A template for using Zola framework to create a static webpage" "/hub/preview/template_zola_ssg" "rust" true }}
+{{card_element "sample" "AI-assisted News Summarizer" "Read an RSS newsfeed and have AI summarize it for you" "/hub/preview/sample_newsreader_ai" "Typescript,Javascript,Ai" true }}
+{{blockEnd}}
diff --git a/content/v4/javascript-components.md b/content/v4/javascript-components.md
new file mode 100644
index 00000000..2c645b8c
--- /dev/null
+++ b/content/v4/javascript-components.md
@@ -0,0 +1,455 @@
+title = "Building Spin Components in JavaScript"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/javascript-components.md"
+
+---
+- [Installing Templates](#installing-templates)
+- [Structure of a JS/TS Component](#structure-of-a-jsts-component)
+- [Building and Running the Template](#building-and-running-the-template)
+- [HTTP Components](#http-components)
+- [Sending Outbound HTTP Requests](#sending-outbound-http-requests)
+ - [Intra-Application Requests in JavaScript](#intra-application-requests-in-javascript)
+- [Storing Data in Redis From JS/TS Components](#storing-data-in-redis-from-jsts-components)
+- [Storing Data in the Spin Key-Value Store](#storing-data-in-the-spin-key-value-store)
+- [Storing Data in SQLite](#storing-data-in-sqlite)
+- [Storing Data in MySQL and PostgreSQL Relational Databases](#storing-data-in-mysql-and-postgresql-relational-databases)
+- [AI Inferencing From JS/TS Components](#ai-inferencing-from-jsts-components)
+- [Node.js Compatibility](#nodejs-compatibility)
+- [Using External NPM Libraries](#using-external-npm-libraries)
+ - [Suggested Libraries for Common Tasks](#suggested-libraries-for-common-tasks)
+- [Caveats](#caveats)
+
+With JavaScript being a very popular language, Spin provides an SDK to support building components. The development of the JavaScript SDK is continually being worked on to improve user experience and add features. The SDK is based on [`ComponentizeJS`](https://github.com/bytecodealliance/ComponentizeJS).
+
+> This guide assumes you have Spin installed. If this is your first encounter with Spin, please see the [Quick Start](quickstart), which includes information about installing Spin with the JavaScript templates and creating JavaScript and TypeScript applications.
+
+> This guide assumes you are familiar with the JavaScript programming language,
+> but if you are just getting started, be sure to check [the MDN guide](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide).
+
+> All examples from this page can be found in [the JavaScript SDK repository on GitHub](https://github.com/spinframework/spin-js-sdk/tree/main/examples).
+
+[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/)
+
+## Installing Templates
+
+The JavaScript/TypeScript templates can be installed from [spin-js-sdk repository](https://github.com/spinframework/spin-js-sdk/tree/main/) using the following command:
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin-js-sdk --update
+```
+
+which will install the `http-js` and `http-ts` templates:
+
+
+
+```text
+Copying remote template source
+Installing template http-ts...
+Installing template http-js...
+Installed 2 template(s)
+
++-------------------------------------------------+
+| Name Description |
++=================================================+
+| http-js HTTP request handler using Javascript |
+| http-ts HTTP request handler using Typescript |
++-------------------------------------------------+
+```
+
+## Structure of a JS/TS Component
+
+A new JS/TS component can be created using the following command:
+
+
+
+```bash
+$ spin new -t http-ts hello-world --accept-defaults
+```
+
+This creates a directory of the following structure:
+
+
+
+```text
+hello-world
+├── package.json
+├── README.md
+├── spin.toml
+├── src
+│ └── index.ts
+├── tsconfig.json
+└── webpack.config.js
+```
+
+The source for the component is present in `src/index.ts`. [Webpack](https://webpack.js.org) is used to bundle the component into a single `.js` file which will then be compiled to a `.wasm` module.
+
+{{ details "Going from JavaScript to Wasm" "The JS source is compiled to a `wasm` module using the `j2w` node executable provided by the `@fermyon/spin-sdk` which is a wrapper around `ComponentizeJS` that is used to manage the dependencies."}}
+
+## Building and Running the Template
+
+First, the dependencies for the template need to be installed using the following commands:
+
+
+
+```bash
+$ cd hello-world
+$ npm install
+```
+
+Next step is to use the `spin build` command to run the build script for the component. Once a Spin compatible module is created, it can be run using `spin up`:
+
+
+
+```bash
+$ spin build
+$ spin up
+```
+
+`spin build` will execute the command in the `command` key under the `[component..build]` section from `spin.toml` for each component in your application. In this case an `npm` script will be run. The command in the `package.json` will looks something like:
+
+
+
+```json
+"scripts": {
+ "build": "npx webpack --mode=production && npx mkdirp target && npx j2w -i dist.js -d combined-wit -n combined -o target/hello-world.wasm",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ }
+```
+
+---
+
+## HTTP Components
+
+In Spin, HTTP components are triggered by the occurrence of an HTTP request, and
+must return an HTTP response at the end of their execution. Components can be
+built in any language that compiles to WASI, and Javascript/TypeScript has improved support
+for writing Spin components with the Spin JS/TS SDK.
+
+> Make sure to read [the page describing the HTTP trigger](./http-trigger.md) for more
+> details about building HTTP applications.
+
+Building a Spin HTTP component with the JavaScript/TypeScript SDK now involves adding an event listener for the `fetch` event. This event listener handles incoming HTTP requests and allows you to construct and return HTTP responses.
+
+Below is a complete implementation for such a component in TypeScript:
+
+```javascript
+import { AutoRouter } from 'itty-router';
+
+let router = AutoRouter();
+
+router
+ .get("/", () => new Response("hello universe"))
+ .get('/hello/:name', ({ name }) => `Hello, ${name}!`)
+
+//@ts-ignore
+addEventListener('fetch', async (event: FetchEvent) => {
+ event.respondWith(router.fetch(event.request));
+});
+
+```
+
+## Sending Outbound HTTP Requests
+
+If allowed, Spin components can send outbound HTTP requests using the `fetch` function.
+Let's see an example of a component that makes a request to [an API that returns random animal facts](https://random-data-api.fermyon.app/animals/json)
+
+```javascript
+import { AutoRouter } from 'itty-router';
+
+let router = AutoRouter();
+
+router
+ .get("*", getDataFromAPI)
+
+async function getDataFromAPI(_request: Request) {
+ let response = await fetch(
+ 'https://random-data-api.fermyon.app/physics/json',
+ );
+ let data = await response.json();
+ let fact = `Here is a fact: ${data.fact}`;
+ return new Response(fact);
+}
+
+//@ts-ignore
+addEventListener('fetch', async (event: FetchEvent) => {
+ event.respondWith(router.fetch(event.request));
+});
+
+```
+
+Before we can execute this component, we need to add the `random-data-api.fermyon.app`
+domain to the application manifest `allowed_outbound_hosts` list containing the list of
+domains the component is allowed to make HTTP requests to:
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+authors = ["Your Name "]
+description = ""
+name = "hello-world"
+version = "0.1.0"
+
+[[trigger.http]]
+route = "/..."
+component = "hello-world"
+
+[component.hello-world]
+source = "target/hello-world.wasm"
+exclude_files = ["**/node_modules"]
+allowed_outbound_hosts = ["https://random-data-api.fermyon.app"]
+[component.hello-world.build]
+command = "npm run build"
+```
+
+The component can be built using the `spin build` command. Running the application using `spin up` will start the HTTP listener locally (by default on `localhost:3000`):
+
+
+
+```text
+$ curl -i localhost:3000
+HTTP/1.1 200 OK
+date = "2023-11-04T00:00:01Z"
+content-type: application/json; charset=utf-8
+content-length: 185
+server: spin/0.1.0
+
+Here is a fact: Reindeer grow new antlers every year
+```
+
+> Without the `allowed_outbound_hosts` field populated properly in `spin.toml`,
+> the component would not be allowed to send HTTP requests, and sending the
+> request would result in a "Destination not allowed" error.
+
+> You can set `allowed_outbound_hosts = ["https://*:*"]` if you want to allow
+> the component to make requests to any HTTP host. This is not recommended
+> unless you have a specific need to contact arbitrary servers and perform your own safety checks.
+
+We just built a WebAssembly component that sends an HTTP request to another
+service, manipulates that result, then responds to the original request.
+This can be the basis for building components that communicate with external
+databases or storage accounts, or even more specialized components like HTTP
+proxies or URL shorteners.
+
+### Intra-Application Requests in JavaScript
+
+JavaScript's `fetch` function handles relative URLs in a way that doesn't work well with Spin's fine-grained outbound HTTP permissions.
+Therefore, when [making a request to another route within the same application](./http-outbound#intra-application-http-requests-by-route),
+you must use the special pseudo-host `self.alt` rather than a relative route. For example:
+
+```javascript
+await fetch('/api'); // Avoid!
+await fetch('http://self.alt/api'); // Prefer!
+```
+
+You must [add `http://self` or `http://self.alt` to the component's `allowed_outbound_hosts`](./http-outbound#intra-application-http-requests-by-route).
+
+---
+
+## Storing Data in Redis From JS/TS Components
+
+> You can find a complete example for using outbound Redis from an HTTP component
+> in the [spin-js-sdk repository on GitHub](https://github.com/spinframework/spin-js-sdk/blob/main/examples/spin-host-apis/spin-redis).
+
+We can install and use the `@spinframework/spin-redis` package to use the Redis key/value store and to publish messages to Redis channels.
+
+Let's see how we can use the JS/TS SDK to connect to Redis:
+
+```javascript
+import { AutoRouter } from 'itty-router';
+import { Redis } from '@spinframework/spin-redis';
+
+const encoder = new TextEncoder();
+const redisAddress = 'redis://localhost:6379/';
+
+let router = AutoRouter();
+
+router
+ .get("/", () => {
+ try {
+ let db = Redis.open(redisAddress);
+ db.set('test', encoder.encode('Hello world'));
+ let val = db.get('test');
+
+ if (!val) {
+ return new Response(null, { status: 404 });
+ }
+ return new Response(val);
+ } catch (e: any) {
+ return new Response(`Error: ${JSON.stringify(e.payload)}`, { status: 500 });
+ }
+ })
+
+//@ts-ignore
+addEventListener('fetch', async (event: FetchEvent) => {
+ event.respondWith(router.fetch(event.request));
+});
+
+```
+
+This HTTP component demonstrates fetching a value from Redis by key, setting a key with a value, and publishing a message to a Redis channel.
+
+> When using Redis databases hosted on the internet (i.e) not on localhost, the `redisAddress` must be of the format "redis://\:\@\" (e.g) `redis://myUsername:myPassword@redis-database.com`
+
+As with all networking APIs, you must grant access to Redis hosts via the `allowed_outbound_hosts` field in the application manifest:
+
+
+
+```toml
+[component.storage-demo]
+allowed_outbound_hosts = ["redis://localhost:6379"]
+```
+
+## Storing Data in the Spin Key-Value Store
+
+Spin has a key-value store built in. For information about using it from TypeScript/JavaScript, see [the key-value API guide](kv-store-api-guide).
+
+## Storing Data in SQLite
+
+We can use the `@spinframework/spin-sqlite` package to interact with Spin's SQLite interface.
+
+For more information about using SQLite from TypeScript/Javascript, see [SQLite storage](sqlite-api-guide).
+
+## Storing Data in MySQL and PostgreSQL Relational Databases
+
+We can use the `@spinframework/spin-mysql`, `@spinframework/spin-postgres` package to interact with Spin's RDBMS interfaces.
+
+For more information about using relational databases from TypeScript/JavaScript, see [Relational Databases](rdbms-storage).
+
+## AI Inferencing From JS/TS Components
+
+We can use the `@spinframework/spin-llm` package to interact with Spin's LLM interface.
+
+For more information about using Serverless AI from JS/TS, see the [Serverless AI](serverless-ai-api-guide) API guide.
+
+## Node.js Compatibility
+
+The SDK does not support the full specification of `Node.js`. A limited set of APIs can be polyfilled using the [`@fermyon/wasi-ext`](https://github.com/fermyon/js-wasi-ext) library which provides a webpack plugin. It can be used by installing the library first using:
+
+
+
+```bash
+$ npm install @spinframework/wasi-ext
+```
+
+Once installed, the plugin provided by it can be added to the webpack config:
+
+```js
+import WasiExtPlugin from "@spinframework/wasi-ext/plugin/index.js";
+
+export default config = () => {
+ ...
+ return {
+ ...
+ plugins: [
+ new WasiExtPlugin()
+ ],
+ ...
+ }
+}
+```
+
+This library only currently supports the following polyfills:
+
+- `Node.js` buffers
+- `process` - certain methods are no-ops and few throw exceptions. For detailed list refer to the [upstream library](https://github.com/defunctzombie/node-process/blob/master/browser.js). **Note:** `process.env` is populated only inside the handler and returns an empty object outside the handler.
+- `fs` - only implements `readFileSync` and `readdirSync`.
+- `os` - Implements only `EOL` and `arch`.
+
+
+## Using External NPM Libraries
+
+> Not all the NPM packages are guaranteed to work with the SDK as it is not fully compatible with the browser or `Node.js`. It implements only a subset of the API.
+
+Some NPM packages can be installed and used in the component. If a popular library does not work, please open an issue/feature request in the [spin-js-sdk repository](https://github.com/spinframework/spin-js-sdk/issues).
+
+### Suggested Libraries for Common Tasks
+
+These are some of the suggested libraries that have been tested and confirmed to work with the SDK for common tasks.
+
+{{ details "HTML parsers" "- [node-html-parser](https://www.npmjs.com/package/node-html-parser)" }}
+
+{{ details "Parsing formdata" "- [parse-multipart-data](https://www.npmjs.com/package/parse-multipart-data)" }}
+
+{{ details "Runtime schema validation" "- [zod](https://www.npmjs.com/package/zod)" }}
+
+{{ details "Unique ID generator" "- [nanoid](https://www.npmjs.com/package/nanoid)\n- [ulidx](https://www.npmjs.com/package/ulidx)\n- [uuid](https://www.npmjs.com/package/uuid)" }}
+
+## Caveats
+
+- All `spin-sdk` related functions and methods (like `Variables`, `Redis`, `Mysql`, `Pg`, `Kv` and `Sqlite`) can be called only inside the fetch event handler. This includes `fetch`. Any attempts to use it outside the function will lead to an error. This is due to Wizer using only Wasmtime to execute the script at build time, which does not include any Spin SDK support.
+- No crypto operation that involve handling private keys are supported.
+
+## Debugging in VSCode
+
+You can use the experimental [StarlingMonkey Debugger](https://marketplace.visualstudio.com/items?itemName=BytecodeAlliance.starlingmonkey-debugger) to debug JavaScript HTTP components.
+
+> The debugger is a work in progress, and has some known issues and limitations. In particular, you will need to restart it for each request.
+
+
+### Installing the Extension
+
+The extension can be installed from the [extension store](https://marketplace.visualstudio.com/items?itemName=BytecodeAlliance.starlingmonkey-debugger). Verify that the installed version is `0.2.1` or higher.
+
+### Setting up the Project for Debugging
+
+Templates starting from Spin v3.5 include the required setup for supporting debugging. You can verify this by looking at `.vscode/setting.json` and `.vscode/launch.json` and checking if something like the following is configured:
+
+```json
+// .vscode/setting.json
+{
+ "starlingmonkey": {
+ "componentRuntime": {
+ "executable": "spin",
+ "options": [
+ "up",
+ "-f",
+ "${workspaceFolder}",
+ ],
+ }
+ }
+}
+
+// .vscode/launch.json
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "starlingmonkey",
+ "request": "launch",
+ "name": "Debug StarlingMonkey component",
+ "component": "${workspaceFolder}/dist/.wasm",
+ "program": "${workspaceFolder}/src/index.ts",
+ "stopOnEntry": false,
+ "trace": true
+ }
+ ]
+}
+```
+
+If it does not exist, create or edit `settings.json` and `launch.json` yourself, using the [testcase](https://github.com/spinframework/spin-js-sdk/tree/main/test/debugger-testing/spin-ts/.vscode) as a reference. Make sure to set up the `program` field in `launch.json` based on whether it is a JavaScript or TypeScript project.
+
+Once that is set up, update the `build` setting in `spin.toml` to build the debug component. To do this, change the component build `command` to be `npm run build:debug`.
+
+You must also add add `"tcp://127.0.0.1:*"` to the list of `allowed_outbound_hosts`.
+
+Now you can build the app using the familiar `spin build` command.
+
+### Running a Project in the Debugger
+
+You can start the app and attach the debugger to it using the `F5` key or using the Start Debugger button as show below:
+
+
+
+Once the debugger is attached, it can be used in the same way as the built-in VS Code debugger. For example, you can do things like setting breakpoints and stepping through the code.
+
+
+
+**Note:** The debugger does not follow sourcemaps for NPM dependencies, as some packages include sourcemaps but not the actual source code. Breakpoints and stepping work as expected in your own code, but may not work in third-party dependencies.
diff --git a/content/v4/kv-store-api-guide.md b/content/v4/kv-store-api-guide.md
new file mode 100644
index 00000000..0d7e8f45
--- /dev/null
+++ b/content/v4/kv-store-api-guide.md
@@ -0,0 +1,218 @@
+title = "Key Value Store"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/kv-store-api-guide.md"
+
+---
+- [Using Key Value Store From Applications](#using-key-value-store-from-applications)
+- [Custom Key Value Stores](#custom-key-value-stores)
+- [Granting Key Value Store Permissions to Components](#granting-key-value-store-permissions-to-components)
+
+Spin provides an interface for you to persist data in a key value store managed by Spin. This key value store allows Spin developers to persist non-relational data across application invocations.
+
+{{ details "Why do I need a Spin interface? Why can't I just use my own external store?" "You can absolutely still use your own external store either with the Redis or Postgres APIs, or outbound HTTP. However, if you're interested in quick, non-relational local storage without any infrastructure set-up then Spin's key value store is a great option." }}
+
+## Using Key Value Store From Applications
+
+The Spin SDK surfaces the Spin key value store interface to your language. The following characteristics are true of keys and values:
+
+* Keys as large as 256 bytes (UTF-8 encoded)
+* Values as large as 1 megabyte
+* Capacity for 1024 key value tuples
+
+The set of operations is common across all SDKs:
+
+| Operation | Parameters | Returns | Behavior |
+|------------|------------|---------|----------|
+| `open` | name | store | Open the store with the specified name. If `name` is the string "default", the default store is opened, provided that the component that was granted access in the component manifest from `spin.toml`. Otherwise, `name` must refer to a store defined and configured in a [runtime configuration file](./dynamic-configuration.md#key-value-store-runtime-configuration) supplied with the application.|
+| `get` | store, key | value | Get the value associated with the specified `key` from the specified `store`. |
+| `set` | store, key, value | - | Set the `value` associated with the specified `key` in the specified `store`, overwriting any existing value. |
+| `delete` | store, key | - | Delete the tuple with the specified `key` from the specified `store`. `error::invalid-store` will be raised if `store` is not a valid handle to an open store. No error is raised if a tuple did not previously exist for `key`.|
+| `exists` | store, key | boolean | Return whether a tuple exists for the specified `key` in the specified `store`.|
+| `get-keys` | store | stream | Return a stream of all the keys in the specified `store`. NOTE: errors are reported via a future (promise) which resolves once the stream has ended. |
+| `close` | store | - | Close the specified `store`. |
+
+The exact detail of calling these operations from your application depends on your language:
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/key_value/index.html)
+
+Key value functions are available in the `spin_sdk::key_value` module. The function names match the operations above. For example:
+
+```rust
+use bytes::Bytes;
+use spin_sdk::{
+ http::{FullBody, IntoResponse, Request, Response},
+ http_service,
+ key_value::Store,
+};
+
+#[http_service]
+async fn handle_request(_req: Request) -> anyhow::Result {
+ let store = Store::open_default().await?;
+ store.set("mykey", b"myvalue").await?;
+ let value = store.get("mykey").await?;
+ let response = value.unwrap_or_else(|| "not found".into());
+ Ok(Response::new(FullBody::new(Bytes::from(response))))
+}
+```
+
+**General Notes**
+
+`set` **Operation**
+- For set, the value argument can be of any type that implements `AsRef<[u8]>`
+
+`get` **Operation**
+- For get, the return value is of type `Option>`. If the key does not exist it returns `None`.
+
+`get_keys` **Operation**
+- This returns a stream containing the keys, and a future containing a `Result`. You _must_ check the future when the stream ends, to determine if the stream ended normally, or was terminated prematurely due to an error.
+
+`open` and `close` **Operations**
+- The close operation is not surfaced; it is called automatically when the store is dropped.
+
+`set_json` and `get_json` **Operation**
+- Rust applications can [store and retrieve serializable Rust types](./rust-components#storing-data-in-the-spin-key-value-store).
+
+{{ blockEnd }}
+
+{{ startTab "Typescript"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/)
+
+The key value functions can be accessed after opening a store using either [the `open` or the `openDefault` functions](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-kv.html) which returns a [handle to the store](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html). For example:
+
+```ts
+import { AutoRouter } from 'itty-router';
+import { openDefault } from '@spinframework/spin-kv';
+
+let router = AutoRouter();
+
+router
+ .get("/", () => {
+ let store = openDefault()
+ store.set("mykey", "myvalue")
+ return new Response(store.get("mykey") ?? "Key not found");
+ })
+
+//@ts-ignore
+addEventListener('fetch', async (event: FetchEvent) => {
+ event.respondWith(router.fetch(event.request));
+});
+```
+
+**General Notes**
+- The SDK doesn't surface the `close` operation. It automatically closes all stores at the end of the request; there's no way to close them early.
+
+[`get` **Operation**](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html#get)
+- The result is of the type `Uint8Array | null`
+- If the key does not exist, `get` returns `null`
+
+[`set` **Operation**](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html#set)
+- The value argument is of the type `Uint8Array | string | object`.
+
+[`setJson`](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html#setjson) and [`getJson` **Operation**](https://spinframework.github.io/spin-js-sdk/interfaces/_spinframework_spin-kv.Store.html#getjson)
+- Applications can store JavaScript objects using `setJson`; these are serialized within the store as JSON. These serialized objects can be retrieved and deserialized using `getJson`. If you call `getJson` on a key that doesn't exist then it returns an empty object.
+
+{{ blockEnd }}
+
+{{ startTab "Python"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v4/key_value.html)
+
+The key value functions are provided through the `key_value` module in the Python SDK. For example:
+
+```python
+from spin_sdk import http, key_value
+from spin_sdk.http import Request, Response
+
+class HttpHandler(http.Handler):
+ async def handle_request(self, request: Request) -> Response:
+ with await key_value.open_default() as store:
+ await store.set("test", bytes("hello world!", "utf-8"))
+ val = await store.get("test")
+
+ return Response(
+ 200,
+ {"content-type": "text/plain"},
+ val
+ )
+
+```
+
+**General Notes**
+- The Python SDK doesn't surface the `close` operation. It automatically closes all stores at the end of the request; there's no way to close them early.
+
+- To open the default key-value store, you can use the [`key_value.open_default`](https://spinframework.github.io/spin-python-sdk/v4/key_value.html#spin_sdk.key_value.open_default) function. You can use [`key_value.open`](https://spinframework.github.io/spin-python-sdk/v4/key_value.html#spin_sdk.key_value.open) to open any store by label.
+
+- Below is a breakdown of the methods surfaced directly from the underlying [spin-key-value-3.0.0 WIT definition](https://spinframework.github.io/spin-python-sdk/v4/wit/imports/spin_key_value_key_value_3_0_0.html):
+
+ [`open` **Operation**](https://spinframework.github.io/spin-python-sdk/v4/wit/imports/spin_key_value_key_value_3_0_0.html#spin_sdk.wit.imports.spin_key_value_key_value_3_0_0.Store.open)
+ - Open the store with the specified label
+
+ [`get` **Operation**](https://spinframework.github.io/spin-python-sdk/v4/wit/imports/spin_key_value_key_value_3_0_0.html#spin_sdk.wit.imports.spin_key_value_key_value_3_0_0.Store.get)
+ - If a key does not exist, it returns `None`
+
+ [`set` **Operation**](https://spinframework.github.io/spin-python-sdk/v4/wit/imports/spin_key_value_key_value_3_0_0.html#spin_sdk.wit.imports.spin_key_value_key_value_3_0_0.Store.set)
+ - Sets a value associated with the specified key, overwriting any existing value.
+
+ [`delete` **Operation**](https://spinframework.github.io/spin-python-sdk/v4/wit/imports/spin_key_value_key_value_3_0_0.html#spin_sdk.wit.imports.spin_key_value_key_value_3_0_0.Store.delete)
+ - Deletes the specified item from the store
+
+ [`exists` **Operation**](https://spinframework.github.io/spin-python-sdk/v4/wit/imports/spin_key_value_key_value_3_0_0.html#spin_sdk.wit.imports.spin_key_value_key_value_3_0_0.Store.exists)
+ - Return whether the specified key is present in the store
+
+ [`get_keys` **Operation**](https://spinframework.github.io/spin-python-sdk/v4/wit/imports/spin_key_value_key_value_3_0_0.html#spin_sdk.wit.imports.spin_key_value_key_value_3_0_0.Store.get_keys)
+ - Returns a `Tuple` containing a [StreamReader](https://github.com/bytecodealliance/componentize-py/blob/1b3d2e936868307a48fb70941dcad71b54e844f8/bundled/componentize_py_async_support/streams.py#L101) and a [FutureReader](https://github.com/bytecodealliance/componentize-py/blob/1b3d2e936868307a48fb70941dcad71b54e844f8/bundled/componentize_py_async_support/futures.py#L11). You _must_ check when the stream ends, to determine if the stream ended normally, or was terminated prematurely due to an error.
+
+ > If you're familiar with previous versions of the Python SDK, note that `get_keys` no longer returns a list. To get the keys as a list, use `await util.collect(await store.get_keys())`. See [collect](https://spinframework.github.io/spin-python-sdk/v4/util.html#spin_sdk.util.collect) for more details.
+
+You can find a complete Python code example using the Key Value store in the [Spin Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples/spin-kv).
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo"}}
+
+> [**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2@v2.2.1/kv)
+
+Key value functions are provided by the `github.com/spinframework/spin-go-sdk/v2/kv` module. [See Go Packages for reference documentation.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2/kv) For example:
+
+```go
+import "github.com/spinframework/spin-go-sdk/v2/kv"
+
+func example() error {
+ store, err := kv.OpenStore("default")
+ if err != nil {
+ return err
+ }
+ defer store.Close()
+ previous, err := store.Get("mykey")
+ return store.Set("mykey", []byte("myvalue"))
+}
+
+```
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+## Custom Key Value Stores
+
+Spin defines a key-value store named `"default"` and provides automatic backing storage. If you need to customize Spin with additional stores, or to change the backing storage for the default store, you can do so via the `--runtime-config-file` flag and the `runtime-config.toml` file. See [Key Value Store Runtime Configuration](./dynamic-configuration#key-value-store-runtime-configuration) for details.
+
+## Granting Key Value Store Permissions to Components
+
+By default, a given component of an app will not have access to any key value store. Access must be granted specifically to each component via the component manifest:
+
+```toml
+[component.example]
+# Pass in 1 or more key value stores, based on how many you'd like your component to have access to
+key_value_stores = ["", ""]
+```
+
+For example, a component could be given access to the default store using `key_value_stores = ["default"]`.
diff --git a/content/v4/language-support-overview.md b/content/v4/language-support-overview.md
new file mode 100644
index 00000000..847b16a9
--- /dev/null
+++ b/content/v4/language-support-overview.md
@@ -0,0 +1,110 @@
+title = "Language Support Overview"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/language-support-overview.md"
+
+---
+
+This page contains information about language support for Spin features:
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+**[📄 Visit the Rust Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/) to see specific modules, functions, variables and syntax relating to the following Rust features.**
+
+| Feature | SDK Supported? |
+|-----|-----|
+| **Triggers** |
+| [HTTP](./http-trigger) | Supported |
+| [Redis](./redis-trigger) | Supported |
+| **APIs** |
+| [Outbound HTTP](./rust-components.md#sending-outbound-http-requests) | Supported |
+| [Configuration Variables](./variables) | Supported |
+| [Key Value Storage](./kv-store-api-guide) | Supported |
+| [SQLite Storage](./sqlite-api-guide) | Supported |
+| [MySQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported |
+| [PostgreSQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported |
+| [Outbound Redis](./rust-components.md#storing-data-in-redis-from-rust-components) | Supported |
+| [Serverless AI](./serverless-ai-api-guide) | Supported |
+| [MQTT Messaging](./mqtt-outbound) | Supported |
+| **Extensibility** |
+| [Authoring Custom Triggers](./extending-and-embedding) | Supported |
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript"}}
+
+**[📄 Visit the JS/TS Spin SDK reference documentation](https://spinframework.github.io/spin-js-sdk/) to see specific modules, functions, variables and syntax relating to the following TS/JS features.**
+
+| Feature | SDK Supported? |
+|-----|-----|
+| **Triggers** |
+| [HTTP](./javascript-components#http-components) | Supported |
+| Redis | Not Supported |
+| **APIs** |
+| [Outbound HTTP](./javascript-components#sending-outbound-http-requests) | Supported |
+| [Configuration Variables](./dynamic-configuration#custom-config-variables) | Supported |
+| [Key Value Storage](./kv-store-api-guide) | Supported |
+| [SQLite Storage](./sqlite-api-guide) | Supported |
+| [MySQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported |
+| [PostgreSQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported |
+| [Outbound Redis](./javascript-components#storing-data-in-redis-from-jsts-components) | Supported |
+| [Serverless AI](./serverless-ai-api-guide) | Supported |
+| [MQTT Messaging](./mqtt-outbound) | Supported |
+| **Extensibility** |
+| Authoring Custom Triggers | Not Supported |
+
+{{ blockEnd }}
+
+{{ startTab "Python"}}
+
+**[📄 Visit the Python Spin SDK reference documentation](https://spinframework.github.io/spin-python-sdk/v4) to see specific modules, functions, variables and syntax relating to the following Python SDK.**
+
+| Feature | SDK Supported? |
+|-----|-----|
+| **Triggers** |
+| [HTTP](./python-components#a-simple-http-components-example) | Supported |
+| [Redis](./redis-trigger) | Supported |
+| **APIs** |
+| [Outbound HTTP](./python-components#an-outbound-http-example) | Supported |
+| [Configuration Variables](./dynamic-configuration#custom-config-variables) | Supported |
+| [Key Value Storage](./kv-store-api-guide) | Supported |
+| [SQLite Storage](./sqlite-api-guide) | Supported |
+| MySQL | Supported |
+| PostgreSQL | Supported |
+| [Outbound Redis](./python-components#an-outbound-redis-example) | Supported |
+| [Serverless AI](./serverless-ai-api-guide) | Supported |
+| [MQTT Messaging](./mqtt-outbound) | Supported |
+| **Extensibility** |
+| Authoring Custom Triggers | Not Supported |
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo"}}
+
+**[📄 Visit the TinyGo Spin SDK reference documentation](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2) to see specific modules, functions, variables and syntax relating to the following TinyGo SDK.**
+
+| Feature | SDK Supported? |
+|-----|-----|
+| **Triggers** |
+| [HTTP](./go-components#http-components) | Supported |
+| [Redis](./go-components#redis-components) | Supported |
+| **APIs** |
+| [Outbound HTTP](./go-components#sending-outbound-http-requests) | Supported |
+| [Configuration Variables](./dynamic-configuration#custom-config-variables) | Supported |
+| [Key Value Storage](./kv-store-api-guide) | Supported |
+| [SQLite Storage](./sqlite-api-guide) | Supported |
+| [MySQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported |
+| [PostgreSQL](./rdbms-storage#using-mysql-and-postgresql-from-applications) | Supported |
+| [Outbound Redis](./go-components#storing-data-in-redis-from-go-components) | Supported |
+| [Serverless AI](./serverless-ai-api-guide) | Supported |
+| [MQTT Messaging](./mqtt-outbound) | Not Supported |
+| **Extensibility** |
+| Authoring Custom Triggers | Not Supported |
+
+{{ blockEnd }}
+
+{{ blockEnd }}
diff --git a/content/v4/managing-plugins.md b/content/v4/managing-plugins.md
new file mode 100644
index 00000000..2704a24c
--- /dev/null
+++ b/content/v4/managing-plugins.md
@@ -0,0 +1,197 @@
+title = "Managing Plugins"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/managing-plugins.md"
+
+---
+- [Installing Plugins](#installing-plugins)
+ - [Installing Well Known Plugins](#installing-well-known-plugins)
+ - [Installing a Specific Version of a Plugin](#installing-a-specific-version-of-a-plugin)
+ - [Installing a Plugin From a URL](#installing-a-plugin-from-a-url)
+ - [Installing a Plugin From a File](#installing-a-plugin-from-a-file)
+- [Running a Plugin](#running-a-plugin)
+- [Viewing Available Plugins](#viewing-available-plugins)
+ - [Viewing Installed Plugins](#viewing-installed-plugins)
+- [Uninstalling Plugins](#uninstalling-plugins)
+- [Refreshing the Catalogue](#refreshing-the-catalogue)
+- [Upgrading Plugins](#upgrading-plugins)
+- [Downgrading Plugins](#downgrading-plugins)
+- [Next Steps](#next-steps)
+
+Plugins are a way to extend the functionality of Spin. Spin provides commands for installing and removing them, so you don't need to use separate installation tools. When you have installed a plugin into Spin, you can call it as if it were a Spin subcommand. For example, you can run the `otel` the `spin otel` command.
+
+## Installing Plugins
+
+To install plugins, use the `spin plugins install` command. You can install plugins by name from a curated repository, or other plugins from a URL or file system.
+
+### Installing Well Known Plugins
+
+The Spin maintainers curate a catalogue of "known" plugins. You can install plugins from this catalogue by name:
+
+
+
+```bash
+$ spin plugins install cloud
+```
+
+Spin checks that the plugin is available for your version of Spin and your operating system, and prompts you to confirm the installation. To skip the prompt, pass the `--yes` flag.
+
+> The curated plugins catalogue is stored in a GitHub repository. The first time you install a plugin from the catalogue, Spin clones this repository into a local cache and uses it for future install, list and upgrade commands (similar to OS package managers such as `apt`). If you want to see new catalogue entries - new plugins or new versions - you must update the local cache by running the `spin plugins update` command.
+
+### Installing a Specific Version of a Plugin
+
+To install a specific version of a plugin, pass the `--version` flag:
+
+
+
+```bash
+$ spin plugins install cloud --version 0.9.1
+```
+
+### Installing a Plugin From a URL
+
+If the plugin you want has been published on the Web but has not been added to the catalogue, you can install it from its manifest URL. The manifest is the JSON document that links to the binaries for different operating systems and processors. For example:
+
+
+
+```bash
+$ spin plugins install --url https://github.com/spinframework/spin-befunge-sdk/releases/download/v1.4.0/befunge2wasm.json
+```
+
+If the URL requires authorization, pass the HTTP authorization header value via `--auth-header-value`. The value of the flag must be the _full_ header value, including the authorization scheme, not just your token or password. For example:
+
+
+
+```bash
+# URL requires bearer authorization
+$ spin plugins install --auth-header-value "Bearer 12345678" --url https://example.com/tell-no-one.json
+
+# URL requires basic authorization
+$ spin plugins install --auth-header-value "Basic c2xhdHM6SWwwdjNmIXNo" --url https://example.com/tell-no-one.json
+```
+
+### Installing a Plugin From a File
+
+If the plugin you want is in your local file system, you can install it from its manifest file path. The manifest is the JSON document that links to the binaries for different operating systems and processors. For example:
+
+
+
+```bash
+$ spin plugins install --file ~/dev/spin-befunge-sdk/befunge2wasm.json
+```
+
+## Running a Plugin
+
+You run plugins in the same way as built-in Spin subcommands. For example:
+
+
+
+```bash
+$ spin cloud --help
+```
+
+## Viewing Available Plugins
+
+To see what plugins are available in the catalogue, run `spin plugins search`:
+
+
+
+```bash
+$ spin plugins search
+befunge2wasm 1.4.0 [incompatible]
+cloud 0.8.0 [installed]
+cloud 0.9.0
+trigger-sqs 0.1.0
+```
+
+The annotations by the plugins show their status and compatibility:
+
+| Annotation | Meaning |
+|---------------------------------|---------|
+| `[incompatible]` | The plugin does not run on your operating system or processor. |
+| `[installed]` | You have the plugin already installed and available to run. |
+| `[requires other Spin version]` | The plugin can run on your operating system and processor, but is not compatible with the version of Spin you are running. The annotation indicates which versions of Spin it is compatible with. |
+
+### Viewing Installed Plugins
+
+To see only the plugins you have installed, run `spin plugins list --installed`.
+
+## Uninstalling Plugins
+
+You can uninstall plugins using `spin plugins uninstall` with the plugin name:
+
+
+
+```bash
+$ spin plugins uninstall befunge2wasm
+```
+
+## Refreshing the Catalogue
+
+The first time you install a plugin from the catalogue, Spin creates a local cache of the catalogue. It continues to use this local cache for future install, list and upgrade commands; this is similar to OS package managers such as `apt`, and avoids rate limiting on the catalogue. However, this means that in order to see new catalogue entries - new plugins or new versions - you must first update the cache.
+
+To update your local cache of the catalogue, run `spin plugins update`.
+
+## Upgrading Plugins
+
+To upgrade a plugin to the latest version, first run `spin plugins update` (to refresh the catalogue), then `spin plugins upgrade`.
+
+The `spin plugins upgrade` command has the same options as the `spin plugins install` command (according to whether the plugin comes from the catalogue, a URL, or a file). For more information, see the command help by running `spin plugins upgrade --help`.
+
+> The `upgrade` command uses your local cache of the catalogue. This might not include recently added plugins or versions. So always remember to run `spin plugins update` to refresh your local cache of the catalogue before performing the `spin plugins upgrade` command.
+
+The following example shows how to upgrade one plugin at a time (i.e. the `cloud` plugin):
+
+
+
+```bash
+$ spin plugins update
+$ spin plugins upgrade cloud
+```
+
+The following example shows how to upgrade all installed plugins at once:
+
+
+
+```bash
+$ spin plugins update
+$ spin plugins upgrade --all
+```
+
+> Note: The above example only installs plugins from the catalogue
+
+The following example shows additional upgrade options. Specifically, how to upgrade using the path to a remote plugin manifest and how to upgrade using the path to a local plugin manifest:
+
+
+
+```bash
+$ spin plugins upgrade --url https://github.com/spinframework/spin-befunge-sdk/releases/download/v1.7.0/befunge2wasm.json
+$ spin plugins upgrade --file ~/dev/spin-befunge-sdk/befunge2wasm.json
+```
+
+## Downgrading Plugins
+
+By default, Spin will only _upgrade_ plugins. Pass the `--downgrade` flag and specify the `--version` if you want Spin to roll back to an earlier version. The following abridged example (which doesn't list the full console output for simplicity) lists the versions of plugins, downgrades the `cloud` to an older version (`0.9.0`) and then lists the versions again to show the results:
+
+
+
+```bash
+$ spin plugins update
+$ spin plugins list
+// --snip--
+cloud 0.9.0
+cloud 0.9.1 [installed]
+$ spin plugins upgrade cloud --downgrade --version 0.9.0
+$ spin plugins list
+// --snip--
+cloud 0.9.0 [installed]
+cloud 0.9.1
+```
+
+After downgrading, the `[installed]` indicator is aligned with the `0.9.0` version of `cloud`, as intended in the example.
+
+## Next Steps
+
+- [Check out the spin cloud plugin](https://github.com/fermyon/cloud-plugin)
diff --git a/content/v4/managing-templates.md b/content/v4/managing-templates.md
new file mode 100644
index 00000000..5fb5d714
--- /dev/null
+++ b/content/v4/managing-templates.md
@@ -0,0 +1,140 @@
+title = "Managing Templates"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/managing-templates.md"
+
+---
+- [Installing Templates](#installing-templates)
+ - [Installing From the Spin Git Repository](#installing-from-the-spin-git-repository)
+ - [Installing From a Specific Branch](#installing-from-a-specific-branch)
+ - [Installing From a Local Directory](#installing-from-a-local-directory)
+ - [Installing From a Remote Tarball](#installing-from-a-remote-tarball)
+- [Viewing Your Installed Templates](#viewing-your-installed-templates)
+- [Uninstalling Templates](#uninstalling-templates)
+- [Upgrading Templates](#upgrading-templates)
+ - [Upgrading Templates From a Local Directory](#upgrading-templates-from-a-local-directory)
+- [Next Steps](#next-steps)
+
+Templates are a Spin tool for scaffolding new applications and components. You can use them via the `spin new` and `spin add` commands. For more information about creating applications with templates, see [Writing Spin Applications](writing-apps).
+
+## Installing Templates
+
+> This section covers general principles for installing templates. For information about installing templates for specific languages, see [Writing Spin Applications](writing-apps).
+
+To install templates, use the `spin templates install` command. You can install templates from a Git repository, or while [authoring templates](template-authoring) you can install them from a local directory.
+
+### Installing From the Spin Git Repository
+
+To install templates from the Spin Git repository, run `spin templates install --git`:
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin
+```
+
+If you prefer a shorter command, you can just pass the repository id instead of the full URL:
+
+
+
+```bash
+$ spin templates install --git fermyon/spin
+```
+
+The above command installs _all_ templates in the repository.
+
+> Language SDKs often ship templates in their repositories; see the relevant language guide to find out where to get its templates.
+
+### Installing From a Specific Branch
+
+By default, if you install templates from a Git repository, Spin tries to find a repo tag that matches the version of Spin, and installs from that tag. Failing this, it installs from `HEAD`. If you would like to install from a specific tag or branch, pass the `--branch` option:
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin --branch spin/templates/v0.8
+```
+
+### Installing From a Local Directory
+
+To install templates from your local file system, run `spin templates install --dir`.
+
+> The directory you pass must be one that _contains_ a `templates` directory. Don't pass the `templates` directory itself!
+
+
+
+```bash
+# Expects to find a directory ~/dev/spin-befunge-sdk/templates
+$ spin templates install --dir ~/dev/spin-befunge-sdk
+```
+
+See [Template Authoring](template-authoring) for more details on this layout.
+
+### Installing From a Remote Tarball
+
+To install templates from a remote tarball, run `spin templates install --tar`.
+
+> The tarball must have a `/templates` directory at its root, _or_ have a single root directory and have a `templates` directory within that. This slightly complicated rule is so that it works correctly with a GitHub release tarball, which always has a root directory named after the release.
+
+
+
+```bash
+$ spin templates install --tar https://github.com/spinframework/spin/archive/refs/tags/v9.8.7.tar.gz
+```
+
+## Viewing Your Installed Templates
+
+To see what templates you have installed, run `spin templates list`.
+
+You can use the `--verbose` option to see additional information such as where they were installed from.
+
+## Uninstalling Templates
+
+You can uninstall templates using `spin templates uninstall` with the template name:
+
+
+
+```bash
+$ spin templates uninstall redis-befunge
+```
+
+> Spin doesn't currently support uninstalling a whole repo-worth of templates, only individual templates.
+
+## Upgrading Templates
+
+When you upgrade Spin, you will typically want to upgrade your templates to match. This means new applications and components will get dependencies that match the Spin version you are using. To do this, run `spin templates upgrade`:
+
+
+
+```bash
+$ spin templates upgrade
+Select repos to upgrade. Use Space to select/deselect and Enter to confirm selection.
+ [x] https://github.com/spinframework/spin-python-sdk
+ [ ] https://github.com/spinframework/spin (at spin/templates/v1.0)
+> [x] https://github.com/spinframework/spin-js-sdk
+```
+
+Use the cursor keys and the space bar to select the repositories you want to upgrade, then hit Enter to upgrade the selected repositories.
+
+> Upgrading happens at the repo level, not the individual template level. If you've uninstalled templates, upgrading the repo they came from will bring them back.
+
+If you want to upgrade _all_ repositories without being prompted, run `spin templates upgrade --all`.
+
+As mentioned above, if you want to check which templates come from which repositories use `--verbose` i.e. `spin templates list --verbose`.
+
+### Upgrading Templates From a Local Directory
+
+`spin templates upgrade` only upgrades from Git repositories. If you want to upgrade and your templates are in a local directory, run the `spin templates install` command with the `--upgrade` flag:
+
+
+
+```bash
+$ spin templates install --dir ~/dev/spin-befunge-sdk --upgrade
+```
+
+## Next Steps
+
+- [Install the templates for your language](quickstart)
+- [Use your language templates to create an application](writing-apps)
\ No newline at end of file
diff --git a/content/v4/manifest-reference.md b/content/v4/manifest-reference.md
new file mode 100644
index 00000000..ceaa5a7e
--- /dev/null
+++ b/content/v4/manifest-reference.md
@@ -0,0 +1,213 @@
+title = "Spin Application Manifest Reference"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/manifest-reference.md"
+
+---
+- [Manifest Format](#manifest-format)
+- [Using Variables in the Manifest](#using-variables-in-the-manifest)
+- [Manifest Fields](#manifest-fields)
+- [The `application` Table](#the-application-table)
+- [The `application.trigger` Table](#the-applicationtrigger-table)
+ - [The `application.trigger.redis` Table](#the-applicationtriggerredis-table)
+- [The `variables` Table](#the-variables-table)
+- [The `trigger` Table](#the-trigger-table)
+ - [Common Fields for All `trigger.(type)` Tables](#common-fields-for-all-triggertype-tables)
+ - [Additional Fields for `trigger.http` Tables](#additional-fields-for-triggerhttp-tables)
+ - [Additional Fields for `trigger.redis` Tables](#additional-fields-for-triggerredis-tables)
+- [The `component` Table](#the-component-table)
+- [The `component.(id).build` Table](#the-componentidbuild-table)
+- [The `component.(id).profile.(name)` Table](#the-componentidprofilename-table)
+- [Next Steps](#next-steps)
+
+This page describes the contents of the Spin manifest file, typically called `spin.toml`.
+
+> There are two versions of the manifest format. The manifest format described here (version 2) is recommended. If you need to maintain an application that uses the old v1 manifest, [see the Spin 3.x documentation](../v3/manifest-reference-v1).
+
+## Manifest Format
+
+The manifest is a TOML file, and follows standard TOML syntax. See the [TOML documentation](https://toml.io/) for information about the TOML syntax. Here is an example Spin application manifest (`spin.toml`) that was generated using `spin new -t http-rust spin-manifest-example-in-rust`:
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "spin-manifest-example-in-rust"
+version = "0.1.0"
+authors = ["Fermyon Engineering "]
+description = "An example application to generate a Spin manifest file, in this case, via the HTTP Rust template."
+
+[[trigger.http]]
+route = "/..."
+component = "spin-manifest-example-in-rust"
+
+[component.spin-manifest-example-in-rust]
+source = "target/wasm32-wasip2/release/spin_manifest_example_in_rust.wasm"
+allowed_outbound_hosts = []
+[component.spin-manifest-example-in-rust.build]
+command = "cargo build --target wasm32-wasip2 --release"
+watch = ["src/**/*.rs", "Cargo.toml"]
+```
+
+## Using Variables in the Manifest
+
+The following fields allow you to use [expressions](./variables.md#adding-variables-to-your-applications) in their values:
+
+* `application.trigger.redis.address`
+* `trigger.redis.address`
+* `trigger.redis.channel`
+* `component.*.allowed_outbound_hosts`
+
+Spin resolves manifest expressions at application load time. Subsequent changes to variables do not update expression-based values.
+
+The only variables permitted in manifest expressions are application variables.
+
+## Manifest Fields
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `spin_manifest_version` | Required | Number | The version of the file format that the rest of the manifest follows. For manifests using this format, this value must be `2`. | `2` |
+| `application` | Required | Table | Information about the application and application-global options. See [The `application` Table](#the-application-table) below. | `[application]` `name = "greetings"` `version = "1.0.0"` |
+| `variables` | Optional | Table | Application configuration variables which the user can set when they run the application. See [The `variables` Table](#the-variables-table) below. | `[variables]` `message = { default = "hello" }` |
+| `trigger` | Required | Table | Associates triggers and conditions to the components that handle them - for example, mapping a HTTP route to a handling component. See [The `trigger` Table](#the-trigger-table) below. | `[[trigger.http]]` `component = "greeter"` `route = "/greet"` |
+| `component` | Required | Table | The WebAssembly components that make up the application, together with whatever configuration and resources they depend on, such as asset files or storage access. See [The `component` Table](#the-component-table) below. | `[component.greeter]` `source = "greeting_manager.wasm"` `files = ["confetti.jpg"]` |
+
+> If you're familiar with manifest version 1, note that the way trigger parameters map to components - for example, which component handles a particular HTTP route - is now defined on the _trigger_, not on the component. In the version 2 manifest, a `component` section specifies _only_ the Wasm module and the resources it needs.
+
+## The `application` Table
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `name` | Required | String | The name of the application. This may be any string of alphanumeric characters, hyphens and underscores. | `"hello-world"` |
+| `version` | Optional | String | The version of the application. The must be a string of the form `major.minor.patch`, where each element is a number. | `"1.0.5"` |
+| `description` | Optional | String | A human-readable description of the application. | `"The best app for all your world-greeting needs"` |
+| `authors` | Optional | Array of strings | The authors of the applications. If present, this must ba an array, even if it has only one entry. | `["Jane Q Hacker ()"]` |
+| `targets` | Optional | Array of strings | The environments that the application is expected to be compatible with. | ["spin-up:3.2"] |
+| `trigger` | Optional | Table | Application-global trigger settings. See [The `application.trigger` Table](#the-applicationtrigger-table) below. | `[application.trigger.redis]` `address = "redis.example.com"` |
+
+## The `application.trigger` Table
+
+The `application.trigger` should contain only one key, the trigger type whose settings you want to override. This is usually written inline as part of the TOML table header, e.g. `[application.trigger.redis]`.
+
+> In many cases, your trigger will have no settings, or the default ones will suffice. In this case, you can omit the `application.trigger` table.
+
+### The `application.trigger.redis` Table
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `address` | Required | String | The address of the Redis instance to which the components subscribe for messages, for triggers that do not specify an address themselves. Use the `redis:` URL scheme. | `"redis://localhost:6379"` |
+
+## The `variables` Table
+
+The keys of the `variables` table are user-defined. The value of each key is another table with the fields shown in the following table.
+
+> Because each `variables` value usually contains only a few simple fields, you will usually see the table entries written inline with the values written using brace notation, rather than fully written out using square-brackets table syntax for each variable. For example:
+>
+> ```toml
+> [variables]
+> vessel = { description = "I'm a little teapot!", default = "teapot" }
+> token = { required = true, secret = true }
+> ```
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `description` | Optional | String | A brief description of the variable | `This a variable!` |
+| `default` | Optional | String | The value of the variable if no value is supplied at runtime. If specified, the value must be a string. If not specified, `required` must be `true`. | `"teapot"` |
+| `required` | Optional | Boolean | Whether a value must be supplied at runtime. If not specified, `required` defaults to `false`, and `default` must be provided | `false` |
+| `secret` | Optional | Boolean | If set, this variable should be treated as sensitive. | `false` |
+
+## The `trigger` Table
+
+The `trigger` table contains only one key, the trigger type to which your application responds. The value of this key is a table array. In practice, the `trigger` table is written using table array syntax with the trigger type inlined into each entry. For example:
+
+```toml
+[[trigger.http]]
+route = "/users"
+component = "user-manager"
+
+[[trigger.http]]
+route = "/reports"
+component = "report"
+```
+
+Each array entry contains a mix of common fields and trigger-specific fields.
+
+### Common Fields for All `trigger.(type)` Tables
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-----------------|----------|-----------|
+| `component` | Required | String or table | The component to run when a trigger event matching the trigger setting occurs (for example, when Spin receives an HTTP request matching the trigger's `route`). It can be in one of the following formats: | |
+| | | String | * A key in the `component` table | `"user-manager"` |
+| | | Table | * Specifies an unnamed component to be associated with the trigger setting. This allows simple components to be written inline instead of needing a separate section. Such a table follows [the `component` table](#the-component-table) format. | { source = "reports.wasm" }` |
+
+### Additional Fields for `trigger.http` Tables
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `route` | Required | String or table | The route which this component handles. Requests to the route will cause the component to execute. | |
+| | | String | This may be an exact route (`/example`), which matches only the given path, or may include wildcards (`/example/:id` or `/example/...`). If two routes overlap, requests are directed to the matching route with the longest prefix. See [the HTTP trigger documentation](http-trigger) for details and examples. | `"/api/cart/..."` |
+| | | Table | If the component is a private endpoint used for [local service chaining](http-outbound#local-service-chaining) then use the table value shown here. | `{ private = true }`|
+| `executor` | Optional | Table | How Spin should invoke the component. If present, this is a table. The `type` key is required and may have the values `"spin"` or `"wagi"`. If omitted. the default is `{ type = "spin"}`. See [the HTTP trigger documentation](http-trigger) for details. | `{ type = "wagi" }` |
+| | | | If `type = "spin"` there are no other keys defined. In this case, Spin calls the component using a standard Wasm component interface. Components built using Spin SDKs or Spin interface files use this convention. | `{ type = "spin" }` |
+| | | | If `type = "wagi"`, Spin calls the component's "main" (`_start`) function using [a CGI-like interface](https://github.com/deislabs/wagi). Components built using languages or toolchains that do not support Wasm interfaces will need to be called in this way. In this case, the following additional keys may be set:
* `argv` (optional): The string representation of the `argv` list that should be passed into the handler. `${SCRIPT_NAME}` will be replaced with the script name, and `${ARGS}` will be replaced with the query parameters of the request, formatted as arguments. The default is to follow the CGI specification, and pass `${SCRIPT_NAME} ${ARGS}`
* `entrypoint` (optional): The name of the function to call as the entry point to this handler. By default, it is `_start` (which in most languages translates to `main` in the source code).
See [the HTTP trigger documentation](http-trigger) for details. | `{ type = "wagi" }` |
+| `static_response` | Optional | Table | The response Spin should give to requests on this route. A route may not specify both `static_response` and `component`. The response may contain the following fields, all optional:
* `status_code`: The HTTP status code. Default is 200.
* `headers`: Table of header names and values. Default is no headers.
* `body`: The response body. Default is no response body. | { status_code = 404, body = "not found" } |
+
+### Additional Fields for `trigger.redis` Tables
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `address` | Optional | String | The address of the Redis instance to which the trigger subscribes. Use the `redis:` URL scheme. If omitted, defaults to `application.trigger.redis.address`. | `"redis://localhost:6379"` |
+| `channel` | Required | String | The Redis channel which this component handles. Messages on this channel will cause the component to execute. | `"purchases"` |
+
+## The `component` Table
+
+The keys of the `component` table, usually written as part of the table syntax e.g. `[component.my-component]`, are user-defined. (In the preceding example, the key is `my-component`). Component names must be kebab-cased, i.e. the only permitted separator is a hyphen.
+
+The value of each key is a table with the following fields.
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `description` | Optional | String | A human-readable description of the component. | `"The shopping cart API"` |
+| `source` | Required | String or table | The Wasm module which should handle the component. This must be built to work with the application trigger. It can be in one of the following formats: | |
+| | | String | * The path to a Wasm file (relative to the manifest file) | `dist/cart.wasm` |
+| | | Table | * The URL of a Wasm file downloadable over HTTP. This must be a table containing a `url` field for the Wasm file, and a `digest` field contains a SHA256 hex digest, used to check integrity. | `{ url = "https://example.com/example.wasm", digest = "sha256:6503...2375" }` |
+| | | Table (Highly Experimental) | * The registry, package and version of a component from a registry. This experimental source configuration must be a table containing a `registry` domain field, a `package` field and a `version` field. | `{ registry = "registrytest-abcd.fermyon.app", package = "component:hello-world", version="0.0.1" }` or `{ registry = "ttl.sh", package = "user:registrytest", version="1.0.0" }` |
+| `files` | Optional | Array of strings and/or tables | The [files to be made available to the Wasm module at runtime](writing-apps#including-files-with-components). This is an array, and each element of the array is either: | `[ "images/*.jpg", { source = "assets/images", destination = "/pictures" } ]` |
+| | | String | * A file path or glob pattern, relative to the manifest file. The matching file or files will be available in the Wasm module at the same relative paths. | `"images/*.jpg"` |
+| | | Table | * A file or directory to be made available to the Wasm module at a specific path. This must be a table containing a `source` field for the file or directory relative to the manifest file, and a `destination` field containing the absolute path at which to make it available. | `{ source = "assets/images", destination = "/pictures" }` |
+| `exclude_files` | Optional | Array of strings | Any files or glob patterns that should _not_ be available to the Wasm module at runtime, even though they match a `files` entry. | `[assets/images/test/**/*.*]` |
+| `allowed_http_hosts` | Optional | Array of strings | The host names or addresses to which the Wasm component is allowed to send HTTP requests. This is retained to simplify transition from the [Version 1 manifest](../v3/manifest-reference-v1.md); new applications should use `allow_outbound_hosts` instead. | `["example.com", "localhost:8081"]` |
+| `allowed_outbound_hosts` | Optional | Array of strings | The addresses to which the Wasm component is allowed to send network requests. This applies to the outbound HTTP, outbound Redis, MySQL and PostgreSQL APIs. (It does not apply to built-in storage services such as key-value and SQLite.) Each entry must contain both a scheme, a name (or IP address) and a port in `scheme://name:port` format. For known schemes, you may omit the port if it is the default for the scheme. Use `*` for wildcards. If this field is omitted or an empty list, no outbound access is permitted. | `["mysql://db.example.com", "*://example.com:4567", "http://127.0.0.1:*"]` |
+| `key_value_stores` | Optional | Array of strings | An array of key-value stores that the Wasm module is allowed to read or write. A store named `default` is provided by the Spin runtime, though modules must still be permitted to access it. In current versions of Spin, `"default"` is the only store allowed. | `["default"]` |
+| `environment` | Optional | Table | Environment variables to be set for the Wasm module. This is a table. The table keys are user-defined; the values must be strings. | `{ DB_URL = "mysql://spin:spin@localhost/dev" }` |
+| `build` | Optional | Table | The command that `spin build` uses to build this component. See [The `component.(id).build` Table](#the-componentidbuild-table) below. | `[component.cart.build]` `command = "npm run build"` |
+| `variables` | Optional | Table | Application configuration values to be made available to this component. The table keys are user-defined; the values must be strings, and may use template notation as described under [Application Variables](variables#adding-variables-to-your-applications). | `[component.cart.variables]` `api_base_url = "https://{{ api_host }}/v1"` |
+| `targets` | Optional | Array of strings | The environments that the component is expected to be compatible with. The default is the application `targets`. | ["spin-up:3.2"] |
+| `dependencies_inherit_configuration` | Optional | Boolean | If true, dependencies can invoke Spin APIs with the same permissions as the main component. If false, dependencies have no permissions (e.g. network, key-value stores, SQLite databases). The default is false. | `false` |
+| `dependencies` | Optional | Table | Specifies how to satisfy Wasm Component Model imports of this component. See [Using Component Dependencies](writing-apps.md#using-component-dependencies). | `[component.cart.dependencies]` `"example:calculator/adder" = { registry = "example.com", package = "example:adding-calculator", version = "1.0.0" }` |
+| `profile` | Optional | Table | Overrides corresponding component fields when using a [build profile](build.md#building-with-profiles). Each profile is its own named table. | `[component.cart.profile.debug]` `source = "debug/cart.was"` |
+
+## The `component.(id).build` Table
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `command` | Required | String | The command to execute on `spin build`. | `"cargo build --target wasm32-wasip2 --release"` |
+| `workdir` | Optional | String | The directory in which to execute `command`, relative to the manifest file. The default is the directory containing the manifest file. An example of where this is needed is a multi-component application where each component is its own source tree in its own directory. | `"my-project"` |
+| `watch` | Optional | Array of strings | The files or glob patterns which `spin watch` should monitor to determine if the component Wasm file needs to be rebuilt. These are relative to `workdir`, or to the directory containing the manifest file if `workdir` is not present. | `["src/**/*.rs", "Cargo.toml"]` |
+
+## The `component.(id).profile.(name)` Table
+
+| Name | Required? | Type | Value | Example |
+|-------------------------|------------|-------------|----------|-----------|
+| `source` | Optional | String or table | The Wasm module which should handle the component when run with this profile. The same formats are permitted as for `source` in the `component` table. If omitted, defaults to the value in the `component` table. | `"debug/cart.wasm"` |
+| `environment` | Optional | Table | Additional environment variables to be set for the Wasm module when run with this profile. This is a table, and is merged with the base `environment` table (with values here taking priority). | `{ TRACE_LEVEL = "full", DB_URL = "mysql://spin:spin@localhost/scratch" }` |
+| `dependencies` | Optional | Table | Overrides dependencies when run with this profile. See `dependencies` in the main `component` table. | `[component.cart.profile.debug.dependencies]` `"example:calculator/adder" = { path = "logging-calculator.wasm" }` |
+| `build.command` | Required | String | The command to execute on `spin build --profile (name)`. | `"cargo build --target wasm32-wasip2"` |
+
+## Next Steps
+
+- Learn about [writing Spin applications and their manifests](writing-apps)
+- Learn about [dynamic and runtime configuration](dynamic-configuration)
+- See more information about the [HTTP trigger](http-trigger)
+- See more information about the [Redis trigger](redis-trigger)
diff --git a/content/v4/mqtt-outbound.md b/content/v4/mqtt-outbound.md
new file mode 100644
index 00000000..0bd78b88
--- /dev/null
+++ b/content/v4/mqtt-outbound.md
@@ -0,0 +1,131 @@
+title = "MQTT Messaging"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/mqtt-outbound.md"
+
+---
+- [Sending MQTT Messages From Applications](#sending-mqtt-messages-from-applications)
+- [Granting Network Permissions to Components](#granting-network-permissions-to-components)
+ - [Configuration-Based Permissions](#configuration-based-permissions)
+- [Known Issues](#known-issues)
+
+Spin provides an experimental interface for you to send messages using the MQTT protocol.
+
+{{ details "Why do I need a Spin interface? Why can't I just use my language's MQTT library?" "Few MQTT libraries have been updated to work over the WASI 0.2 sockets interface. The Spin interface means Wasm modules can bypass this limitation by asking Spin to make the MQTT connection on their behalf." }}
+
+> Want to receive MQTT messages? Use [the MQTT trigger](https://github.com/spinkube/spin-trigger-mqtt) to handle messages in your Spin application.
+
+## Sending MQTT Messages From Applications
+
+The Spin SDK surfaces the Spin MQTT interface to your language. The set of operations defined in Spin's API is as follows:
+
+| Operation | Parameters | Returns | Behavior |
+|--------------|---------------------|---------|----------|
+| `open` | address, username, password, keep-alive | connection resource | Opens a connection to the specified MQTT server. The host must be listed in `allowed_outbound_hosts`. Other operations must be called through a connection. |
+| `publish` | topic, payload, QoS | - | Publishes the payload (a binary blob) as a message to the specified topic. |
+
+The exact detail of calling these operations from your application depends on your language:
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/mqtt/index.html)
+
+MQTT functions are available in the `spin_sdk::mqtt` module.
+
+To access an MQTT server, use the `Connection::open` function.
+
+```rust
+let connection = spin_sdk::mqtt::Connection::open(&address, &username, &password, keep_alive_secs).await?;
+```
+
+You can then call the `Connection::publish` function to send MQTT messages:
+
+```rust
+let cat_picture: Vec = request.body().to_vec();
+connection.publish("pets", &cat_picture, spin_sdk::mqtt::Qos::AtLeastOnce).await?;
+```
+
+For full details of the MQTT API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/mqtt/index.html);
+
+You can find a complete Rust code example for using outbound MQTT from an HTTP component in the [Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/mqtt-outbound).
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-mqtt.html)
+
+To access an MQTT server, use the `open` function.
+
+```ts
+import { open, QoS } from "@spinframework/spin-mqtt";
+
+let connection = Mqtt.open(address, username, password, keepAliveSecs);
+```
+
+You can then call the `publish` method on the connection to send MQTT messages:
+
+```ts
+let catPicture = new Uint8Array(await req.arraybuffer());
+connection.publish("pets", catPicture, QoS.AtleastOnce);
+```
+
+For full details of the MQTT API, see the [Spin SDK reference documentation](https://spinframework.github.io/spin-js-sdk/modules/_spinframework_spin-mqtt.html)
+
+You can find a complete Rust code example for using outbound MQTT from an HTTP component in the [Spin Rust SDK repository on GitHub](https://github.com/spinframework/spin-js-sdk/tree/main/examples/spin-host-apis/spin-mqtt).
+
+{{ blockEnd }}
+
+{{ startTab "Python"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v4/mqtt.html)
+
+To access an MQTT server, use the `open` function. You can then call the `publish` method on the connection to send MQTT messages:
+
+```python
+from spin_sdk import http, mqtt
+from spin_sdk.mqtt import Qos
+from spin_sdk.http import Request, Response
+
+class HttpHandler(http.Handler):
+ async def handle_request(self, request: Request) -> Response:
+ with await mqtt.open("mqtt://localhost:1883?client_id=client001", "user", "password", 30) as conn:
+ await conn.publish("telemetry", bytes("Eureka!", "utf-8"), Qos.AT_LEAST_ONCE)
+```
+
+For full details of the MQTT API, see the [Spin SDK reference documentation](https://spinframework.github.io/spin-python-sdk/v4/mqtt.html)
+
+You can find a complete Python code example for using outbound MQTT from an HTTP component in the [Spin Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples/spin-outbound-mqtt).
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo"}}
+
+MQTT is not available in the current version of the Go SDK.
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+## Granting Network Permissions to Components
+
+By default, Spin components are not allowed to make outgoing network requests, including MQTT. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to make network requests to a particular host, use the `allowed_outbound_hosts` field in the component manifest, specifying the host and allowed port:
+
+```toml
+[component.uses-mqtt]
+allowed_outbound_hosts = ["mqtt://messaging.example.com:1883"]
+```
+
+### Configuration-Based Permissions
+
+You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `allowed_outbound_hosts` field.
+
+## Known Issues
+
+The MQTT API is experimental and subject to change. The following issues are known:
+
+* The MQTT sender interface in the current version of Spin is known to occasionally drop errors, especially if under load. A fix is in progress.
diff --git a/content/v4/observing-apps.md b/content/v4/observing-apps.md
new file mode 100644
index 00000000..ca87ab9d
--- /dev/null
+++ b/content/v4/observing-apps.md
@@ -0,0 +1,132 @@
+title = "Observing Applications"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/observing-apps.md"
+
+---
+
+- [Application Logs](#application-logs)
+- [OpenTelemetry (OTel)](#opentelemetry-otel)
+- [The `otel` Plugin for Spin](#the-otel-plugin-for-spin)
+ - [Supported Observability Stacks](#supported-observability-stacks)
+ - [Using the `otel` Plugin for Spin](#using-the-otel-plugin-for-spin)
+- [Configuring your own observability stack](#configuring-your-own-observability-stack)
+ - [Configure the Docker compose stack](#configure-the-docker-compose-stack)
+ - [Configuring Spin](#configuring-spin)
+ - [Traces](#traces)
+ - [Metrics](#metrics)
+ - [Logs](#logs)
+
+## Application Logs
+
+Spin handles application logs by default, storing output and error messages from file system-run applications in the `.spin/logs` directory alongside the manifest file's location. Users have the option to direct logs to a specific folder using the `--log-dir` flag of the `spin up` command. Additionally, if users wish to prevent `stdout` and `stderr` from being written to disk, they can specify an empty string for the `--log-dir` flag, i.e. `spin up --log-dir ""` - effectively disabling log storage. See the [persistent logs](./running-apps#persistent-logs) section for more details.
+
+## OpenTelemetry (OTel)
+
+Spin now has support for the [OpenTelemetry (OTel)](https://opentelemetry.io/) observability standard. You can learn more about observability [here](https://opentelemetry.io/docs/concepts/observability-primer/). When configured, Spin will emit telemetry about your Spin App in the form of OTel [signals](https://opentelemetry.io/docs/concepts/signals/): traces, metrics, and logs.
+
+## The `otel` Plugin for Spin
+
+We have a plugin that makes it easy to use OpenTelemetry with Spin. If you would like to examine the source code, you can visit the [GitHub repository](https://github.com/fermyon/otel-plugin). Otherwise, follow these instructions:
+
+- To install the plugin, run the commands below:
+
+ ```sh
+ spin plugins update
+ spin plugins install otel
+ ```
+
+### Supported Observability Stacks
+
+The `otel` plugin for Spin currently supports the following observability stacks:
+
+- **Default**: A multi-container observability stack based on Prometheus, Loki, Grafana and Jaeger
+- **Aspire**: A single-container observability stack using .NET Aspire Standalone Dashboard
+
+### Using the `otel` Plugin for Spin
+
+Setting up an observability stack is as easy as executing either `spin otel setup` or `spin otel setup --aspire` in the folder where your Spin app remains.
+
+The `otel` plugin comes with a handy `spin otel up` command, which you can use to start a Spin app and instruct it to emit telemetry data to your observability stack of choice.
+
+As your observability stack of choice is executed using traditional containers, you may want to stop them once you don't need them anymore. To do so, run `spin otel cleanup`.
+
+To see all available commands of the `otel` plugin, you can run `spin otel --help`.
+
+## Configuring your own observability stack
+
+Follow this portion of the guide if you want to use Spin and OTel, but want to have more control than what the OTel plugin offers.
+
+### Configure the Docker compose stack
+
+In order to view the telemetry data you need to run an OTel compliant [collector](https://opentelemetry.io/docs/collector/) and the proper backends for each signal type. If you have Docker on your system you can easily start all the observability tools you need with the following commands:
+
+```sh
+cd ~
+git clone git@github.com:fermyon/spin.git
+cd spin/hack/o11y-stack
+docker compose up -d
+```
+
+This will start the following services:
+
+- [OTel Collector](https://opentelemetry.io/docs/collector/): Collector to receive OTel signals from Spin and forward to the appropriate backends.
+- [Jaeger](https://www.jaegertracing.io/): Backend for traces.
+- [Tempo](https://grafana.com/oss/tempo/): Alternative backend for traces.
+- [Loki](https://grafana.com/oss/loki/): Backend for logs.
+- [Prometheus](https://prometheus.io/): Backend for metrics.
+- [Grafana](https://grafana.com/oss/grafana/): Dashboard for viewing data stored in Tempo, Loki, and Prometheus.
+
+### Configuring Spin
+
+To have Spin export OTel telemetry to the collector you need to set the following environment variable:
+
+```sh
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 spin up
+```
+
+This will enable all OTel signals. If you only want to enable specific signals you can set the following environment variables individually:
+
+- Traces: `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`
+- Metrics: `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`
+- Logs: `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`
+
+For example this would enable exporting of traces and metrics:
+
+```sh
+OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics spin up
+```
+
+Storing lots of trace data can get expensive. You may want to sample traces to reduce the amount of data stored. You can set the following environment variable to control the sampling rate:
+
+```sh
+OTEL_TRACES_SAMPLER=traceidratio OTEL_TRACES_SAMPLER_ARG={desired_ratio} OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 spin up
+```
+
+Under high request loads Spin will start dropping OTel data. If keeping all of this data is important to you there are [environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-span-processor) you can set:
+
+```sh
+OTEL_BSP_MAX_CONCURRENT_EXPORTS=4 OTEL_BSP_MAX_QUEUE_SIZE=4096 OTEL_BSP_SCHEDULE_DELAY=1000 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 spin up
+```
+
+Spin supports a wide array of OTel configuration options beyond what we've covered here. You can read more about them [here](https://opentelemetry.io/docs/specs/otel/protocol/exporter/) and [here](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration).
+
+### Traces
+
+After sending some requests to your Spin app, navigate to Jaeger [http://localhost:16686](http://localhost:16686) to view the traces.
+
+
+
+Spin supports both inbound and outbound [trace context propagation](https://opentelemetry.io/docs/concepts/context-propagation/). This allows you to include Spin in your distributed traces that span all your services.
+
+### Metrics
+
+Navigate to [http://localhost:5050/explore](http://localhost:5050/explore) to view the metrics in Grafana. Make sure to choose the Prometheus data source from the top left dropdown menu.
+
+### Logs
+
+Navigate to [http://localhost:5050/explore](http://localhost:5050/explore) to view the logs in Grafana. Make sure to choose the Loki data source from the top left dropdown menu.
+
+Spin will still emit application logs as described in the [Application Logs](#application-logs) section. However, it will also send the logs to the OTel collector.
diff --git a/content/v4/other-languages.md b/content/v4/other-languages.md
new file mode 100644
index 00000000..293e3ddc
--- /dev/null
+++ b/content/v4/other-languages.md
@@ -0,0 +1,73 @@
+title = "Building Spin components in other languages"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/other-languages.md"
+
+---
+- [C/C++](#cc)
+- [C# and .NET Languages](#c-and-net-languages)
+- [Grain](#grain)
+- [Ruby](#ruby)
+- [Zig](#zig)
+
+> This document is continuously evolving as we improve language SDKs and add
+> more examples on how to build Spin components in various programming languages.
+
+> See the document on writing [Rust](./rust-components.md) and [Go](./go-components.md)
+> components for Spin for detailed guides.
+
+WebAssembly is becoming [a popular compilation target for programming languages](https://www.fermyon.com/wasm-languages/webassembly-language-support), and as language toolchains add support for the
+[WebAssembly component model](https://github.com/WebAssembly/component-model),
+building Spin components will also become supported.
+
+As a general rule:
+
+- if your language supports the
+[WebAssembly component model](https://component-model.bytecodealliance.org/),
+you can build Spin components either through an official Spin SDK
+(such as [the Spin SDK for Rust](./rust-components.md)), or through using
+bindings generators like [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen)
+(for languages such as C and C++)
+- if your language compiles to WASI, but doesn't have support for the component
+model, you can build [Spin HTTP components](./http-trigger.md) that use the
+Wagi executor — for example in languages such as
+[Grain](https://github.com/deislabs/hello-wagi-grain) or [Swift](https://github.com/fermyon/wagi-python).
+- if your language doesn't currently compile to WASI, there is no way to
+build and run Spin components in that programming language
+
+## C/C++
+
+C and C++ are both broadly supported in the WebAssembly ecosystem. WASI/Wagi support means that both can be used to write Spin apps.
+
+- The [C entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/c-lang) has examples.
+- The [C++ entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/cpp) has specific caveats for writing C++ (like exception handling)
+- The [yo-wasm](https://github.com/deislabs/yo-wasm) project makes setting up C easier.
+
+## C# and .NET Languages
+
+.NET has experimental support for WASI, so many (if not all) .NET languages, including C# and F#, can be used to write Spin applications.
+
+- The [C# entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/c-sharp) has a full example.
+
+## Grain
+
+[Grain](https://grain-lang.org/), a new functional programming language, has WASI/Wagi support and can be used to write Spin apps.
+
+- The [Grain entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/grain) has details
+- A simple [Hello World example](https://github.com/deislabs/hello-wagi-grain) shows how to use Grain
+- For a production-quality example. the [Wagi Fileserver](https://github.com/deislabs/wagi-fileserver) is written in Grain
+
+## Ruby
+
+Upstream [Ruby](https://www.ruby-lang.org/en/) officially supports WebAssembly and WASI, and we here at Fermyon have successfully run Ruby apps in Spin.
+
+- The [Ruby entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/ruby) has the latest information
+- [Ruby's 3.2.0 Preview 1 release notes](https://www.ruby-lang.org/en/news/2022/04/03/ruby-3-2-0-preview1-released/) detail WASI support
+
+## Zig
+
+Zig is a low-level systems language that has support for Wasm and WASI, and can be used to write Spin apps.
+
+- The [Zig entry in the Wasm Language Guide](https://www.fermyon.com/wasm-languages/zig) covers the basics
+- Zig's [0.4 release notes](https://ziglang.org/download/0.4.0/release-notes.html#WebAssembly-Support) explain WebAssembly support
\ No newline at end of file
diff --git a/content/v4/plugin-authoring.md b/content/v4/plugin-authoring.md
new file mode 100644
index 00000000..ced536f7
--- /dev/null
+++ b/content/v4/plugin-authoring.md
@@ -0,0 +1,143 @@
+title = "Creating Spin Plugins"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/plugin-authoring.md"
+
+---
+- [What Are Spin Plugins?](#what-are-spin-plugins)
+- [How to Find and Use a Spin Plugin](#how-to-find-and-use-a-spin-plugin)
+- [Authoring a Spin Plugin](#authoring-a-spin-plugin)
+ - [Environment Variables Available to the Plugin Executable](#environment-variables-available-to-the-plugin-executable)
+ - [Packaging a Plugin](#packaging-a-plugin)
+ - [Creating a Spin Plugin Manifest](#creating-a-spin-plugin-manifest)
+ - [Installing a Local Plugin](#installing-a-local-plugin)
+ - [Contributing a Plugin](#contributing-a-plugin)
+
+Spin plugins add new functionality or subcommands to Spin without modifying the
+Spin codebase. They make Spin easily extensible while keeping it lightweight.
+Spin plugins can add new triggers to Spin (such as the [example timer
+trigger](https://github.com/spinframework/spin/blob/main/examples/spin-timer/trigger-timer.json)),
+add deployment integrations (such as
+[`kube`](https://github.com/spinframework/spin-plugins/blob/main/manifests/kube/kube.json)),
+and more.
+
+This document will cover what Spin plugins are, how to use a plugin, and how to
+create a plugin.
+
+## What Are Spin Plugins?
+
+A Spin plugin is an executable that is added to Spin's plugins directory
+(`$XDG_DATA_HOME/spin/plugins`) upon a `spin plugins install `. The
+plugin is then ready to be used. If the plugin is an extension to the Spin CLI,
+it can now be executed directly as a subcommand: `spin `. If the
+plugin is a trigger plugin, it will be executed during `spin up` when an app
+using that trigger is run.
+
+While for now plugins are assumed to be executables, in the future, support for
+plugging in WebAssembly modules may be desirable.
+
+## How to Find and Use a Spin Plugin
+
+Spin maintains a centralized catalogue of available Spin plugins in the [Spin
+plugins repository](https://github.com/spinframework/spin-plugins). During plugin
+installation, if it does not already exist, Spin fetches the remote catalogue
+and creates a local snapshot. To ensure that the local snapshot is up to date,
+it is best to run `spin plugins update` before installing any plugins.
+
+To list available plugins, run `spin plugins search`. Now, decide which plugin to
+install. For example, the `kube` plugin, which is needed in order to deploy
+applications to [SpinKube](https://www.spinkube.dev/), can be installed by running:
+
+
+
+```bash
+$ spin plugins install kube
+```
+
+With the plugin installed, you can now call `spin kube` to run it.
+
+To upgrade installed plugins to newer versions, run `spin plugin update` to
+fetch the latest plugins to the local catalogue and `spin plugin upgrade` to perform the
+upgrade on the installed plugins.
+
+## Authoring a Spin Plugin
+
+Spin plugins are implemented as a manifest that points to one or more `.tar.gz` archives which contain the plugin executables. So, to create a plugin you must:
+
+1. Create tar archives of the executables for the platforms you want to support
+2. Compose a manifest that describes the plugin and lists the URLs for those tar archives
+
+### Environment Variables Available to the Plugin Executable
+
+Your plugin may need to know information about the instance of Spin it's running in. For example, suppose your plugin wants to call `spin build`. The trouble is that you don't know if it's on the user's system PATH. Suppose, further, that your plugin would prefer to call `spin build -c` (to build only a specific component) if it's available but can fall back to `spin build` (to build everything) if it's not. The `-c` option only exists in Spin 1.4 and above, so this optimization requires that you know which version of Spin you're running in.
+
+To help with this, when a user uses Spin to run your plugin, Spin sets a number of environment variables on the plugin process. Your code can use these environment variables to find out things like the path to the Spin binary and which version of Spin it is. When your plugin runs, the parent Spin process will set these to the right values for the _user's_ instance of Spin. In the example above, when your plugin wants to run `spin build`, it can consult the `SPIN_BIN_PATH` environment variable for the program path, and be confident that the `SPIN_VERSION` environment variable matches the Spin binary at that location.
+
+The variables Spin sets are:
+
+| Name | Meaning | Example |
+|--------------------|-----------------------------------------------------------------------------------------------------------------------|---------|
+| SPIN_BIN_PATH | The path to the Spin executable that the user is running. Use this if your plugin issues commands using the Spin CLI. | /Users/alice/.cargo/bin/spin |
+| SPIN_BRANCH | The Git branch from which the Spin executable was built. | main |
+| SPIN_BUILD_DATE | The date on which the Spin executable was built, in yyyy-mm-dd format. | 2023-05-15 |
+| SPIN_COMMIT_DATE | The date of the Git commit from which the Spin executable was built, in yyyy-mm-dd format. | 2023-05-15 |
+| SPIN_COMMIT_SHA | The SHA of the Git commit from which the Spin executable was built. | 49fb11b |
+| SPIN_DEBUG | Whether the Spin executable is a debug build. | false |
+| SPIN_TARGET_TRIPLE | The processor and operating system for which the Spin executable was built, in Rust target-triple format. | aarch64-apple-darwin |
+| SPIN_VERSION | The version of Spin. This can be used to detect features availability, or to determine pre-stable command syntax. | 1.3.0 |
+| SPIN_VERSION_MAJOR | The major version of Spin. | 1 |
+| SPIN_VERSION_MINOR | The minor version of Spin. | 3 |
+| SPIN_VERSION_PRE | The prerelease version string, or empty if this is a released version of Spin. | pre0 |
+
+> These variables aren't set if the launching Spin instance is version 1.3 or earlier. If you depend on these variables, set the `spinCompatibility` entry in the manifest to require 1.4 or above.
+
+### Packaging a Plugin
+
+After creating your plugin executable, package it along with its license as a
+`tar.gz` archive. Note that the `name` field in the plugin manifest must match
+both the binary and license name. See the [`spin-plugins`
+repository
+README](https://github.com/spinframework/spin-plugins#spin-plugin-naming-conventions)
+for more details on naming conventions.
+
+Refer to the aptly named [`example`
+plugin](https://github.com/spinframework/spin-plugins/tree/main/example) for an
+example of how to build a plugin.
+
+### Creating a Spin Plugin Manifest
+
+A Spin plugin manifest is a JSON file that conforms to the [a specific JSON
+schema](https://github.com/spinframework/spin-plugins/blob/main/json-schema/spin-plugin-manifest-schema-0.1.json).
+A manifest defines a plugin’s name, version, license, homepage (i.e. GitHub
+repo), compatible Spin version, and gives a short description of the plugin. It
+also lists the URLs of the tar archives of the plugin for various operating
+systems and platforms. The URL can point to the local path to the file by using
+the file scheme `file://`, for example, `file:///tmp/my-plugin.tar.gz`.
+
+To ensure your plugin manifest is valid, follow the steps in the [`spin-plugins`
+repository
+README](https://github.com/spinframework/spin-plugins#validating-plugin-schemas).
+
+### Installing a Local Plugin
+
+By default, Spin will look in the plugins catalogue for a plugin. However, when
+developing and testing a plugin, it is unlikely to be in the the catalogue. For
+both installs and upgrades, the `--file` or `--url` flags can be used to point
+to specific local or remote plugin manifests. For example, a local manifest
+called `practice.json` can be installed and run as follows:
+
+
+
+```bash
+$ spin plugin install --file practice.json
+$ spin practice
+```
+
+> While developing a plugin, you can [use the `pluginify` plugin to automate packaging and installation](/hub/preview/plugin_spin_pluginify) for testing in the Spin environment. This saves going through the package-manifest-install cycle every time you want to try an update!
+
+### Contributing a Plugin
+
+If you think the community would benefit from your newly created plugin, create
+a PR to add it to the [Spin plugins
+catalogue](https://github.com/spinframework/spin-plugins/tree/main/manifests)!
diff --git a/content/v4/python-components.md b/content/v4/python-components.md
new file mode 100644
index 00000000..fd9f1ad7
--- /dev/null
+++ b/content/v4/python-components.md
@@ -0,0 +1,566 @@
+title = "Building Spin Components in Python"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/python-components.md"
+
+---
+- [Prerequisite](#prerequisite)
+- [Spin's Python HTTP Request Handler Template](#spins-python-http-request-handler-template)
+- [Creating a New Python Component](#creating-a-new-python-component)
+ - [System Housekeeping (Use a Virtual Environment)](#system-housekeeping-use-a-virtual-environment)
+ - [Requirements](#requirements)
+- [Structure of a Python Component](#structure-of-a-python-component)
+- [A Simple HTTP Components Example](#a-simple-http-components-example)
+ - [Building and Running the Application](#building-and-running-the-application)
+- [A HTTP Request Parsing Example](#a-http-request-parsing-example)
+ - [Building and Running the Application](#building-and-running-the-application-1)
+- [An Outbound HTTP Example](#an-outbound-http-example)
+ - [Configuring Outbound Requests](#configuring-outbound-requests)
+ - [Building and Running the Application](#building-and-running-the-application-2)
+- [An Outbound Redis Example](#an-outbound-redis-example)
+ - [Configuring Outbound Redis](#configuring-outbound-redis)
+ - [Building and Running the Application](#building-and-running-the-application-3)
+- [Async and Streaming Idioms in Python](#async-and-streaming-idioms-in-python)
+ - [Spawning Asynchronous Tasks](#spawning-asynchronous-tasks)
+ - [Creating Futures and Streams](#creating-futures-and-streams)
+- [Storing Data in the Spin Key-Value Store](#storing-data-in-the-spin-key-value-store)
+- [Storing Data in SQLite](#storing-data-in-sqlite)
+- [AI Inferencing From Python Components](#ai-inferencing-from-python-components)
+- [Troubleshooting](#troubleshooting)
+
+With Python being a very popular language, Spin provides support for building components with Python; [using an experimental SDK](https://github.com/spinframework/spin-python-sdk). The development of the Python SDK is continually being worked on to improve user experience and also add new features.
+
+> This guide assumes you have Spin installed. If this is your first encounter with Spin, please see the [Quick Start](quickstart), which includes information about installing Spin with the Python templates, installing required tools, and creating Python applications.
+
+> This guide assumes you are familiar with the Python programming language, but if you are just getting started, be sure to check out the official Python documentation and comprehensive language reference.
+
+[**Want to go straight to the Spin SDK reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v4)
+
+## Prerequisite
+
+Ensure that you have Python 3.10 or later installed on your system. You can check your Python version by running:
+
+```bash
+python3 --version
+```
+
+If you do not have Python 3.10 or later, you can install it by following the instructions [here](https://www.python.org/downloads/).
+
+## Spin's Python HTTP Request Handler Template
+
+Spin's Python HTTP Request Handler Template can be installed from [spin-python-sdk repository](https://github.com/spinframework/spin-python-sdk) using the following command:
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin-python-sdk --update
+```
+
+The above command will install the `http-py` template and produce an output similar to the following:
+
+
+
+```text
+Copying remote template source
+Installing template http-py...
+Installed 1 template(s)
+
++---------------------------------------------+
+| Name Description |
++=============================================+
+| http-py HTTP request handler using Python |
++---------------------------------------------+
+```
+
+**Please note:** For more information about managing Spin templates, see the [templates guide](./managing-templates).
+
+## Creating a New Python Component
+
+A new Python component can be created using the following command:
+
+
+
+```bash
+$ spin new -t http-py hello-world --accept-defaults
+```
+
+### System Housekeeping (Use a Virtual Environment)
+
+Once the component is created, we can change into the `hello-world` directory, create and activate a virtual environment and then install the component's requirements:
+
+
+
+```console
+$ cd hello-world
+```
+
+Create a virtual environment directory (we are still inside the Spin app directory):
+
+
+
+```console
+# python -m venv
+$ python3 -m venv venv-dir
+```
+
+Activate the virtual environment (this command depends on which operating system you are using):
+
+
+
+```console
+# macOS command to activate
+$ source venv-dir/bin/activate
+```
+
+If you are using Windows, use the following commands:
+
+```bash
+C:\Work> python3 -m venv venv
+C:\Work> venv\Scripts\activate
+```
+
+The `(venv-dir)` will prefix your terminal prompt now:
+
+
+
+```console
+(venv-dir) user@123-456-7-8 hello-world %
+```
+
+### Requirements
+
+The `requirements.txt`, by default, contains the references to the `spin-sdk` and [`componentize-py`](https://github.com/bytecodealliance/componentize-py) packages. These can be installed in your virtual environment using the following command:
+
+
+
+```bash
+$ pip3 install -r requirements.txt
+```
+
+## Structure of a Python Component
+
+The `hello-world` directory structure created by the Spin `http-py` template is shown below:
+
+
+
+```text
+├── app.py
+├── spin.toml
+└── requirements.txt
+```
+
+The `spin.toml` file will look similar to the following:
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "hello-world"
+version = "0.1.0"
+authors = ["Your Name "]
+description = ""
+
+[[trigger.http]]
+route = "/..."
+component = "hello-world"
+
+[component.hello-world]
+source = "app.wasm"
+[component.hello-world.build]
+command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm"
+```
+
+## A Simple HTTP Components Example
+
+In Spin, HTTP components are triggered by the occurrence of an HTTP request and must return an HTTP response at the end of their execution. Components can be built in any language that compiles to WASI. If you would like additional information about building HTTP applications you may find [the HTTP trigger page](./http-trigger.md) useful.
+
+Building a Spin HTTP component using the Python SDK means defining a top-level class named HttpHandler which inherits from [`HttpHandler`](https://spinframework.github.io/spin-python-sdk/v4/wit/exports/index.html#spin_sdk.wit.exports.HttpHandler), overriding the `handle_request` method. Here is an example of the default Python code which the previous `spin new` created for us; a simple example of a request/response:
+
+
+
+```python
+from spin_sdk.http import Handler, Request, Response
+
+class HttpHandler(Handler):
+ async def handle_request(self, request: Request) -> Response:
+ return Response(
+ 200,
+ {"content-type": "text/plain"},
+ bytes("Hello from Python!", "utf-8")
+ )
+```
+
+The important things to note in the implementation above:
+
+- the `handle_request` method is the entry point for the Spin component.
+- the component returns a `spin_sdk.http.Response`.
+
+### Building and Running the Application
+
+All you need to do is run the `spin build` command from within the project's directory; as shown below:
+
+
+
+```bash
+$ spin build
+```
+
+Essentially, we have just created a new Spin compatible module which can now be run using the `spin up` command, as shown below:
+
+
+
+```bash
+$ spin up
+```
+
+With Spin running our application in our terminal, we can now go ahead (grab a new terminal) and call the Spin application via an HTTP request:
+
+
+
+```bash
+$ curl -i localhost:3000
+
+HTTP/1.1 200 OK
+content-type: text/plain
+content-length: 25
+
+Hello from Python!
+```
+
+## A HTTP Request Parsing Example
+
+The following snippet shows how you can access parts of the request e.g. the `request.method` and the `request.body`:
+
+
+
+```python
+import json
+from spin_sdk import http
+from spin_sdk.http import Request, Response
+
+class HttpHandler(http.Handler):
+ async def handle_request(self, request: Request) -> Response:
+ # Access the request.method
+ if request.method == 'POST':
+ # Read the request.body as a string
+ json_str = request.body.decode('utf-8')
+ # Create a JSON object representation of the request.body
+ json_object = json.loads(json_str)
+ # Access a value in the JSON object
+ name = json_object['name']
+ # Print the variable to console logs
+ print(name)
+ # Print the type of the variable to console logs
+ print(type(name))
+ # Print the available methods of the variable to the console logs
+ print(dir(name))
+ return Response(200,
+ {"content-type": "text/plain"},
+ bytes(f"Practicing reading the request object", "utf-8"))
+```
+
+### Building and Running the Application
+
+All you need to do is run the `spin build --up` command from within the project's directory; as shown below:
+
+
+
+```bash
+$ spin build --up
+```
+
+With Spin running our application in our terminal, we can now go ahead (grab a new terminal) and call the Spin application via an HTTP request:
+
+
+
+```bash
+$ curl --header "Content-Type: application/json" \
+ --request POST \
+ --data '{"name":"Python"}' \
+ http://localhost:3000/
+
+HTTP/1.1 200 OK
+content-type: text/plain
+content-length: 37
+date: Mon, 15 Apr 2024 04:26:00 GMT
+
+Practicing reading the request object
+```
+
+The response "Practicing reading the request object" is returned as expected. In addition, if we check the terminal where Spin is running, we will see that the console logs printed the following:
+
+The value of the variable called `name`:
+
+
+
+```bash
+Python
+```
+
+The `name` variable type (in this case a Python string):
+
+
+
+```bash
+
+```
+
+The methods available to that type:
+
+
+
+```bash
+['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
+... abbreviated ...
+'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
+```
+
+> **Please note:** All examples from this documentation page can be found in [the Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples). If you are following along with these examples and don't get the desired result perhaps compare your own code with our previously built examples (mentioned above). Also please feel free to reach out on [Spin CNCF Slack channel](https://cloud-native.slack.com/archives/C089NJ9G1V0) if you have any questions or need any additional support.
+
+## An Outbound HTTP Example
+
+This next example will create an outbound request, to obtain a random fact about animals, which will be returned to the calling code. If you would like to try this out, you can go ahead and update your existing `app.py` file from the previous step; using the following source code:
+
+
+
+```python
+from spin_sdk import http
+from spin_sdk.http import Request, Response, send
+
+class HttpHandler(http.Handler):
+ async def handle_request(self, request: Request) -> Response:
+ resp = await send(Request("GET", "https://random-data-api.fermyon.app/animals/json", {}, None))
+
+ return Response(
+ 200,
+ {"content-type": "text/plain"},
+ bytes(f"Here is an animal fact: {str(resp.body, 'utf-8')}", "utf-8")
+ )
+```
+
+### Configuring Outbound Requests
+
+The Spin framework protects your code from making outbound requests to just any URL. For example, if we try to run the above code **without any additional configuration**, we will correctly get the following error `AssertionError: HttpError::DestinationNotAllowed`. To allow our component to request the `random-data-api.fermyon.app` domain, all we have to do is add that domain to the specific component of the application that is making the request. Here is an example of an updated `spin.toml` file where we have added `allowed_outbound_hosts`:
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "hello-world"
+version = "0.1.0"
+authors = ["Your Name "]
+description = ""
+
+[[trigger.http]]
+route = "/..."
+component = "hello-world"
+
+[component.hello-world]
+source = "app.wasm"
+allowed_outbound_hosts = ["https://random-data-api.fermyon.app"]
+[component.hello-world.build]
+command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm"
+watch = ["*.py", "requirements.txt"]
+```
+
+### Building and Running the Application
+
+Run the `spin build --up` command from within the project's directory; as shown below:
+
+
+
+```bash
+$ spin build --up
+```
+
+With Spin running our application in our terminal, we can now go ahead (grab a new terminal) and call the Spin application via an HTTP request:
+
+
+
+```bash
+$ curl -i localhost:3000
+HTTP/1.1 200 OK
+content-type: text/plain
+content-length: 99
+date: Mon, 15 Apr 2024 04:52:45 GMT
+
+Here is an animal fact: {"timestamp":1713156765221,"fact":"Bats are the only mammals that can fly"}
+```
+
+## An Outbound Redis Example
+
+In this final example, we talk to an existing Redis instance. You can find the official [instructions on how to install Redis here](https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/). We also gave a quick run-through on setting up Redis with Spin in our previous article called [Persistent Storage in Webassembly Applications](https://www.fermyon.com/blog/persistent-storage-in-webassembly-applications), so please take a look at that blog if you need a hand.
+
+### Configuring Outbound Redis
+
+After installing Redis on localhost, we add two entries to the `spin.toml` file:
+
+* `variables = { redis_address = "redis://127.0.0.1:6379" }` externalizes the URL of the server to access
+* `allowed_outbound_hosts = ["redis://127.0.0.1:6379"]` enables network access to the host and port where Redis is running
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "hello-world"
+version = "0.1.0"
+authors = ["Your Name "]
+description = ""
+
+[[trigger.http]]
+route = "/..."
+component = "hello-world"
+
+[component.hello-world]
+source = "app.wasm"
+variables = { redis_address = "redis://127.0.0.1:6379" }
+allowed_outbound_hosts = ["redis://127.0.0.1:6379"]
+[component.hello-world.build]
+command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm"
+```
+
+If you are still following along, please go ahead and update your `app.py` file one more time, as follows:
+
+
+
+```python
+from spin_sdk import http, redis, variables
+from spin_sdk.http import Request, Response
+
+class HttpHandler(http.Handler):
+ async def handle_request(self, request: Request) -> Response:
+ with await redis.open(await variables.get("redis_address")) as db:
+ await db.set("foo", b"bar")
+ value = await db.get("foo")
+ await db.incr("testIncr")
+ await db.sadd("testSets", ["hello", "world"])
+ content = await db.smembers("testSets")
+ await db.srem("testSets", ["hello"])
+ assert value == b"bar", f"expected \"bar\", got \"{str(value, 'utf-8')}\""
+
+ return Response(
+ 200,
+ {"content-type": "text/plain"},
+ bytes(f"Executed outbound Redis commands: {request.uri}", "utf-8")
+ )
+```
+
+### Building and Running the Application
+
+Run the `spin build --up` command from within the project's directory; as shown below:
+
+
+
+```bash
+$ spin build --up
+```
+
+In a new terminal, make the request via the curl command, as shown below:
+
+
+
+```bash
+$ curl -i localhost:3000
+HTTP/1.1 200 OK
+content-type: text/plain
+content-length: 35
+date: Mon, 15 Apr 2024 05:53:17 GMT
+
+Executed outbound Redis commands: /
+```
+
+If we go into our Redis CLI on localhost we can see that the value `foo` which was set in the Python source code ( `redis_set(redis_address, "foo", b"bar")` ) is now correctly set to the value of `bar`:
+
+
+
+```bash
+redis-cli
+127.0.0.1:6379> get foo
+"bar"
+```
+
+## Async and Streaming Idioms in Python
+
+When a Spin API returns a potentially large number of values, such as database query APIs, the convention is to return the values as a asynchronous iterator (`componentize_py_async_support.streams.StreamReader`), plus a future (`componentize_py_async_support.futures.FutureReader`) containing the result of the operation. For example, the key-value `Store::get_keys` function returns a stream of strings and a future of 'either OK or error'. This signature is likely to be unfamiliar. The way to read it is:
+
+- Spin will stream values to you until either there are no more values, or an error occurs.
+- When that happens, you must `await` the future to find out which one it was.
+
+For example, here's how you might use `Store::get_keys`:
+
+```python
+stream, future = await store.get_keys()
+
+with stream, future:
+ while not stream.writer_dropped: # check if at the end of the stream
+ batch = await stream.read(max_count=100)
+ # do something with `batch`
+
+ result = await result.read() # check if the key stream hit an error
+ if isinstance(result, Err):
+ raise result
+ else:
+ pass
+```
+
+The future does not resolve until the stream ends, so be sure not to await it until you've finished with the stream.
+
+> If the data set is small enough to fit in memory and you are happy to wait for the last item, use `spin_sdk.util.collect` function that collects all the streamed values into a list, and checks for an error. For example, `all_keys = spin_sdk.util.collect(await store.get_keys())`.
+
+### Spawning Asynchronous Tasks
+
+You can spawn an asynchronous task in a component using the `componentize_py_async_support.spawn()` function, passing it a future. The future is then run to completion in the background. The task may outlive the entry point of your component - this is crucial in, for example, the HTTP trigger, where your handler function doesn't necessarily want to wait for all response data to be available before it starts sending.
+
+### Creating Futures and Streams
+
+The Python SDK provides `spin_sdk.wit.*` functions for creating Wasm Component Model futures and streams. The bindings contain a corresponding function for each concrete future or stream type mentioned in the Spin and WASI APIs.
+
+To create a future, call `spin_sdk.wit._future()` - for example, `spin_sdk.wit.fields_future()`. This returns a writer (which you can use later to complete the future) and a reader (representing the future which will eventually resolve to a value).
+
+To create a stream, call `spin_sdk.wit._stream()` - for example, `spin_sdk.wit.byte_stream()` is a byte stream. Again, this returns a writer and a reader. The writer is typically handed to a background task (created using `spawn`) to asynchronously send values into the stream. The reader is typically passed to an API that takes a stream parameter, for example acting as the body in an HTTP response.
+
+For generic types, the type name in the function is formed by concatenation, so you may see things like `result_option_wasi_http_types_fields_wasi_http_types_error_code_future` at the bindings level.
+
+## Storing Data in the Spin Key-Value Store
+
+Spin has a key-value store built in. For information about using it from Python, see [the key-value store API guide](kv-store-api-guide).
+
+## Storing Data in SQLite
+
+For more information about using SQLite from Python, see [SQLite storage](sqlite-api-guide).
+
+## AI Inferencing From Python Components
+
+For more information about using Serverless AI from Python, see the [Serverless AI](serverless-ai-api-guide) API guide.
+
+## Troubleshooting
+
+If you bump into issues when installing the requirements.txt. For example:
+
+
+
+```console
+error: externally-managed-environment
+× This environment is externally managed
+```
+
+Please note, this error is specific to Homebrew-installed Python installations and occurs because installing a **non-brew-packaged** Python package requires you to either:
+- create a virtual environment using `python3 -m venv path/to/venv`, or
+- use the `--break-system-packages` option in your `pip3 install` command i.e. `pip3 install -r requirements.txt --break-system-packages`
+
+We recommend installing a virtual environment using `venv`, as shown in the [system housekeeping section](#system-housekeeping-use-a-virtual-environment) above.
+
+For all Python examples, please ensure that you have Python 3.10 or later installed on your system. You can check your Python version by running:
+
+```bash
+python3 --version
+```
+
+If you do not have Python 3.10 or later, you can install it by following the instructions [here](https://www.python.org/downloads/).
diff --git a/content/v4/quickstart.md b/content/v4/quickstart.md
new file mode 100644
index 00000000..1dfaf4fa
--- /dev/null
+++ b/content/v4/quickstart.md
@@ -0,0 +1,890 @@
+title = "Taking Spin for a spin"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/quickstart.md"
+keywords = "quickstart"
+
+---
+- [Install Spin](#install-spin)
+- [Install the Prerequisites](#install-the-prerequisites)
+ - [Install a Template](#install-a-template)
+ - [Install the Tools](#install-the-tools)
+- [Create Your First Application](#create-your-first-application)
+- [Structure of a Python Component](#structure-of-a-python-component)
+- [Build Your Application](#build-your-application)
+- [Run Your Application](#run-your-application)
+- [Next Steps](#next-steps)
+
+Let's get Spin and take it from nothing to a "hello world" application!
+
+
+
+## Install Spin
+
+{{ tabs "os" }}
+
+{{ startTab "Linux"}}
+
+Download the `spin` binary along with a starter set of templates and plugins using the `install.sh` script hosted on this site:
+
+
+
+
+
+Then move the `spin` binary somewhere in your path, so you can run it from anywhere. For example:
+
+
+
+```bash
+$ sudo mv ./spin /usr/local/bin/spin
+```
+
+{{ blockEnd }}
+
+{{ startTab "macOS"}}
+
+Download the `spin` binary along with a starter set of templates and plugins using the `install.sh` script hosted on this site:
+
+
+
+
+
+Then move the `spin` binary somewhere in your path, so you can run it from anywhere. For example:
+
+
+
+```bash
+$ sudo mv ./spin /usr/local/bin/spin
+```
+
+{{ blockEnd }}
+
+{{ startTab "Windows"}}
+
+Download the Windows binary release of Spin from GitHub.
+
+Unzip the binary release and place the `spin.exe` in your system path.
+
+{{ blockEnd }}
+{{ blockEnd }}
+
+[See more options for installing Spin.](install)
+
+## Install the Prerequisites
+
+### Install a Template
+
+> If you used the installer script above, the templates are already installed, and you can skip this section!
+
+The quickest and most convenient way to start a new application is to install and use a Spin template for your preferred language.
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin --update
+Copying remote template source
+Installing template redis-rust...
+Installing template http-rust...
+... other templates omitted ...
++------------------------------------------------------------------------+
+| Name Description |
++========================================================================+
+| ... other templates omitted ... |
+| http-rust HTTP request handler using Rust |
+| redis-rust Redis message handler using Rust |
+| ... other templates omitted ... |
++------------------------------------------------------------------------+
+```
+
+Note: The Rust templates are in a repo that contains several other languages; they will all be installed together.
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript" }}
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin-js-sdk --update
+Copying remote template source
+Installing template http-js...
+Installing template http-ts...
++------------------------------------------------------------------------+
+| Name Description |
++========================================================================+
+| http-js HTTP request handler using Javascript |
+| http-ts HTTP request handler using Typescript |
++------------------------------------------------------------------------+
+```
+
+{{ blockEnd }}
+
+{{ startTab "Python" }}
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin-python-sdk --update
+Copying remote template source
+Installing template http-py...
++---------------------------------------------+
+| Name Description |
++=============================================+
+| http-py HTTP request handler using Python |
++---------------------------------------------+
+```
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo" }}
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin --update
+Copying remote template source
+Installing template redis-go...
+Installing template http-go...
++------------------------------------------------------------------------+
+| Name Description |
++========================================================================+
+| ... other templates omitted ... |
+| http-go HTTP request handler using (Tiny)Go |
+| redis-go Redis message handler using (Tiny)Go |
+| ... other templates omitted ... |
++------------------------------------------------------------------------+
+```
+
+Note: The Go templates are in a repo that contains several other languages; they will all be installed together.
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+### Install the Tools
+
+Some languages require additional tool support for Wasm:
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+You'll need the `wasm32-wasip2` target for Rust:
+
+
+
+```bash
+$ rustup target add wasm32-wasip2
+```
+
+[Learn more in the language guide.](rust-components)
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript" }}
+
+You will need `npm` installed and on the path. `npm` will install any additional build tools as part of building the application.
+
+[Learn more in the language guide.](javascript-components)
+
+{{ blockEnd }}
+
+{{ startTab "Python" }}
+
+Ensure that you have Python 3.10 or later installed on your system. You can check your Python version by running:
+
+```bash
+python3 --version
+```
+
+If you do not have Python 3.10 or later, you can install it by following the instructions [here](https://www.python.org/downloads/).
+
+You'll install all the required Python tools as part of building the application. We'll cover that in the Build Your Application section below. For now, there's nothing to do here!
+
+[Learn more in the language guide.](python-components)
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo" }}
+
+You'll need the TinyGo compiler, as the standard Go compiler does not yet support WASI exports. See the [TinyGo installation guide](https://tinygo.org/getting-started/install/).
+
+[Learn more in the language guide.](go-components)
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+## Create Your First Application
+
+{{suh_cards}}
+{{card_element "sample" "Checklist Sample App" "A checklist app that persists data in a key value store" "/hub/preview/sample_checklist" "Typescript,Http,Kv" true }}
+{{card_element "sample" "AI-assisted News Summarizer" "Read an RSS newsfeed and have AI summarize it for you" "/hub/preview/sample_newsreader_ai" "Typescript,Javascript,Ai" true }}
+{{card_element "template" "Zola SSG Template" "A template for using Zola framework to create a static webpage" "/hub/preview/template_zola_ssg" "rust" true }}
+{{blockEnd}}
+
+Now you are ready to create your first Spin application:
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+Use the `spin new` command and the `http-rust` template to scaffold a new Spin application:
+
+
+
+```bash
+$ spin new
+Pick a template to start your application with:
+ http-c (HTTP request handler using C and the Zig toolchain)
+ http-csharp (HTTP request handler using C# (EXPERIMENTAL))
+ http-go (HTTP request handler using (Tiny)Go)
+ http-grain (HTTP request handler using Grain)
+> http-rust (HTTP request handler using Rust)
+ http-swift (HTTP request handler using SwiftWasm)
+ http-zig (HTTP request handler using Zig)
+ redis-go (Redis message handler using (Tiny)Go)
+ redis-rust (Redis message handler using Rust)
+
+Enter a name for your new application: hello_rust
+Project description: My first Rust Spin application
+HTTP path: /...
+```
+
+This command created a directory with the necessary files needed to build and run a Rust Spin application. Change to that directory, and look at the files. It looks very much like a normal Rust library project:
+
+
+
+```bash
+$ cd hello_rust
+$ tree
+.
+├── .gitignore
+├── Cargo.toml
+├── spin.toml
+└── src
+ └── lib.rs
+```
+
+The additional `spin.toml` file is the manifest file, which tells Spin what events should trigger what components. In this case our trigger is HTTP, for a Web application, and we have only one component, at the route `/...`. This is a wildcard that matches any route:
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "hello_rust"
+version = "0.1.0"
+authors = ["Your Name "]
+description = "My first Rust Spin application"
+
+[[trigger.http]]
+route = "/..."
+component = "hello-rust"
+
+[component.hello-rust]
+source = "target/wasm32-wasip2/release/hello_rust.wasm"
+allowed_outbound_hosts = []
+[component.hello-rust.build]
+command = "cargo build --target wasm32-wasip2 --release"
+watch = ["src/**/*.rs", "Cargo.toml"]
+```
+
+This represents a simple Spin HTTP application (triggered by an HTTP request). It has:
+
+* A single HTTP trigger, for the `/...` route, associated with the `hello-rust` component. `/...` is a wildcard, meaning it will match any route. When the application gets an HTTP request that matches this route - that is, any HTTP request at all! - Spin will run the `hello-rust` component.
+* A single component called `hello-rust`, whose implementation is in the associated `hello_rust.wasm` WebAssembly component. When, in response to the HTTP trigger, Spin runs this component, it will execute the HTTP handler in `hello_rust.wasm`. (We're about to see the source code for that.)
+
+[Learn more about the manifest here.](./writing-apps)
+
+Now let's have a look at the code. Below is the complete source
+code for a Spin HTTP component written in Rust — a regular Rust function that
+takes an HTTP request as a parameter and returns an HTTP response, and it is
+annotated with the `http_service` macro which identifies it as the entry point
+for HTTP requests:
+
+```rust
+use spin_sdk::http::{IntoResponse, Request, Response};
+use spin_sdk::http_service;
+
+/// A simple Spin HTTP component.
+#[http_service]
+async fn handle_hello_rust(req: Request) -> anyhow::Result {
+ println!("Handling request to {:?}", req.headers().get("spin-full-url"));
+ Ok(Response::builder()
+ .status(200)
+ .header("content-type", "text/plain")
+ .body("Hello World!".to_string())?)
+}
+```
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript"}}
+
+Use the `spin new` command and the `http-ts` template to scaffold a new Spin application. (If you prefer JavaScript to TypeScript, the `http-js` template is very similar.):
+
+
+
+```bash
+$ spin new
+Pick a template to start your application with:
+ http-js (HTTP request handler using Javascript)
+> http-ts (HTTP request handler using Typescript)
+Enter a name for your new application: hello_typescript
+Project description: My first TypeScript Spin application
+HTTP path: /...
+```
+
+This command created a directory with the necessary files needed to build and run a TypeScript Spin application. Change to that directory, and look at the files. It looks similar to a lot of NPM projects:
+
+
+
+```bash
+$ cd hello_typescript
+$ tree
+.
+├── config
+│ └── knitwit.json
+├── package.json
+├── spin.toml
+├── src
+│ └── index.ts
+├── tsconfig.json
+└── webpack.config.js
+```
+
+The additional `spin.toml` file is the manifest file, which tells Spin what events should trigger what components. In this case our trigger is HTTP, for a Web application, and we have only one component, at the route `/...`. This is a wildcard that matches any route:
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "hello_typescript"
+version = "0.1.0"
+authors = ["Your Name "]
+description = "My first TypeScript Spin application"
+
+[[trigger.http]]
+route = "/..."
+component = "hello-typescript"
+
+[component.hello-typescript]
+source = "dist/hello-typescript.wasm"
+exclude_files = ["**/node_modules"]
+[component.hello-typescript.build]
+command = "npm run build"
+```
+
+This represents a simple Spin HTTP application (triggered by an HTTP request). It has:
+
+* A single HTTP trigger, for the `/...` route, associated with the `hello-typescript` component. `/...` is a wildcard, meaning it will match any route. When the application gets an HTTP request that matches this route - that is, any HTTP request at all! - Spin will run the `hello-typescript` component.
+* A single component called `hello-typescript`, whose implementation is in the associated `hello-typescript.wasm` WebAssembly component. When, in response to the HTTP trigger, Spin runs this component, it will execute the HTTP handler in `hello-typescript.wasm`. (We're about to see the source code for that.)
+
+[Learn more about the manifest here.](./writing-apps)
+
+Now let's have a look at the code. Below is the complete source
+code for a Spin HTTP component written in TypeScript — A function is attached to the fetch event listener which receives and responds to the HTTP request.
+
+```javascript
+❯ cat hello-world/src/index.ts
+import { AutoRouter } from 'itty-router';
+
+let router = AutoRouter();
+
+router
+ .get("/", () => new Response("hello universe"))
+ .get('/hello/:name', ({ name }) => `Hello, ${name}!`)
+
+//@ts-ignore
+addEventListener('fetch', async (event: FetchEvent) => {
+ event.respondWith(router.fetch(event.request));
+});
+```
+
+{{ blockEnd }}
+
+{{ startTab "Python"}}
+
+You can install the Spin template for Python HTTP handlers from the [spin-python-sdk repository](https://github.com/spinframework/spin-python-sdk) using the following command:
+
+
+
+```bash
+$ spin templates install --git https://github.com/spinframework/spin-python-sdk --update
+```
+
+The above command will install the `http-py` template and produce an output similar to the following:
+
+
+
+```text
+Copying remote template source
+Installing template http-py...
+Installed 1 template(s)
+
++---------------------------------------------+
+| Name Description |
++=============================================+
+| http-py HTTP request handler using Python |
++---------------------------------------------+
+```
+
+**Please note:** For more information about managing Spin templates, see the [templates guide](./managing-templates).
+
+This command created a directory with the necessary files needed to build and run a Python Spin application. Change to that directory, and look at the files. It contains a minimal Python application:
+
+
+
+```bash
+$ spin new -t http-py hello-python --accept-defaults
+```
+
+Once the component is created, we can change into the `hello-python` directory, create and activate a virtual environment and then install the component's requirements:
+
+
+
+```console
+$ cd hello-python
+```
+
+Create a virtual environment directory (we are still inside the Spin app directory):
+
+
+
+```console
+# python -m venv
+$ python3 -m venv venv-dir
+```
+
+Activate the virtual environment (this command depends on which operating system you are using):
+
+
+
+```console
+# macOS & Linux command to activate
+$ source venv-dir/bin/activate
+```
+
+If you are using Windows, use the following commands:
+
+```bash
+C:\Work> python3 -m venv venv
+C:\Work> venv\Scripts\activate
+```
+
+The `(venv-dir)` will prefix your terminal prompt now:
+
+
+
+```console
+(venv-dir) user@123-456-7-8 hello-python %
+```
+
+The `requirements.txt`, by default, contains the references to the `spin-sdk` and `componentize-py` packages. These can be installed in your virtual environment using the following command:
+
+
+
+```bash
+$ pip3 install -r requirements.txt
+Collecting spin-sdk==4.0.0 (from -r requirements.txt (line 1))
+ Using cached spin_sdk-4.0.0-py3-none-any.whl.metadata (16 kB)
+Collecting componentize-py==0.22.0 (from -r requirements.txt (line 2))
+ Using cached componentize_py-0.22.0-cp37-abi3-macosx_10_12_x86_64.whl.metadata (3.4 kB)
+Using cached spin_sdk-4.0.0-py3-none-any.whl (94 kB)
+Using cached componentize_py-0.22.0-cp37-abi3-macosx_10_12_x86_64.whl (38.8 MB)
+Installing collected packages: spin-sdk, componentize-py
+Successfully installed componentize-py-0.22.0 spin-sdk-4.0.0
+```
+
+## Structure of a Python Component
+
+The `hello-python` directory structure created by the Spin `http-py` template is shown below:
+
+
+
+```text
+├── app.py
+├── spin.toml
+└── requirements.txt
+```
+
+The additional `spin.toml` file is the manifest file, which tells Spin what events should trigger what components. In this case our trigger is HTTP, for a Web application, and we have only one component, at the route `/...`. This is a wildcard that matches any route.
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "hello-python"
+version = "0.1.0"
+authors = ["Your Name "]
+description = "My first Python Spin application"
+
+[[trigger.http]]
+route = "/..."
+component = "hello-python"
+
+[component.hello-python]
+source = "app.wasm"
+[component.hello-python.build]
+command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm"
+watch = ["*.py", "requirements.txt"]
+```
+This represents a simple Spin HTTP application (triggered by an HTTP request). It has:
+
+* A single HTTP trigger, for the `/...` route, associated with the `hello-python` component. `/...` is a wildcard, meaning it will match any route. When the application gets an HTTP request that matches this route - that is, any HTTP request at all! - Spin will run the `hello-python` component.
+* A single component called `hello-python`, whose implementation is in the associated `app.wasm` WebAssembly component. When, in response to the HTTP trigger, Spin runs this component, it will execute the HTTP handler in `app.wasm`. (We're about to see the source code for that.)
+
+[Learn more about the manifest here.](./writing-apps)
+
+Now let's have a look at the code. Below is the complete source
+code for a Spin HTTP component written in Python — a regular function named `handle_request` that
+takes an HTTP request as a parameter and returns an HTTP response.
+
+
+
+```python
+from spin_sdk.http import Handler, Request, Response
+
+class HttpHandler(Handler):
+ async def handle_request(self, request: Request) -> Response:
+ return Response(
+ 200,
+ {"content-type": "text/plain"},
+ bytes("Hello from Python!", "utf-8")
+ )
+```
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo"}}
+
+Use the `spin new` command and the `http-go` template to scaffold a new Spin application.
+
+
+
+```bash
+$ spin new
+Pick a template to start your application with:
+ http-c (HTTP request handler using C and the Zig toolchain)
+ http-empty (HTTP application with no components)
+> http-go (HTTP request handler using (Tiny)Go)
+ http-grain (HTTP request handler using Grain)
+ http-php (HTTP request handler using PHP)
+ http-rust (HTTP request handler using Rust)
+Enter a name for your new application: hello_go
+Description: My first Go Spin application
+HTTP path: /...
+```
+
+This command created a directory with the necessary files needed to build and run a Go Spin application using the TinyGo compiler. Change to that directory, and look at the files. It looks very much like a normal Go project:
+
+
+
+```bash
+$ cd hello_go
+$ tree
+.
+├── go.mod
+├── go.sum
+├── main.go
+└── spin.toml
+```
+
+The additional `spin.toml` file is the manifest file, which tells Spin what events should trigger what components. In this case our trigger is HTTP, for a Web application, and we have only one component, at the route `/...`. This is a wildcard that matches any route.
+
+
+
+```toml
+spin_manifest_version = 2
+
+[application]
+name = "hello_go"
+version = "0.1.0"
+authors = ["Your Name "]
+description = "My first Go Spin application"
+
+[[trigger.http]]
+route = "/..."
+component = "hello-go"
+
+[component.hello-go]
+source = "main.wasm"
+allowed_outbound_hosts = []
+[component.hello-go.build]
+command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ."
+```
+
+This represents a simple Spin HTTP application (triggered by an HTTP request). It has:
+
+* A single HTTP trigger, for the `/...` route, associated with the `hello-go` component. `/...` is a wildcard, meaning it will match any route. When the application gets an HTTP request that matches this route - that is, any HTTP request at all! - Spin will run the `hello-go` component.
+* A single component called `hello-go`, whose implementation is in the associated `main.wasm` WebAssembly component. When, in response to the HTTP trigger, Spin runs this component, it will execute the HTTP handler in `main.wasm`. (We're about to see the source code for that.)
+
+[Learn more about the manifest here.](./writing-apps)
+
+Now let's have a look at the code. Below is the complete source
+code for a Spin HTTP component written in Go. Notice where the work is done. Because this is a component
+rather than an application, there is no `main` function. Instead, the `init` function
+sets up a callback, and passes that callback to `spinhttp.Handle` to register it as
+the handler for HTTP requests. You can learn more about this structure
+in the [Go language guide](go-components).
+
+```go
+package main
+
+import (
+ "fmt"
+ "net/http"
+
+ spinhttp "github.com/spinframework/spin-go-sdk/v2/http"
+)
+
+func init() {
+ spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ fmt.Fprintln(w, "Hello Fermyon!")
+ })
+}
+```
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+## Build Your Application
+
+The Spin template creates starter source code. Now you need to turn that into a Wasm module. The template puts build instructions for each component into the manifest. Use the `spin build` command to run them:
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+```bash
+$ spin build
+Executing the build command for component hello-rust: cargo build --target wasm32-wasip2 --release
+ Updating crates.io index
+ Updating git repository `https://github.com/spinframework/spin`
+ Updating git repository `https://github.com/bytecodealliance/wit-bindgen`
+ Compiling anyhow v1.0.69
+ Compiling version_check v0.9.4
+ # ...
+ Compiling spin-sdk v6.0.0
+ Compiling hello-rust v0.1.0 (/home/ivan/testing/start/hello_rust)
+ Finished release [optimized] target(s) in 11.94s
+Finished building all Spin components
+```
+
+If the build fails, check:
+
+* Are you in the `hello_rust` directory?
+* Did you successfully [install the `wasm32-wasip2` target](#install-the-tools)?
+* Is your version of Rust up to date (`cargo --version`)? The Spin SDK needs Rust 1.91 or above.
+
+If you would like to know what build command Spin runs for a component, you can find it in the manifest, in the `component.(id).build` section:
+
+```toml
+[component.hello-rust.build]
+command = "cargo build --target wasm32-wasip2 --release"
+```
+
+You can always run this command manually; `spin build` is a shortcut to save you having to remember it.
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript"}}
+
+As normal for NPM projects, before you build for the first time, you must run `npm install`:
+
+
+
+```bash
+$ npm install
+
+added 141 packages, and audited 142 packages in 13s
+
+20 packages are looking for funding
+ run `npm fund` for details
+
+found 0 vulnerabilities
+```
+
+Then run `spin build`:
+
+
+
+```bash
+$ spin build
+Executing the build command for component hello-typescript: npm run build
+
+> hello-typescript@1.0.0 build
+> knitwit --out-dir build/wit/knitwit --out-world combined && npx webpack --mode=production && npx mkdirp target && npx j2w -i dist.js -d combined-wit -n combined -o target/hello-typescript.wasm
+
+asset spin.js 4.57 KiB [emitted] (name: main)
+runtime modules 670 bytes 3 modules
+./src/index.ts 2.85 KiB [built] [code generated]
+webpack 5.75.0 compiled successfully in 1026 ms
+
+Finished building all Spin components
+```
+
+If you would like to know what build command Spin runs for a component, you can find it in the manifest, in the `component.(id).build` section:
+
+```toml
+[component.hello-typescript.build]
+command = ["npm install", "npm run build"]
+```
+
+You can always run this command manually; `spin build` is a shortcut.
+
+{{ blockEnd }}
+
+{{ startTab "Python"}}
+
+As a standard practice for Python, create and activate a virtual env:
+
+If you are on a Mac/Linux based operating system use the following commands:
+
+```bash
+$ python3 -m venv venv
+$ source venv/bin/activate
+```
+
+If you are using Windows, use the following commands:
+
+```bash
+C:\Work> python3 -m venv venv
+C:\Work> venv\Scripts\activate
+```
+
+Install `componentize-py` and `spin-sdk` packages
+
+
+
+```bash
+$ pip3 install -r requirements.txt
+```
+
+Then run:
+
+
+
+```bash
+$ spin build
+Building component hello-python with `componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm
+Component built successfully
+Finished building all Spin components
+```
+
+If the build fails, check:
+
+* Are you in the `hello-python` directory?
+* Did you install the requirements?
+* Is your virtual environment still activated?
+
+If you would like to know what build command Spin runs for a component, you can find it in the manifest, in the `component.(id).build` section:
+
+```toml
+[component.hello-python.build]
+command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm"
+```
+
+You can always run this command manually; `spin build` is a shortcut.
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo"}}
+
+
+
+```bash
+$ spin build
+Executing the build command for component hello-go: tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm .
+go: downloading github.com/fermyon/spin/sdk/go v0.10.0
+Finished building all Spin components
+```
+
+If the build fails, check:
+
+* Are you in the `hello_go` directory?
+* Did you successfully [install TinyGo](#install-the-tools)?
+* Are your versions of Go and TinyGo up to date? The Spin SDK needs TinyGo 0.35 or above and Go 1.22 or above.
+* Set Environment Variable `CGO_ENABLED=1`. (Since the Go SDK is built using CGO, it requires the CGO_ENABLED=1 environment variable to be set.)
+
+If you would like to know what build command Spin runs for a component, you can find it in the manifest, in the `component.(id).build` section:
+
+```toml
+[component.hello-go.build]
+command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ."
+```
+
+You can always run this command manually; `spin build` is a shortcut to save you having to remember it.
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+> `spin build` can be used to build all components defined in the Spin manifest
+> file at the same time, and also has a flag that starts the application after
+> finishing the compilation, `spin build --up`.
+>
+> For more details, see the [page about building Spin applications](./build.md).
+
+## Run Your Application
+
+Now that you have created the application and built the component, you can _spin up_
+the application (pun intended):
+
+
+
+```bash
+$ spin up
+Serving http://127.0.0.1:3000
+Available Routes:
+ hello-typescript: http://127.0.0.1:3000 (wildcard)
+```
+
+> If another program is using port 3000, add the `--listen` flag. E.g. `spin up --listen 127.0.0.1:12345`.
+
+> You can also run a Spin application using `spin watch`. This automatically rebuilds code and reloads content whenever they change. [Learn more.](./running-apps.md#monitoring-applications-for-changes)
+
+Spin instantiates all components from the application manifest, and
+creates the router configuration for the HTTP trigger according to the routes in the manifest. The
+component can now be invoked by making requests to `http://localhost:3000/`
+(or any path under that, since it's a wildcard):
+
+
+
+```bash
+$ curl -i localhost:3000
+HTTP/1.1 200 OK
+content-type: text/plain
+content-length: 14
+date = "2023-11-04T00:00:01Z"
+
+Hello, Spin
+```
+
+> The `curl` output may vary based on which language SDK you use.
+
+You'll also see any logging (stdout/stderr) from the generated code printed to the console where Spin is running. For more details, see the [page about running Spin applications](./running-apps.md).
+
+Congratulations! You just created, built and ran your first Spin application!
+
+## Next Steps
+
+- Learn more about [writing Spin components and manifests](writing-apps)
+- Learn how to [build your Spin application code](build)
+- Learn how to [deploy your application to a cloud host](deploying)
diff --git a/content/v4/rdbms-storage.md b/content/v4/rdbms-storage.md
new file mode 100644
index 00000000..5ebf276d
--- /dev/null
+++ b/content/v4/rdbms-storage.md
@@ -0,0 +1,267 @@
+title = "Relational Database Storage"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/rdbms-storage.md"
+
+---
+- [Using MySQL and PostgreSQL From Applications](#using-mysql-and-postgresql-from-applications)
+- [Application Development Considerations](#application-development-considerations)
+ - [PostgreSQL Range Queries](#postgresql-range-queries)
+- [Granting Network Permissions to Components](#granting-network-permissions-to-components)
+ - [Configuration-Based Permissions](#configuration-based-permissions)
+
+Spin provides two interfaces for relational (SQL) databases:
+
+* A built-in [SQLite Database](./sqlite-api-guide), which is always available and requires no management on your part.
+* "Bring your own database" support for MySQL and PostgreSQL, where you host and manage the database outside of Spin.
+
+This page covers the "bring your own database" scenario. See [SQLite Storage](./sqlite-api-guide) for the built-in service.
+
+{{ details "Why do I need a Spin interface? Why can't I just use my language's database libraries?" "The current version of the WebAssembly System Interface (WASI) doesn't provide a sockets interface, so database libraries that depend on sockets can't be built to Wasm. The Spin interface means Wasm modules can bypass this limitation by asking Spin to make the database connection on their behalf." }}
+
+## Using MySQL and PostgreSQL From Applications
+
+The Spin SDK surfaces the Spin MySQL and PostgreSQL interfaces to your language. The set of operations is the same across both databases:
+
+| Operation | Parameters | Returns | Behavior |
+|------------|----------------------------|---------------------|----------|
+| `open` | address | connection resource | Opens a connection to the specified database. The host must be listed in `allowed_outbound_hosts`. Other operations must be called through a connection. |
+| `query` | statement, SQL parameters | database records | Runs the specified statement against the database, returning the query results as a set of rows. |
+| `execute` | statement, SQL parameters | integer (not MySQL) | Runs the specified statement against the database, returning the number of rows modified by the statement. (MySQL does not return the modified row count.) |
+
+> The PostgreSQL interface is asynchronous (a blocking one is available for backward compatibility); the MySQL interface is blocking.
+
+The exact detail of calling these operations from your application depends on your language:
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/index.html)
+
+MySQL functions are available in the `spin_sdk::mysql` module, and PostgreSQL functions in the `spin_sdk::pg` module.
+
+The function names match the operations above. This example shows MySQL (blocking):
+
+```rust
+use spin_sdk::mysql::{self, Connection, Decode, ParameterValue};
+
+let connection = Connection::open(&address)?;
+
+let params = vec![ParameterValue::Int32(id)];
+
+let rowset = connection.query("SELECT id, name FROM pets WHERE id = ?", ¶ms)?;
+
+// MySQL returns the rows as a vector
+match rowset.rows.first() {
+ None => /* no rows matched query */,
+ Some(row) => {
+ let name = String::decode(&row[1])?;
+ }
+}
+```
+
+PostgreSQL (async) uses a `QueryResult` struct to encapsulate the streaming results:
+
+```rust
+use spin_sdk::pg::{Connection, Decode, ParameterValue};
+
+let connection = Connection::open(&address).await?;
+
+let mut query_result = connection.query(
+ "SELECT id, name FROM pets WHERE id = $1",
+ &[ParameterValue::Int32(id)]
+).await?;
+
+// PostgreSQL returns the rows asynchronously (stream-like)
+while let Some(row) = query_result.next().await {
+ let name = row.get::("name")?;
+ // ... more processing ...
+}
+
+// Check if the row stream ended due to completion or an error
+query_result.result().await?;
+```
+
+> If you are querying for a small result set, you can load all the rows into a `Vec` by calling `QueryResult::collect()`. This can be more convenient for some operations.
+
+**Notes**
+
+* Parameters are instances of the `ParameterValue` enum; you must wrap raw values in this type.
+* A row is a vector of the `DbValue` enum. Use the `Decode` trait to access conversions to common types.
+* Modified row counts are returned as `u64`.
+* All functions wrap the return in `anyhow::Result`.
+
+You can find complete examples for using relational databases in the Spin repository on GitHub ([MySQL](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/mysql), [PostgreSQL](https://github.com/spinframework/spin-rust-sdk/tree/main/examples/postgres)).
+
+For full information about the MySQL and PostgreSQL APIs, see [the Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/index.html).
+
+{{ blockEnd }}
+
+{{ startTab "TypeScript"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-js-sdk/)
+
+The code below is an [Outbound MySQL example](https://github.com/spinframework/spin-js-sdk/tree/main/examples/spin-host-apis/spin-mysql). There is also an outbound [PostgreSQL example](https://github.com/spinframework/spin-js-sdk/tree/main/examples/spin-host-apis/spin-postgres) available.
+
+```ts
+// https://itty.dev/itty-router/routers/autorouter
+import { AutoRouter } from 'itty-router';
+import { open } from '@spinframework/spin-mysql';
+
+// Connects as the root user without a password
+const DB_URL = "mysql://root:@127.0.0.1/spin_dev"
+
+/*
+ Run the following commands to setup the instance:
+ create database spin_dev;
+ use spin_dev;
+ create table test(id int, val int);
+ insert into test values (4,4);
+*/
+
+let router = AutoRouter();
+
+router
+ .get("/", () => {
+ let conn = open(DB_URL);
+ conn.execute('delete from test where id=?', [4]);
+ conn.execute('insert into test values (4,5)', []);
+ let ret = conn.query('select * from test', []);
+
+ return new Response(JSON.stringify(ret, null, 2));
+ })
+
+//@ts-ignore
+addEventListener('fetch', async (event: FetchEvent) => {
+ event.respondWith(router.fetch(event.request));
+});
+```
+
+{{ blockEnd }}
+
+{{ startTab "Python"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://spinframework.github.io/spin-python-sdk/v4/)
+
+The code below shows use of the [Postgres](https://spinframework.github.io/spin-python-sdk/v4/postgres.html) module and its [open](https://spinframework.github.io/spin-python-sdk/v4/postgres.html#spin_sdk.postgres.open) function for opening a connection to the database:
+
+```python
+from spin_sdk import http, postgres
+from spin_sdk.http import Request, Response
+
+class HttpHandler(http.Handler):
+ async def handle_request(self, request: Request) -> Response:
+ with await postgres.open("user=postgres dbname=spin_dev host=localhost sslmode=disable password=password") as db:
+ print(db.query("SELECT * FROM test", []))
+
+ return Response(
+ 200,
+ {"content-type": "text/plain"},
+ bytes("Hello from Python!", "utf-8")
+ )
+```
+
+You can find a complete outbound PostgreSQL example in the [Spin Python SDK repository on GitHub](https://github.com/spinframework/spin-python-sdk/tree/main/examples/spin-postgres). There is also an [Outbound MySQL example](https://github.com/spinframework/spin-python-sdk/tree/main/examples/spin-mysql) available.
+
+{{ blockEnd }}
+
+{{ startTab "TinyGo"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2)
+
+MySQL functions are available in the `github.com/spinframework/spin-go-sdk/v2/mysql` package, and PostgreSQL in `github.com/spinframework/spin-go-sdk/v2/pg`. [See Go Packages for reference documentation.](https://pkg.go.dev/github.com/spinframework/spin-go-sdk/v2)
+
+The package follows the usual Go database API. Use `Open` to return a connection to the database of type `*sql.DB` - see the [Go standard library documentation](https://pkg.go.dev/database/sql#DB) for usage information. For example:
+
+```go
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+
+ spinhttp "github.com/spinframework/spin-go-sdk/v2/http"
+ "github.com/spinframework/spin-go-sdk/v2/pg"
+)
+
+type Pet struct {
+ ID int64
+ Name string
+ Prey *string // nullable field must be a pointer
+ IsFinicky bool
+}
+
+func init() {
+ spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
+
+ // addr is the environment variable set in `spin.toml` that points to the
+ // address of the Mysql server.
+ addr := os.Getenv("DB_URL")
+
+ // For MySQL, use `mysql.Open`
+ db := pg.Open(addr)
+ defer db.Close()
+
+ // For MySQL, use `?` placeholder syntax
+ _, err := db.Query("INSERT INTO pets VALUES ($1, 'Maya', $2, $3);", int32(4), "bananas", true)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ rows, err := db.Query("SELECT * FROM pets")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ var pets []*Pet
+ for rows.Next() {
+ var pet Pet
+ if err := rows.Scan(&pet.ID, &pet.Name, &pet.Prey, &pet.IsFinicky); err != nil {
+ fmt.Println(err)
+ }
+ pets = append(pets, &pet)
+ }
+ json.NewEncoder(w).Encode(pets)
+ })
+}
+```
+
+{{ blockEnd }}
+
+{{ blockEnd }}
+
+## Application Development Considerations
+
+This section contains notes and gotchas for developers using Spin's relational database APIs.
+
+### PostgreSQL Range Queries
+
+The PostgreSQL "range contains" operator, `<@`, is overloaded for "contains value" and "contains another range." This ambiguity can result in "wrong type" errors when executing "range contains" queries where the left hand side is parameterised.
+
+To avoid this, use a type annotation on the parameter placeholder, e.g.:
+
+```
+SELECT name FROM cats WHERE $1::int4 <@ reign
+```
+
+The ambiguity is tracked at https://github.com/sfackler/rust-postgres/issues/1258.
+
+## Granting Network Permissions to Components
+
+By default, Spin components are not allowed to make outgoing network requests, including MySQL or PostgreSQL. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to make network requests to a particular host, use the `allowed_outbound_hosts` field in the component manifest, specifying the host and allowed port:
+
+```toml
+[component.uses-db]
+allowed_outbound_hosts = ["postgres://postgres.example.com:5432"]
+```
+
+### Configuration-Based Permissions
+
+You can use [application variables](./variables.md#adding-variables-to-your-applications) in the `allowed_outbound_hosts` field.
diff --git a/content/v4/redis-outbound.md b/content/v4/redis-outbound.md
new file mode 100644
index 00000000..f74f295d
--- /dev/null
+++ b/content/v4/redis-outbound.md
@@ -0,0 +1,191 @@
+title = "Redis Storage"
+template = "main"
+date = "2023-11-04T00:00:01Z"
+enable_shortcodes = true
+[extra]
+url = "https://github.com/spinframework/spin-docs/blob/main/content/v4/redis-outbound.md"
+
+---
+- [Using Redis From Applications](#using-redis-from-applications)
+- [Granting Network Permissions to Components](#granting-network-permissions-to-components)
+ - [Configuration-Based Permissions](#configuration-based-permissions)
+
+Spin provides an interface for you to read and write the Redis key/value store, and to publish Redis pub-sub messages.
+
+{{ details "Why do I need a Spin interface? Why can't I just use my language's Redis library?" "The current version of the WebAssembly System Interface (WASI) doesn't provide a sockets interface, so Redis libraries that depend on sockets can't be built to Wasm. The Spin interface means Wasm modules can bypass this limitation by asking Spin to make the Redis connection on their behalf." }}
+
+## Using Redis From Applications
+
+The Spin SDK surfaces the Spin Redis interface to your language. The set of operations is common across all SDKs:
+
+| Operation | Parameters | Returns | Behavior |
+|--------------|---------------------|---------|----------|
+| `open` | address | connection resource | Opens a connection to the specified Redis instance. The host must be listed in `allowed_outbound_hosts`. Other operations must be called through a connection. (Exception: for JavaScript, do not call `open`, and pass the address to each data operation - see the language guides below.) |
+| Single value operations |
+| `get` | key | bytes | Returns the value of `key`. If the key does not exist, returns a zero-length array. |
+| `set` | key, bytes | - | Sets the value of `key`, overwriting any existing value. |
+| `incr` | key | integer | Increments the value at `key` by 1. If the key does not exist, its value is set to 0 first (and immediately incremented to 1). If the current value isn't an integer, or a string that represents an integer, it errors and the value is not changed. |
+| `del` | list of keys | - | Removes the specified keys. Keys that don't exist are ignored (they do _not_ cause an error). |
+| Set value operations |
+| `sadd` | key, list of strings | integer | Adds the strings to the set of values of `key`, and returns the number of newly added values. If the key does not exist, its value is set to the set of values |
+| `smembers` | key | list of strings | Returns the set of values of `key`. if the key does not exist, this is an empty set. |
+| `srem` | key, list of strings | integer | Removes the strings from the set of values of `key`, and returns the number of newly removed values. If the key does not exist, this does nothing. |
+| Pub-sub operations |
+| `publish` | channel, bytes | - | Publishes a message to the specified channel, with the specified payload bytes. |
+| General operations |
+| `execute` | command, list of argument values | list of results | Executes the specified command with the specified arguments. This is a general-purpose 'escape hatch' if you need a command that isn't covered by the built-in operations. |
+
+The exact detail of calling these operations from your application depends on your language:
+
+{{ tabs "sdk-type" }}
+
+{{ startTab "Rust"}}
+
+> [**Want to go straight to the reference documentation?** Find it here.](https://docs.rs/spin-sdk/latest/spin_sdk/redis/index.html)
+
+Redis functions are available in the `spin_sdk::redis` module.
+
+To access a Redis instance, use the `Connection::open` function.
+
+```rust
+let connection = spin_sdk::redis::Connection::open(&address).await?;
+```
+
+You can then call functions on the `Connection` to work with the Redis instance:
+
+```rust
+connection.set("my-key", &"my-value".into()).await?;
+let data = connection.get("my-key").await?;
+```
+
+For full details of the Redis API, see the [Spin SDK reference documentation](https://docs.rs/spin-sdk/latest/spin_sdk/redis/index.html);
+
+**General Notes**
+
+* Keys are of type `&str`.
+* Bytes parameters are of type `&[u8]` and return values are `Vec`.
+* Numeric return values are of type `i64`.
+* All functions wrap the return in `anyhow::Result`.
+
+**`get` Operation**
+
+* This returns a `Result