This guide shows end-to-end ExtendDO usage with the AWS CLI and AWS-published SDKs for JavaScript, Go, and Python. The examples use DynamoDB endpoint overrides so requests go to ExtendDO instead of AWS service endpoints.
ExtendDO targets ExtendDB parity, not full AWS parity. Use the Worker-native management API to create accounts, users, policies, and access keys. Use the AWS CLI or SDKs only as clients for DynamoDB-compatible data-plane requests against an ExtendDO endpoint.
For local development, configure local Wrangler variables:
cat > .dev.vars <<'EOF'
EXTENDDO_ADMIN_USER=admin
EXTENDDO_ADMIN_PASSWORD=secret
EOFStart the Worker:
cp wrangler.example.json wrangler.jsonc
pnpm run build:wasm
pnpm run generate:types
pnpm exec wrangler dev --config wrangler.jsoncUse the local endpoint in another terminal:
export EXTENDDO_ENDPOINT=http://localhost:8787
export EXTENDDO_ADMIN_USER=admin
export EXTENDDO_ADMIN_PASSWORD=secret
export EXTENDDO_ACCOUNT_ID=123456789012
export EXTENDDO_USER=quickstart
export EXTENDDO_USER_PASSWORD=secret
export AWS_REGION=us-east-1
export AWS_DEFAULT_REGION="$AWS_REGION"
export AWS_EC2_METADATA_DISABLED=true
export AWS_PAGER=""For a deployed Worker, keep the same variables and change only the endpoint:
export EXTENDDO_ENDPOINT=https://<worker>The endpoint should be the Worker origin. Do not append a DynamoDB path. ExtendDO does not proxy these requests to AWS.
Create an ExtendDO account and user:
curl -u "${EXTENDDO_ADMIN_USER}:${EXTENDDO_ADMIN_PASSWORD}" \
-X POST "$EXTENDDO_ENDPOINT/management/accounts" \
-H 'content-type: application/json' \
-d "{\"account_id\":\"$EXTENDDO_ACCOUNT_ID\",\"account_name\":\"dev\"}"
curl -u "${EXTENDDO_ADMIN_USER}:${EXTENDDO_ADMIN_PASSWORD}" \
-X POST "$EXTENDDO_ENDPOINT/management/accounts/$EXTENDDO_ACCOUNT_ID/users" \
-H 'content-type: application/json' \
-d "{\"user_name\":\"$EXTENDDO_USER\",\"password\":\"$EXTENDDO_USER_PASSWORD\"}"Attach a broad tutorial policy:
curl -u "${EXTENDDO_ADMIN_USER}:${EXTENDDO_ADMIN_PASSWORD}" \
-X PUT "$EXTENDDO_ENDPOINT/management/accounts/$EXTENDDO_ACCOUNT_ID/users/$EXTENDDO_USER/policies/FullAccess" \
-H 'content-type: application/json' \
-d '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"dynamodb:*","Resource":"*"}]}'Create an access key:
ACCESS_KEY_RESPONSE=$(curl -s \
-u "$EXTENDDO_ACCOUNT_ID/$EXTENDDO_USER:$EXTENDDO_USER_PASSWORD" \
-X POST "$EXTENDDO_ENDPOINT/management/accounts/$EXTENDDO_ACCOUNT_ID/users/$EXTENDDO_USER/access-keys")
printf '%s\n' "$ACCESS_KEY_RESPONSE"Export the returned credentials:
export AWS_ACCESS_KEY_ID=$(printf '%s' "$ACCESS_KEY_RESPONSE" | jq -r '.access_key_id')
export AWS_SECRET_ACCESS_KEY=$(printf '%s' "$ACCESS_KEY_RESPONSE" | jq -r '.secret_access_key')If jq is not available, copy access_key_id and secret_access_key from the
JSON response and export them manually. The secret is returned only when the
access key is created or imported.
This section uses the AWS CLI as a DynamoDB-compatible HTTP client. All commands must include the ExtendDO endpoint override:
alias extenddo-dynamodb='aws --endpoint-url "$EXTENDDO_ENDPOINT" --region "$AWS_REGION" dynamodb --no-cli-pager'Create a table:
extenddo-dynamodb create-table \
--table-name Music \
--attribute-definitions \
AttributeName=Artist,AttributeType=S \
AttributeName=SongTitle,AttributeType=S \
--key-schema \
AttributeName=Artist,KeyType=HASH \
AttributeName=SongTitle,KeyType=RANGE \
--billing-mode PAY_PER_REQUESTDescribe the table:
extenddo-dynamodb describe-table --table-name MusicAdd items:
extenddo-dynamodb put-item \
--table-name Music \
--item '{"Artist":{"S":"No One You Know"},"SongTitle":{"S":"Call Me Today"},"AlbumTitle":{"S":"Somewhat Famous"},"Year":{"N":"2015"}}'
extenddo-dynamodb put-item \
--table-name Music \
--item '{"Artist":{"S":"No One You Know"},"SongTitle":{"S":"My Dog Spot"},"AlbumTitle":{"S":"Hey Now"},"Year":{"N":"2016"}}'Get one item:
extenddo-dynamodb get-item \
--table-name Music \
--key '{"Artist":{"S":"No One You Know"},"SongTitle":{"S":"Call Me Today"}}'Query by partition key:
extenddo-dynamodb query \
--table-name Music \
--key-condition-expression 'Artist = :artist' \
--expression-attribute-values '{":artist":{"S":"No One You Know"}}'Update an item:
extenddo-dynamodb update-item \
--table-name Music \
--key '{"Artist":{"S":"No One You Know"},"SongTitle":{"S":"Call Me Today"}}' \
--update-expression 'SET Rating = :rating' \
--expression-attribute-values '{":rating":{"N":"5"}}' \
--return-values ALL_NEWDelete an item:
extenddo-dynamodb delete-item \
--table-name Music \
--key '{"Artist":{"S":"No One You Know"},"SongTitle":{"S":"My Dog Spot"}}'Delete the table:
extenddo-dynamodb delete-table --table-name MusicInstall the AWS-published SDK for JavaScript v3 DynamoDB client:
npm install @aws-sdk/client-dynamodbCreate usage.mjs:
import {
CreateTableCommand,
DeleteItemCommand,
DeleteTableCommand,
DynamoDBClient,
GetItemCommand,
PutItemCommand,
QueryCommand,
UpdateItemCommand
} from "@aws-sdk/client-dynamodb";
const endpoint = process.env.EXTENDDO_ENDPOINT ?? "http://localhost:8787";
const region = process.env.AWS_REGION ?? "us-east-1";
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
if (!accessKeyId || !secretAccessKey) {
throw new Error("Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY first");
}
const client = new DynamoDBClient({
endpoint,
region,
credentials: { accessKeyId, secretAccessKey }
});
const TableName = "Music";
async function main() {
await client.send(new CreateTableCommand({
TableName,
AttributeDefinitions: [
{ AttributeName: "Artist", AttributeType: "S" },
{ AttributeName: "SongTitle", AttributeType: "S" }
],
KeySchema: [
{ AttributeName: "Artist", KeyType: "HASH" },
{ AttributeName: "SongTitle", KeyType: "RANGE" }
],
BillingMode: "PAY_PER_REQUEST"
}));
await client.send(new PutItemCommand({
TableName,
Item: {
Artist: { S: "No One You Know" },
SongTitle: { S: "Call Me Today" },
AlbumTitle: { S: "Somewhat Famous" },
Year: { N: "2015" }
}
}));
await client.send(new PutItemCommand({
TableName,
Item: {
Artist: { S: "No One You Know" },
SongTitle: { S: "My Dog Spot" },
AlbumTitle: { S: "Hey Now" },
Year: { N: "2016" }
}
}));
const got = await client.send(new GetItemCommand({
TableName,
Key: {
Artist: { S: "No One You Know" },
SongTitle: { S: "Call Me Today" }
}
}));
console.log("get-item", got.Item);
const queried = await client.send(new QueryCommand({
TableName,
KeyConditionExpression: "Artist = :artist",
ExpressionAttributeValues: {
":artist": { S: "No One You Know" }
}
}));
console.log("query", queried.Items);
const updated = await client.send(new UpdateItemCommand({
TableName,
Key: {
Artist: { S: "No One You Know" },
SongTitle: { S: "Call Me Today" }
},
UpdateExpression: "SET Rating = :rating",
ExpressionAttributeValues: {
":rating": { N: "5" }
},
ReturnValues: "ALL_NEW"
}));
console.log("update-item", updated.Attributes);
await client.send(new DeleteItemCommand({
TableName,
Key: {
Artist: { S: "No One You Know" },
SongTitle: { S: "My Dog Spot" }
}
}));
await client.send(new DeleteTableCommand({ TableName }));
}
main().catch((error) => {
console.error(error);
process.exit(1);
});Run it:
node usage.mjsCreate a small Go module using the AWS-published SDK for Go v2:
mkdir extenddo-go-usage
cd extenddo-go-usage
go mod init extenddo-go-usage
go get github.com/aws/aws-sdk-go-v2/config \
github.com/aws/aws-sdk-go-v2/credentials \
github.com/aws/aws-sdk-go-v2/service/dynamodbCreate main.go:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
ctx := context.Background()
endpoint := getenv("EXTENDDO_ENDPOINT", "http://localhost:8787")
region := getenv("AWS_REGION", "us-east-1")
accessKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
secretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
if accessKeyID == "" || secretAccessKey == "" {
log.Fatal("set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY first")
}
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(region),
config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
),
)
if err != nil {
log.Fatal(err)
}
client := dynamodb.NewFromConfig(cfg, func(options *dynamodb.Options) {
options.BaseEndpoint = aws.String(endpoint)
})
tableName := "Music"
_, err = client.CreateTable(ctx, &dynamodb.CreateTableInput{
TableName: aws.String(tableName),
AttributeDefinitions: []types.AttributeDefinition{
{AttributeName: aws.String("Artist"), AttributeType: types.ScalarAttributeTypeS},
{AttributeName: aws.String("SongTitle"), AttributeType: types.ScalarAttributeTypeS},
},
KeySchema: []types.KeySchemaElement{
{AttributeName: aws.String("Artist"), KeyType: types.KeyTypeHash},
{AttributeName: aws.String("SongTitle"), KeyType: types.KeyTypeRange},
},
BillingMode: types.BillingModePayPerRequest,
})
if err != nil {
log.Fatal(err)
}
_, err = client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String(tableName),
Item: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "No One You Know"},
"SongTitle": &types.AttributeValueMemberS{Value: "Call Me Today"},
"AlbumTitle": &types.AttributeValueMemberS{Value: "Somewhat Famous"},
"Year": &types.AttributeValueMemberN{Value: "2015"},
},
})
if err != nil {
log.Fatal(err)
}
_, err = client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String(tableName),
Item: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "No One You Know"},
"SongTitle": &types.AttributeValueMemberS{Value: "My Dog Spot"},
"AlbumTitle": &types.AttributeValueMemberS{Value: "Hey Now"},
"Year": &types.AttributeValueMemberN{Value: "2016"},
},
})
if err != nil {
log.Fatal(err)
}
got, err := client.GetItem(ctx, &dynamodb.GetItemInput{
TableName: aws.String(tableName),
Key: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "No One You Know"},
"SongTitle": &types.AttributeValueMemberS{Value: "Call Me Today"},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("get-item: %#v\n", got.Item)
queried, err := client.Query(ctx, &dynamodb.QueryInput{
TableName: aws.String(tableName),
KeyConditionExpression: aws.String("Artist = :artist"),
ExpressionAttributeValues: map[string]types.AttributeValue{
":artist": &types.AttributeValueMemberS{Value: "No One You Know"},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("query count: %d\n", len(queried.Items))
updated, err := client.UpdateItem(ctx, &dynamodb.UpdateItemInput{
TableName: aws.String(tableName),
Key: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "No One You Know"},
"SongTitle": &types.AttributeValueMemberS{Value: "Call Me Today"},
},
UpdateExpression: aws.String("SET Rating = :rating"),
ExpressionAttributeValues: map[string]types.AttributeValue{
":rating": &types.AttributeValueMemberN{Value: "5"},
},
ReturnValues: types.ReturnValueAllNew,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("update-item: %#v\n", updated.Attributes)
_, err = client.DeleteItem(ctx, &dynamodb.DeleteItemInput{
TableName: aws.String(tableName),
Key: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "No One You Know"},
"SongTitle": &types.AttributeValueMemberS{Value: "My Dog Spot"},
},
})
if err != nil {
log.Fatal(err)
}
_, err = client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
TableName: aws.String(tableName),
})
if err != nil {
log.Fatal(err)
}
}
func getenv(key string, fallback string) string {
value := os.Getenv(key)
if value == "" {
return fallback
}
return value
}Run it:
go run .Install Boto3, the AWS-published SDK for Python:
python -m pip install boto3Create usage.py:
import os
import boto3
endpoint = os.environ.get("EXTENDDO_ENDPOINT", "http://localhost:8787")
region = os.environ.get("AWS_REGION", "us-east-1")
access_key_id = os.environ.get("AWS_ACCESS_KEY_ID")
secret_access_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
if not access_key_id or not secret_access_key:
raise RuntimeError("Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY first")
client = boto3.client(
"dynamodb",
endpoint_url=endpoint,
region_name=region,
aws_access_key_id=access_key_id,
aws_secret_access_key=secret_access_key,
)
table_name = "Music"
client.create_table(
TableName=table_name,
AttributeDefinitions=[
{"AttributeName": "Artist", "AttributeType": "S"},
{"AttributeName": "SongTitle", "AttributeType": "S"},
],
KeySchema=[
{"AttributeName": "Artist", "KeyType": "HASH"},
{"AttributeName": "SongTitle", "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
)
client.put_item(
TableName=table_name,
Item={
"Artist": {"S": "No One You Know"},
"SongTitle": {"S": "Call Me Today"},
"AlbumTitle": {"S": "Somewhat Famous"},
"Year": {"N": "2015"},
},
)
client.put_item(
TableName=table_name,
Item={
"Artist": {"S": "No One You Know"},
"SongTitle": {"S": "My Dog Spot"},
"AlbumTitle": {"S": "Hey Now"},
"Year": {"N": "2016"},
},
)
got = client.get_item(
TableName=table_name,
Key={
"Artist": {"S": "No One You Know"},
"SongTitle": {"S": "Call Me Today"},
},
)
print("get-item:", got.get("Item"))
queried = client.query(
TableName=table_name,
KeyConditionExpression="Artist = :artist",
ExpressionAttributeValues={":artist": {"S": "No One You Know"}},
)
print("query:", queried.get("Items", []))
updated = client.update_item(
TableName=table_name,
Key={
"Artist": {"S": "No One You Know"},
"SongTitle": {"S": "Call Me Today"},
},
UpdateExpression="SET Rating = :rating",
ExpressionAttributeValues={":rating": {"N": "5"}},
ReturnValues="ALL_NEW",
)
print("update-item:", updated.get("Attributes"))
client.delete_item(
TableName=table_name,
Key={
"Artist": {"S": "No One You Know"},
"SongTitle": {"S": "My Dog Spot"},
},
)
client.delete_table(TableName=table_name)Run it:
python usage.py- Each example creates and deletes the
Musictable. Run one example at a time, or changeTableNamebefore running examples concurrently. - The examples use a broad tutorial policy. Production users should attach narrower policies that match the required DynamoDB actions and resource ARNs.
- If you use temporary role-session credentials from the management assume-role
endpoints, also export
AWS_SESSION_TOKENor pass the session token to the SDK client. - AWS IAM, AWS STS, and AWS account-management service APIs are not ExtendDO
management APIs. Continue to use
/management/...for ExtendDO accounts, users, roles, policies, and access keys.