diff --git a/content/master/concepts/composition-functions.md b/content/master/concepts/composition-functions.md
index 6cb2215a..c9fa4a3f 100644
--- a/content/master/concepts/composition-functions.md
+++ b/content/master/concepts/composition-functions.md
@@ -303,6 +303,315 @@ See the [Crossplane CLI docs](https://github.com/crossplane/docs/pull/584) to
learn how to install and use the Crossplane CLI.
{{< /hint >}}
+## Write a composition function
+
+Composition functions let you replace complicated Compositions with code written
+in your programming language of choice. Crossplane has tools, software
+development kits (SDKs) and templates to help you write a function.
+
+
+
+Here's an example of a tiny, hello world function. This example is written in
+Go.
+
+
+```go
+func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
+ rsp := response.To(req, response.DefaultTTL)
+ response.Normal(rsp, "Hello world!")
+ return rsp, nil
+}
+```
+
+Some people design composition functions for you to use them with any kind of
+composite resource. Function Patch and Transform and Function Auto Ready work
+with any kind of composite resource.
+
+Another common pattern is to write a composition function specific to one kind
+of composite resource. The function contains all the logic needed to tell
+Crossplane what resources to create when you create a composite resource. When
+you write a composition function like this, your Composition can be small. It
+just tells Crossplane what function to run when you create, update, or delete a
+composite resource.
+
+This Composition tells Crossplane to call {{}}function-xr-xbucket{{}} whenever you create, update, or
+delete an {{}}XBucket{{}} composite
+resource. `function-xr-xbucket` is hard coded to handle `XBucket` composite
+resources.
+
+```yaml {label="dedicated"}
+apiVersion: apiextensions.crossplane.io/v1
+kind: Composition
+metadata:
+ name: example-bucket-function
+spec:
+ compositeTypeRef:
+ apiVersion: example.crossplane.io/v1
+ kind: XBucket
+ mode: Pipeline
+ pipeline:
+ - step: handle-xbucket-xr
+ functionRef:
+ name: function-xr-xbucket
+```
+
+To write a composition function, you:
+
+1. Create the function from a template.
+1. Edit the template to add your function's logic.
+1. Test your function.
+1. Build your function, and push it to a package registry.
+
+You use the [Crossplane CLI](https://github.com/crossplane/docs/pull/584) to
+create, test, build, and push a function. For example,
+
+```shell {copy-lines=none}
+# Create the function from a template.
+crossplane beta xpkg init function-example function-template-go
+Initialized package "function-example" in directory "/home/negz/control/negz/function-example" from https://github.com/crossplane/function-template-go/tree/91a1a5eed21964ff98966d72cc6db6f089ad63f4 (main)
+
+$ ls
+Dockerfile fn.go fn_test.go go.mod go.sum input LICENSE main.go package README.md renovate.json
+
+# Edit the template to add your function's logic
+$ vim fn.go
+
+# Build the function.
+$ docker build . --quiet --tag runtime
+sha256:2c31b0f7a34b34ba5b0b2dacc94c360d18aca1b99f56ca4f40a1f26535a7c1c4
+
+# Package the function.
+$ crossplane xpkg build -f package --embed-runtime-image=runtime
+
+# Push the function package to xpkg.upbound.io.
+$ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0.1.0
+```
+
+{{}}
+Crossplane has
+[language specific guides]({{[}}) to writing
+a composition function. Refer to the guide for your preferred language for a
+more detailed guide to writing a function.
+{{]}}
+
+When you're writing a composition function it's useful to know how composition
+functions work. Read the next section to learn
+[how composition functions work](#how-composition-functions-work).
+
+## How composition functions work
+
+Each composition function is actually a [gRPC](https://grpc.io) server. gRPC is
+a high performance, open source remote procedure call (RPC) framework. When you
+[install a function](#install-a-composition-function) Crossplane deploys the
+function as a gRPC server. Crossplane encrypts and authenticates all gRPC
+communication.
+
+You don't have to be a gRPC expert to write a function. Crossplane's function
+SDKs setup gRPC for you. It's useful to understand how Crossplane calls your
+function though, and how your function should respond.
+
+```mermaid
+sequenceDiagram
+ User->>+API Server: Create composite resource
+ Crossplane Pod->>+API Server: Observe composite resource
+ Crossplane Pod->>+Function Pod: gRPC RunFunctionRequest
+ Function Pod->>+Crossplane Pod: gRPC RunFunctionResponse
+ Crossplane Pod->>+API Server: Apply desired composed resources
+```
+
+When you create, update, or delete a composite resource that uses composition
+functions Crossplane calls each function in the order they appear in the
+Composition's pipeline. Crossplane calls each function by sending it a gRPC
+RunFunctionRequest. The function must respond with a gRPC RunFunctionResponse.
+
+{{}}
+You can find detailed schemas for the RunFunctionRequest and RunFunctionResponse
+RPCs in the [Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
+{{}}
+
+When Crossplane calls a function it includes four important things in the
+RunFunctionRequest.
+
+1. The __observed state__ of the composite resource, and any composed resources.
+1. The __desired state__ of the composite resource, and any composed resources.
+1. The function's __input__.
+1. The function pipeline's __context__.
+
+A function's main job is to update the __desired state__ and return it to
+Crossplane. It does this by returning a RunFunctionResponse.
+
+Most composition functions read the observed state of the composite resource,
+and use it to add composed resources to the desired state. This tells Crossplane
+which composed resources it should create or update.
+
+{{}}
+
+
+A _composed_ resource is a resource created by a composite resource. Composed
+resources are usually Crossplane managed resources (MRs), but they can be any
+kind of Crossplane resource. For example a composite resource could also create
+a ProviderConfig, or another kind of composite resource.
+
+{{}}
+
+### Observed state
+
+When you create a composite resource like this one, Crossplane _observes_ it and
+sends it to the composition function as part of the observed state.
+
+```yaml
+apiVersion: example.crossplane.io/v1
+kind: XBucket
+metadata:
+ name: example-render
+spec:
+ bucketRegion: us-east-2
+```
+
+If any composed resources already exist, Crossplane observes them and sends them
+to your function to as part of the observed state.
+
+Crossplane also observes the connection details of your composite resource and
+any composed resources. It sends them to your function as part of the observed
+state.
+
+Crossplane observes the composite resource and any composed resources once,
+right before it starts calling the functions in the pipeline. This means that
+Crossplane sends every function in the pipeline the same observed state.
+
+### Desired state
+
+Desired state is the set of the changes the function pipeline wants to make to
+the composite resource and any composed resources. When a function adds composed
+resources to the desired state Crossplane creates them.
+
+A function can change:
+
+* The `status` of the composite resource.
+* The `metadata` and `spec` of any composed resource.
+
+A function can also change the connection details and readiness of the composite
+resource. A function indicates that the composite resource is ready by telling
+Crossplane whether its composed resources are ready. When the function pipeline
+tells Crossplane that all composed resources are ready, Crossplane marks the
+composite resource as ready.
+
+A function can't change:
+
+* The `metadata` or `spec` of the composite resource.
+* The `status` of any composed resource.
+* The connection details of any composed resource.
+
+A pipeline of functions _accumulates_ desired state. This means that each
+function builds upon the desired state of previous functions in the pipeline.
+Crossplane sends a function the desired state accumulated by all previous
+functions in the pipeline. The function adds to or updates the desired state and
+then passes it on. When the last function in the pipeline has run, Crossplane
+applies the desired state it returns.
+
+{{}}
+A function __must__ copy all desired state from its RunFunctionRequest to its
+RunFunctionResponse. If a function adds a resource to its desired state the next
+function must copy it to its desired state. If it doesn't, Crossplane doesn't
+apply the resource. If the resource exists, Crossplane deletes it.
+
+A function can _intentionally_ choose not to copy parts of the desired state.
+For example a function may choose not to copy a desired resource to prevent that
+resource from existing.
+
+Most function SDKs handle copying desired state automatically.
+{{}}
+
+A function should only add the fields it cares about to the desired state. It
+should add these fields every time Crossplane calls it. If a function adds a
+field to the desired state once, but doesn't add it the next time it's called,
+Crossplane deletes the field. The same is true for composed resources. If a
+function adds a composed resource to the desired state, but doesn't add it the
+next time it's called, Crossplane deletes the composed resource.
+
+{{}}
+Crossplane uses
+[server side apply](https://kubernetes.io/docs/reference/using-api/apply/)
+to apply the desired state returned by a function pipeline. In server side apply
+terminology, the desired state is a _fully specified intent_.
+{{}}
+
+For example, if all a function wants is to make sure an S3 bucket in region
+`us-east-2` exists, it should add this resource to its desired composed
+resources.
+
+```yaml
+apiVersion: s3.aws.upbound.io/v1beta1
+kind: Bucket
+spec:
+ forProvider:
+ region: us-east-2
+```
+
+Even if the Bucket already exists and has other `spec` fields, or a `status`,
+`name`, `labels`, etc the function should omit them. The function should only
+include the fields it has an opinion about. Crossplane takes care of applying
+the fields the function cares about, merging them with the existing Bucket.
+
+{{}}
+Composition functions don't actually use YAML for desired and observed
+resources. This example uses YAML for illustration purposes only.
+{{}}
+
+### Function input
+
+If a Composition includes {{}}input{{}}
+Crossplane sends it to the function. Input is a useful way to provide extra
+configuration to a function. Supporting input is optional. Not all functions
+support input.
+
+```yaml {label="input",copy-lines="none"}
+apiVersion: apiextensions.crossplane.io/v1
+kind: Composition
+metadata:
+ name: example-render
+spec:
+ compositeTypeRef:
+ apiVersion: example.crossplane.io/v1
+ kind: XBucket
+ mode: Pipeline
+ pipeline:
+ - step: patch-and-transform
+ functionRef:
+ name: function-patch-and-transform
+ input:
+ apiVersion: pt.fn.crossplane.io/v1beta1
+ kind: Resources
+ resources:
+ - name: storage-bucket
+ base:
+ apiVersion: s3.aws.upbound.io/v1beta1
+ kind: Bucket
+ patches:
+ - type: FromCompositeFieldPath
+ fromFieldPath: spec.bucketRegion
+ toFieldPath: spec.forProvider.region
+```
+
+{{}}
+Crossplane doesn't validate function input. It's a good idea for a function to
+validate its own input.
+{{}}
+
+### Function pipeline context
+
+Sometimes two functions in a pipeline want to share information with each other
+that isn't desired state. Functions can use context for this. Any function can
+write to the pipeline context. Crossplane passes the context to all following
+functions. When Crossplane has called all functions it discards the pipeline
+context.
+
+Crossplane can write context too. If you enable the alpha
+[composition environment]({{[}}) feature Crossplane
+writes the environment to the top-level context field
+`apiextensions.crossplane.io/environment`.
+
## Disable composition functions
Crossplane enables composition functions by default. Disable support for
]